Coder Social home page Coder Social logo

badoo / mvicore Goto Github PK

View Code? Open in Web Editor NEW
1.2K 39.0 86.0 5.23 MB

MVI framework with events, time-travel, and more

Home Page: https://badoo.github.io/MVICore/

License: Other

Kotlin 99.89% Java 0.11%
mvi mvi-architecture android android-library android-architecture kotlin-android

mvicore's People

Contributors

andkulikov avatar bumblender avatar cherryperry avatar daniil-shevtsov avatar idyatlov avatar jeksor avatar journeyman avatar kissedcode avatar kyhule avatar lachlanmckee avatar lukaville avatar mark-tai avatar maximpestryakov avatar nublo avatar shikasd avatar zsoltk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mvicore's Issues

PlaybackMiddleware not working

Downloaded and launched your demo project. Tried to record and playback my actions from DebugDrawer, but nothing happened, when I pressed play. Some time later toast popped up with the text, that playback finished. What I'm doing wrong?

Configuration of custom views

Could you please help me to find out better solutions to configurate custom views?
I have 20 different features on one screen. Each feature has its own view. The app has a dynamic theme. Each view has one input/state. I need to set theme settings for each view.
I hope that anybody has some ideas or any examples of how to do it in the best way.

Why the Feature should be invoked on the same thread?

Since invocations of the reducer must always happen on the same thread, you must ensure that you observe results of your asynchronous jobs on that thread.

Extracted from here

Why don't you use scan to execute the reducer? It handle the sync so you don't care about the thread. Add the observeOn(main) in the actor is a bit ugly. If the user of the feature cares about the thread they can always add the observeOn.

Shouldn't DiffStrategy return true if the values are equal?

At the moment DiffStrategy return true if the values are different. Perhaps they should be inverted to be consistent with normal comparators. Also Rx distingUntilChanged lambdas return true if values are equal. Inverted behavour is confusing.

Features testing.

Hi guys. Do you have any examples, best practices how you tests yours features?

Private state representation for feature

I have a NavFeature which handles routes.
NavWish(path: String) goes in and NavState(module: String, args : ...) goes out.
All my code depends on Store<NavWish, NavState>.
Now I want to use a stack with NavStates, but I don't want the rest of my code to know that I'm using a Stack.

What is the best way to expose a Store-interface without the stack, but use the stack interrnally?

Roadmap

Tracking issue of what has been/will be done and for which milestone.

1.2.0

  • Source parameter in connection (#66)
  • Connector - transformer from source to source (#73)
  • MemoFeature (#76)
  • Model watcher (#82)

2.0.0

  • Previous state in newsPublisher and postProcessor (#88)
  • Add initial state to Bootrstrapper invocation (#92)
  • Multi platform / framework support (#90)
  • Parameter to delay feature start (#89)

What is the best way to handle N Features for a View?

I'm working on implementing MVICore on a complicated screen and have many Features that need to reduce down to one ViewModel. The demo app uses an example with two and a pair with an extension function wrapping Observable.combineLatest() I am finding this is not scalable enough for most situations. The code I am referring to is below:

binder.bind(combineLatest(feature1, feature2) to view using ViewModelTransformer() named "MainActivity.ViewModels")

fun <T1, T2> combineLatest(o1: ObservableSource<T1>, o2: ObservableSource<T2>): ObservableSource<Pair<T1, T2>> =

class ViewModelTransformer : (Pair<Feature1.State, Feature2.State>) -> ViewModel {

I have attempted to use RxJava's combineLatest that takes a list and a Function (docs) but have run into an issue with generics and inheritance from the Features.

I have spent some time working on a solution, but have not yet found one.

I have created an extension function like so:

<T, R> fun combineFeatures(
    features: Array<ObservableSource<T>>,
    combine: (Array<T>) -> R
): ObservableSource<R> = 
    Observable
        .combineLatest(list) {
            combine(it)
        }

However in attempting to use this like:

binder.bind(combineFeatures(arrayOf(feature1, feature2) { 
    var combinedState = CombinedState() // Initial State
    it.forEach { feature ->
        when (feature) {
            is Feature1 -> combinedState.copy(fromFeature1 = feature.data)
            is Feature2 -> combinedState.copy(fromFeature2 = feature.data)
        }
    }
    combinedState
} ) to this using ViewModelTransformer

and making the appropriate changes to the ViewModelTransformer to accept the CombinedState to then derive the data for the View. The generics/inheritance issues arise in the arrayOf(feature1, feature2) creation. Even if I define the Any type. (This looks like it could be a limitation of the JVM)

What I'm hoping for here is to see if there is already an appropriate solution to the N-Features for 1-View problem.

Please let me know if there is any other information I can provide.

Is it really Actors work in async mode?

Hi there.
First: when i place Logger in the so-called async actor

fun loadRandomImage(): Observable<Response> {
          Log.d("!@#", "thread is ${Thread.currentThread().name}")
            return service.getRandomImage()
                .randomlyThrowAnException()
                .observeOn(AndroidSchedulers.mainThread())
        }

i got
com.badoo.mvicoredemo D/!@#: thread is main
Second: when i try to emulate delay with

is LoadNewImage -> loadRandomImage()
                .delay(1000, TimeUnit.MILLISECONDS)
                .map {
                    LoadedImage(it.url!!) as Effect
                }
                .startWith(just(StartedLoading))
                .onErrorReturn { ErrorLoading(it) }

i got
Caused by: java.lang.AssertionError: Not on same thread as previous verification from your
class SameThreadVerifier

what do i wrong?

A good place for UI-related tests

Could you provide a view on how to implement tests that cover interactions with UI. In MVVM it was solely done inside ViewModel but with MVI I'm not sure where it belongs.

Say, inside a fragment we need to combine values from several edittexts and send a wish based on some criteria:

Observable.combineLatest(
    RxTextView.textChanges(newPinEditText),
    RxTextView.textChanges(pinRepeatEditText),
    BiFunction<CharSequence, CharSequence, Boolean>() { v1, v2 -> v1.length == v2.length && v1.length == limit }
)
...
.map{ some wish }

Is there an mvi way to test the logic before the map?

I know there's a concept of actor middleware that deals with async requests and other backend logic - do you think it should also cover ui-related checks? Wouldn't it bloat State?

Thanks!

Handle SingleLiveEvents like Snackbar

Hello, Guys!
Would you provide simple example on how to show Snackbar? Is it SingleLiveEvent? Or you just put the state of Snackbar into your feature's ViewState?

Add a trigger parameter to delay feature start.

Current implementation of the feature launches internal subscriptions when created. Although it is conceptually correct, the limitations of the Android framework sometimes force us to wait until onCreate to have any meaningful data.
This proposal suggests to add an optional startWhen parameter to the feature constructor, so its start can be done after creation.

Suggested update to the feature constructor:

class BaseFeature(
    initialState: State,
    ...,
    startWhen: Single<Unit> = Single.just(Unit)
)

AndroidTimeCapsule in example app

In MVI core demo app you pass AndroidTimeCapsule in Feature2 through constructor.

class Feature2(
    timeCapsule: TimeCapsule<Parcelable>? = null
) : ActorReducerFeature<Wish, Effect, State, News>(
...

How it supposed to be used with Android lifecycle methods onRestoreInstanceState and onSaveInstanceState?

Dispose a feature is "tricky"

Observable.wrap(feature).subscribe().dispose() doesn't dispose the feature.

The problem is that wrap treats feature as an ObservableSource. And it doesn't have the concept of dispose.

I know that you usually use the Binders but if you don't this is kind of strange because you expose an ObservableSource that you need to dispose. So you end with things like this:

disposable += Observable.wrap(feature).subscribe()
disposable += Observable.wrap(feature.news).subscribe()
disposable += feature

An idea is to use a real Observable. This way all will work "as expected".

mvicore artifact missing binding artifact as transient dependency

In the mvicore module, binder is declared as an api dependency but the pom published to jitpack does not include it. Is this intentional?

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.github.badoo.mvicore</groupId>
  <artifactId>mvicore</artifactId>
  <version>1.2.6</version>
  <dependencies>
    <dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxjava</artifactId>
      <version>2.2.10</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxkotlin</artifactId>
      <version>2.2.0</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-stdlib-jdk7</artifactId>
      <version>1.3.72</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-test-junit</artifactId>
      <version>1.3.72</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.amshove.kluent</groupId>
      <artifactId>kluent</artifactId>
      <version>1.23</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.nhaarman</groupId>
      <artifactId>mockito-kotlin</artifactId>
      <version>1.3.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

mvicore-demo-app: excessive strings in log

Hi there!
See the duplicate strings in logcat from LifecycleDemoActivity:

2018-11-21 10:13:58.769 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onPause
2018-11-21 10:13:58.769 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onPause
2018-11-21 10:13:58.770 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onSaveState
2018-11-21 10:13:58.774 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onStop
2018-11-21 10:13:58.827 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onStart
2018-11-21 10:13:58.828 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onRestoreState
2018-11-21 10:13:58.828 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onRestoreState
2018-11-21 10:13:58.830 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onResume
2018-11-21 10:13:58.830 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onResume
2018-11-21 10:13:58.831 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onPostResume
2018-11-21 10:13:58.831 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onPostResume
2018-11-21 10:13:58.831 14219-14219/com.badoo.mvicoredemo D/LifecycleDemo2: onPostResume

meanwhile standard logging makes things right:

    override fun onPostResume() {
        super.onPostResume()
        
        Log.d("!@#", "onPostResume")

//        events.onNext("onPostResume")
    }
---------------
2018-11-21 10:21:28.715 14219-14219/com.badoo.mvicoredemo D/!@#: onPostResume

Add initial state to Bootrstrapper invocation

We need to decide what to do in Bootstrapper based in the initial state quite often. Right now this needs to be passed manually to implementation constructor, which is quite inconvenient when e.g deciding the initial state based on TimeCapsule, as a reference cannot be stored for it inside constructor.

Proposed solution is to change Bootstrapper and BaseFeature so that initial state is passed automatically to invocation.

Example for KMP

In the description it stated that is 100% Kotlin, but in the examples I see dependencies on RxJava.
Where I can have a look pure Kotlin example that could be suitable for Kotlin Multiplatform project? Ideally if this example can demonstrate how to use MviCore between iOS and Android.

The problem of redundant view rendering

Hey everyone!
How do you guys deal with problem of unnecessary view updates?
For example, you have a counter view which one is being updated each second with a new value. And you don't need to update other views, because their state is not being changed. It means that you have redundant view rendering.
Do you have any solution to avoid this?

Question: Render the whole ViewModel

It looks like a really great lib which solves the most common problems/missings of other MVI implementations. Thank you very much for share it with us! :)
Maybe I have only one "issue":
When you render the viewmodel, because the state changed, you always render the whole object. Forexample if it contains a loading indicator boolean and a string and only the boolean changed, you also render the string.
Is this correct or I missed something?

I'm using a modified version of this code to avoid this situation:
https://github.com/OlegSheliakin/state-binder

What do you think?

Best way to use observeOn(AndroidSchedulers.mainThread())

I want to test my actor and observeOn prevent me to do it.
What is the best way to move out Android platform classes from my actor?

class InsultActor(
    private val clipboard: Clipboard,
    private val share: Share,
    private val api: InsultRepository
) : Actor<InsultState, InsultWish, InsultEffect> {

    override fun invoke(state: InsultState, wish: InsultWish): Observable<InsultEffect> =
        when (wish) {
            is InsultWish.LoadInsult -> api.generateInsult(state.lang.literal)
                    .map(Insult::insult)
                    .map(::LoadedInsult)
                    .cast(InsultEffect::class.java)
                    .startWith(just(StartedLoading))
                    .onErrorReturn(::ErrorLoading)
                    .observeOn(AndroidSchedulers.mainThread())

            is InsultWish.CopyInsult -> {
                clipboard.copy(wish.text)
                Observable.just(InsultEffect.CopiedInsult)
            }

            is InsultWish.ShareInsult -> {
                share.share(wish.text)
                Observable.just(InsultEffect.SharedInsult)
            }

            is InsultWish.LanguageDialog -> just(ShowLanguageDialog)

            is InsultWish.DismissDialog -> just(DismissLanguageDialog)

            is InsultWish.ChangeLang -> just(ChangedLang(wish.lang))

            is InsultWish.OpenAbout -> just(OpenAbout)
        }
    
}

Bootstraper starts working before newsListener was binded with view and feature

Привет.
Столкнулись с такой проблемой.
В Bootstraper вызывается invoke() до того, как newsListener будет связан с представлением и фичей и из за этого мы пропускаем событие, отправленное при старте запроса (Observable.startWith())
Как результат - ProgressDialog не будет показан при старте запроса.
Как можно решить эту проблему?

class ToursToHotelFeature(
    api: Api
) : ActorReducerFeature<ToursToHotelFeature.Wish, ToursToHotelFeature.Effect, ToursToHotelFeature.State, ToursToHotelFeature.News>(
    initialState = State(),
    actor = ActorImpl(api),
    reducer = ReducerImpl(),
    newsPublisher = NewsPublisherImpl(),
    bootstrapper = BootstraperImpl()
) {

    class BootstraperImpl() : Bootstrapper<Wish> {
        override fun invoke(): Observable<Wish> = Observable.just(Wish.StartSearch)
    }

    data class State(...)

    sealed class Wish {
        object StartSearch : Wish()
    }

    sealed class News {
        data class ShowProgressBar(val show: Boolean) : News()
    }

    sealed class Effect {
        object StartedLoading : Effect()
        data class FinishWithError(val error: Throwable) : Effect()
    }

    class ActorImpl(val api: ApiService.TourApi) : Actor<State, Wish, Effect> {
        override fun invoke(state: State, wish: Wish): Observable<Effect> = when (wish) {
            Wish.StartSearch -> {
                   api.someRequest()
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .startWith(Effect.StartedLoading)
                        .doOnError { Effect.FinishWithError(it) }
                        .onErrorReturn { Effect.FinishWithError(it) }
            }
        }
    }

    class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> {
        override fun invoke(action: Wish, effect: Effect, state: State): News? {
            return when (effect) {
                Effect.StartedLoading -> News.ShowProgressBar(true)
                is Effect.FinishWithError -> News.ShowProgressBar(false)
                else -> null
            }
        }
    }
}

Issues with EditText and suggestions

Sorry, this is a question more than an issue but I since there isn't a mail list or similar I'll write it here. Please tell me if this is not the correct place.

We use MVICore in our app and we are having two problems that I'd like to share to see if we are doing something wrong, we are missing something or they are really issues so we can create a PR to fix them:

  • The first one appears with the suggestions in a search view. When you are type a character we launch a wish. This wish ends with a call to the server to get the suggestions to autocomplete. The problem is that while the user keep typing it launch more wishes and there is no way to "dispose" the previous calls. In this case we have race conditions. Normally the last call is the last one that we receive so all seems to work, but we see edge cases where we receive those responses in different orther. How do you handle this? With rxJava is just a switchMap but we can't use it here.
  • The second problem is related with the EditText. When you start typing in the EditText you send wishes with that data. And you generates a new state with that data. And in the render you should set that data. If you do so you get an infinite loop because you are changing the EditText again so it launch another wish... Ok, this problem is kind of easy to solve: distinctUntilChange. The problem now is that we have an observeOn(mainScheduler) so we lose one frame to sync the data. If the user types fast (not really fast, just fast) we are setting an "old text" so the user loses the last typed character. Here we are fighting against the framework and losing. How do you handle this problem?

Feature to Feature binding

Hi.
In best practices you wrote next code

class BootstrapperImpl(
        private val feature1: Feature1
    ) : Bootstrapper<Wish> {
        override fun invoke(): Observable<Wish> =
            feature1.news.map { SomeWishOfFeature2  }
    }

But i can't understand best way to do this map , because news is ObservableSource and don't have map function.
Maybe you have some ktx extensions for this?

Now i do this.

class  BootstrapperImpl(
       private val feature: Feature1
   ) : Bootstrapper<Wish> {
       override fun invoke(): Observable<Wish> =
          Observable.create {
              emitter ->
              val newsListener = object : Observer<Feature1.News>{
                  override fun onNext(news: Feature1.News) {
                      when(news){
                          else->emitter.onNext(Wish.Update())
                      }
                  }
                  override fun onComplete() {
                     emitter.onComplete() 
                  }
                  override fun onSubscribe(d: Disposable) {}
                  override fun onError(e: Throwable) {}
              }
              feature.news.subscribe(newsListener)
           }
   }

Troubles with using MVICore while using RxJava 3.x.

Error message providing me by IDEA:

Cannot access 'io.reactivex.ObservableSource' which is a supertype of 'com.example.androidbasics.fragments.FirstFragmentFeature'. 
Check your module classpath for missing or conflicting dependencies

And the same one more errors in same class but with different "can't access":

Cannot access 'io.reactivex.functions.Consumer'
Cannot access 'io.reactivex.disposables.Disposable' 

It also shown randomly, for example, I just formatter file or add any symbol and code analyzer do wakeup :).

Deps related probably to this issue:

    implementation "io.reactivex.rxjava3:rxkotlin:$rxkotlin_version"
    implementation "io.reactivex.rxjava3:rxandroid:$rxandroid_version"
    implementation "io.reactivex.rxjava3:rxjava:$rxjava_version"

where rxkotlin_version, rxandroid_version is 3.0.0 and rxjava_version is 3.0.4

Fragment state to reproduce (some lines formatted to in-line): it's pure boilerplate code

class FirstFragmentFeature : ReducerFeature<Wish, State, Nothing>(
    initialState = State(), reducer = ReducerImpl()
) {
    data class State(val counter: Int = 0)

    sealed class Wish {
        object IncreaseCounter : Wish()
        data class MultiplyBy(val value: Int) : Wish()
    }

    class ReducerImpl : Reducer<State, Wish> {
        override fun invoke(state: State, wish: Wish): State {
            return when (wish) {
                Wish.IncreaseCounter -> state.copy(counter = state.counter + 1)
                is Wish.MultiplyBy -> state.copy(counter = state.counter * wish.value)
            }
        }
    }
}

Installed mvicore dependencies (in app/build.gradle): used 1.2.4 version

    implementation "com.github.badoo.mvicore:mvicore:${mvicore_version}"
    implementation "com.github.badoo.mvicore:mvicore-android:${mvicore_version}"
    implementation "com.github.badoo.mvicore:mvicore-diff:${mvicore_version}"

Best way to use DialogFragment

I use DialogFragment with the common feature of parent Fragment and another ui event transformer.

class Fragment : 
SourceFragment<FragmentUiEvent>(), 
Consumer<FragmentViewModel> {
...
    private val feature by scope.inject<Feature>
...
class Dialog : 
SourceDialog<DialogUiEvent>(), 
Consumer<DialogViewModel> {
...
    private val feature by scope.inject<Feature>
...

But i not sure that is the best way of using dialogs.

Can somebody show example of using dialogs with MVICore?

Fragment navigation

Hi.
Is there best way to use navigation?
I using:

   sealed class News {
        object GoToFragment1 : News()
        object GoToFragment2 : News()
    }
   sealed class Effect {
        object Fragment1: Effect()
        object Fragment2: Effect()
    }
   class ActorImpl() : Actor<State, Wish, Effect> {
        override fun invoke(state: State, wish: Wish): Observable<Effect> {
            return when (wish) {
                Wish.Something -> if (some logic) Effect.Fragment1 else Effect.Fragment2
            }
        }
    }
    class NewsPublisherImpl : NewsPublisher<Wish, Effect, State, News> {
        override fun invoke(wish: Wish, effect: Effect, state: State): News? {
            return when (effect) {
                Effect.Fragment1 -> News.GoToFragment1 
                Effect.Fragment2 -> News.GoToFragment2 
            }
        }
    }

But I have problem in Reducer.

    class ReducerImpl : Reducer<State, Effect> {
        override fun invoke(state: State, effect: Effect): State {
            return when (effect) {
                Effect.Fragment1 -> state // not changed
                Effect.Fragment2-> state // not changed
            }
        }
    }

state is not changed.
NewsListener works fine. Fragment changed.
But in first fragment called accept.

Is there way to don't call accept when state is not changed?

Middlewares added after a feature is instantiated do not get hooked into a feature's internal streams

This may be by design but it was not immediately obvious and drove me insane this morning. I was looking for the logging for an Actor in one of my Features but I was not getting any output. Turned out that a refactor recently had the side-effect of the Feature being instantiated before the LoggingMiddleware was added.

Is this a case that could be handled or if not, maybe it is worth calling attention to in the wiki.

Multi - framework/platform support

This proposal allows us to move on from RxJava only implementation.

The general idea is to have a minimalistic "core" as a Kotlin MPP module with our own implementation of the base types: Source<T>, Consumer<T>, Feature and Binder.

The framework level contains a set of extensions to convert between "core" types to framework ones (e.g. from Source<T> to ObservableSource<T> from Rx and vice versa). In current PoC, we need to provide the following implementations:

  • Conversion between Source and Consumer to framework types
  • Feature implementation with framework source type (optional)
  • Binder extensions for framework type (optional)

As we don't do any thread/concurrency management in the "core" implementation, I suspect we need to have some checks (similar to current SameThreadVerifier) in place to prevent race conditions.

Reusing Features in a RecyclerView

We have a variety of complicated cards to use in a RecyclerView and we would like to use a Feature for each card. The cards observe multiple Observable sources, and need to be disposed of properly when recycled/backgrounded/etc.

  1. Should we be calling .dispose() on a Feature that belongs to a card that is being recycled?

  2. Is there a way to re-use a Feature after it has been .disposed()? We would rather not have to allocate new Features in onBindViewHolder() as the object allocations can lead to GC/jank.

  3. How does the Binder that we use play into this? Do we need to call .dispose() on it as well? Or is that unnecessary if we are calling .dispose() on each bound Feature when it's no longer needed?

Thank you!

Capture stacktrace on SameThreadVerifier creation

If SameThreadVerifier is enabled we can capture stack trace where this object was created and then print it on assertion error (or add it as a cause). It should help with debugging such errors.

Allow to make Actions internal

Hi! I'm creating a module that expose a feature to other modules to consume. The only things that I want to expose in my feature are: the Feature, the Wishs and the State (and the News eventually). I want the other classes internal.

I don't want to expose the Actions. Is there a way to set the Actions internal?

Here a really basic example:

class BasicFeature<T>(
  seed: Single<Either<Throwable, T>>,
  @Named("main") main: Scheduler
) : BaseFeature<BasicWish, BasicAction, BasicState<T>, BasicState<T>, Nothing>(
  initialState = BasicState.Loading,
  actor = BasicActor(seed, main),
  reducer = { _, newState -> newState },
  bootstrapper = { Observable.just(BasicAction.Load) },
  wishToAction = {
    when (it) {
      is BasicWish.Refresh -> BasicAction.Load
    }
  }
)

sealed class BasicWish {
  object Refresh : BasicWish()
}

internal sealed class BasicAction { // I can't set this internal because it's exposed by BaseFeature
  object Load : BasicAction()
}

sealed class BasicState<out T> {
  data class Data<T>(val data: T) : BasicState<T>()
  object Loading : BasicState<Nothing>()
  object Error : BasicState<Nothing>()
}

Best practices for using ModelWatcher with sealed class ViewModel

We represent our view model as sealed classes a lot of times and was looking to leverage ModelWatcher but I'm struggling to come up with a good way to handle this. Take for example the following view model:

sealed class TransportControlsViewModel {
    data class Show(
        val startTime: Long,
        val position: Long,
        val duration: Int
    ) : TransportControlsViewModel()
    object Hide : TransportControlsViewModel()
}

My initial approach was to set to watch the entire model but that turned out not to be very helpful because, when shown, there is a new model emitted every second with the only property usually changing is position.

Have you had to handle any similar scenario in your since implementing this?

Consider a mechanism to rebind connections on lifecycle begin event

Encountered during a use case where feature and screen had longer lifecycle than a binder.
Hopefully, an example will illustrate it to some extent.

Let's say there is some feature and some events from UI:

class MyActivity: Activity() {
    val uiEvents = ViewBinder();
    val feature = MyFeature()
}

The feature lives in the scope of Activity and destroyed together with it.

Then consider that the feature should update the UI only between onStart() and onStop() while executing some actions in the background if needed (through Bootstrapper or smth).
To achieve this one can introduce some lifecycle and rebind in onStart():

val lifecycle = Lifecycle.manual()
val binder = Binder.from(lifecycle)
override fun onStart() {
    // Usually done in ViewBindings.setup, but still reflect actual case
    binder.bind(uiEvents to feature using ::uiEventToWish)
    binder.bind(feature to uiEvents using ::stateToViewModel)
}

override fun onStop() {
    lifecycle.end()
}

However, these bindings need to be initialised every onStart() event, which is kinda error prone. The binder interface could consider storing connections it has and reconnect them when lifecycle.begin() is triggered.

Then, the user connect everything only once, and the lifecycle events will be managed by the framework (e.g. ViewBindings abstraction that already exists).

val lifecycle = Lifecycle.manual()
val binder = Binder.from(lifecycle)

init {
    // Usually done in ViewBindings.setup, but still reflect actual case
    binder.bind(uiEvents to feature using ::uiEventToWish)
    binder.bind(feature to uiEvents using ::stateToViewModel)
}
override fun onStart() {
    lifecycle.begin()
}

override fun onStop() {
    lifecycle.end()
}

Lifecycle bound connections can be subscribed several times

The connections using repeatable Android lifecycles (Resume-Pause, Start-Stop) can be subscribed several times, as activity callbacks are triggered after onNewIntent.
Possible solutions:

  • have lifecycle subscribed in binder with distinctUntilChanged
  • apply distinctUntilChanged for Android lifecycles
  • ???

Move docs to wiki

It's very convenient to have the list of contents on the sidebar.

Duplicated connections when using StartStopBinderLifecycle which causes News events (probably other things also) to be fired several times

Hello!

I've faced a problem while trying to use StartStopBinderLifecycle with fragments.
When going back and forth between fragments, binding happens every time and it creates duplicated connections and subscriptions. After that NewsListener's accept method called 2 times. (I'm not sure if reducer or actor are affected)

Снимок экрана 2019-12-26 в 12 07 20

P.S. .distrinctUntilChanged() doesn't help.

Thanks!

Multiple Feature Binding

How can I handle multiple Feature to View bindings? Where ViewModels should be merged into one to render in View? Do you have any examples of this?

mvicore-android artifact no longer published

Is there a reason why the mvicore-android artifact is no longer published as of 1.2.5? Looks like the com.github.dcendents.android-maven plugin was removed from the build script as part of #134.

Access to old state to NewsPublisher and PostProcessor

Current implementation of NewsPublisher and PostProcessor has a signature of (Action, Effect, State) -> T?, which can be somehow limiting, when you want to diff the previous and current state.
The common suggestion to update the signature to include the previous state, e.g. (State, Action, Effect, State) -> T?, which seems to be the best idea for now.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.