Coder Social home page Coder Social logo

spotify / mobius Goto Github PK

View Code? Open in Web Editor NEW
1.2K 48.0 95.0 1.53 MB

A functional reactive framework for managing state evolution and side-effects.

Home Page: https://spotify.github.io/mobius/

License: Apache License 2.0

Java 93.59% Shell 0.05% Kotlin 6.36%
android java functional-reactive-programming mobius state-management

mobius's People

Contributors

akirataguchi115 avatar aleksandrn-spotify avatar alexbeggs avatar amilcar-andrade avatar anawara avatar bootstraponline avatar btoo avatar cortinico avatar fkorotkov avatar jeppes avatar juanmrivero avatar larryng avatar lukaciko avatar milchopenchev avatar nathan3d avatar perploug avatar pettermahlen avatar ragunathjawahar avatar renatanunes-spotify avatar sedovmik avatar spabeggs avatar spkrka avatar togi avatar vestrel00 avatar viktorgardart avatar zvonicek 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mobius's Issues

Race condition results in RejectedExecutionException crash with default event runner

There appears to be a race condition between MobiusLoop#dispatchEvent and MobiusLoop#dispose where eventDispatcher can accept a new event after it's been disposed because dispatchEvent() is not synchronized with dispose().

Sample app to reproduce the issue: https://github.com/larryng/MobiusBug

I think a possible fix may be to simply set disposed = true before disposing everything in dispose() instead of after.

cc @anawara

Unit test example

Please, provide an example of what would be the right way of unit testing Mobius.
I've seen a few Hamcrest matchers in your presentation but, for example, I stopped using Hamcrest in favor of Atrium (fluent assertions). Probably, it would be beneficial to provide a generic way of testing Mobius, not bound to any particular framework. Here is an example of a simple test, that tries to assert the initial model in a stream.

import com.spotify.mobius.First
import com.spotify.mobius.Mobius
import com.spotify.mobius.MobiusLoop
import com.spotify.mobius.Next
import com.spotify.mobius.runners.WorkRunners
import com.spotify.mobius.rx2.RxConnectables
import com.spotify.mobius.rx2.RxMobius
import io.reactivex.Observable
import org.junit.Test

class MobiusUnitTest {

	private val loop: MobiusLoop.Factory<Model, Event, Effect> = RxMobius.loop(::update, ::effectHandler)
		.init { First.first(InitModel, setOf(InitEffect)) }
		.eventRunner { WorkRunners.immediate() }
		.effectRunner { WorkRunners.immediate() }

	private var controller: MobiusLoop.Controller<Model, Event> = Mobius.controller(loop, InitModel)

	private fun update(model: Model, event: Event): Next<Model, Effect> = when (event) {
		is InitEvent -> Next.next(InitModel)
	}

	private fun effectHandler(effects: Observable<Effect>): Observable<Event> = effects.flatMap { effect ->
		when (effect) {
			is InitEffect -> Observable.just(InitEvent)
		}
	}

	private fun bind(modelToEvent: (Observable<Model>) -> Observable<Event>) {
		controller.connect(RxConnectables.fromTransformer(modelToEvent))
		controller.start()
	}

	@Test
	fun should_receive_init_model() {
		bind { models: Observable<Model> ->
			val observer = models.test()

			// assert received model?? No model is received...

			Observable.empty<Event>()
		}
	}
}

sealed class Model
object InitModel : Model()

sealed class Event
object InitEvent : Event()

sealed class Effect
object InitEffect : Effect()

Would you be so kind to point out what's missing? Seems like effectHandler and update methods are being executed, but I'm still not getting the model in my receiver.

How to handle errors in Effects?

Hi!
First of all, thank you for a great library!

One topic that I didn't find from the Wiki is how to handle errors from Effects properly.
For example, if I use RxMobius, do I need to use .onErrorReturn operator from rx, and put a throwable into the Event, instead of resulting data?
is GetSavedUserEffect -> prefs.getUserSavedCredentials() .map { (login, pass) -> UserCredentialsLoadedEvent(login, pass) } .onErrorReturn { return@onErrorReturn UserCredentialsLoadedEvent("", "", err = it) }

MobiusLoop can't receive onActivityResult

The current suggested way to setup Mobius is to call start() in onStart and stop() in onStop. This lifecycle doesn't allow the system to receive onActivityResult calls since it takes place before onStart calls start()

Confused about Android configuration changes with requests in flight

Hi. Let me preface this question by saying I'm still learning this library as well as RxJava.

My question is: say you've got an effect that performs some fake work on a separate thread, e.g.

fun doFakeWork(work: Observable<DoWorkEffect>): Observable<Event> {
    return work
        .delay(5, TimeUnit.SECONDS)
        .map { DoneLoading }
}

and that's hooked up neatly into an effects handler. And say this effect was a consequence of an event that puts the model into a loading state. If the user rotates their phone and destroys the hosting activity during this loading state, the model's state can be saved and restored via the savedInstanceState bundle -- great. But I'm noticing I have nothing to restore and continue the observable stream from where it left off, meaning the fake work being done in that above effect doesn't go beyond the delay operator, and thus the DoneLoading event won't fire to clear the model of its loading state.

How should I be better handling this scenario? I thought to put the effects handler and loop factory into an AAC ViewModel, but the observable stream still ceases upon rotation. I'm not entirely sure why, but I'm guessing it's because the controller still needs to be in an activity/fragment (to bind to the view events, e.g. clicks) and the stream is only subscribed to in that class.

Clarification on my learnings on mobius loop

  1. Is mobius-core and mobius-test compatible with KMM ? - I can see that both these modules are completely platform agnostic, assuming this is right theoretically i believe it is possible to use these in KMM modules CMIIW

iOS Port?

Great work and thank you for releasing this library. Is there an iOS counterpart for mobius in the works?

How to do granular updates to view ?

Great framework but I see this example in wiki

        private Connection<MyModel> connectViews(Consumer<MyEvent> eventConsumer) {
              // send events to the consumer when the button is pressed
                 button.setOnClickListener(view ->
                              eventConsumer.accept(MyEvent.buttonPressed()));

           return new Connection<MyModel>() {
               public void accept(MyModel model) {
                   // this will be called whenever there is a new model
                   textView.setText(model.getValue());
           }

             public void dispose() {
                 // don't forget to remove listeners when the UI is disconnected
        button.setOnClickListener(null);
         }
       };
      }

But in my case i have complex view and every time accept get called i have to rebind and render the view again which i want to avoid

How to setup controller in ViewModel properly?

First of all, thank you for open sourcing Mobius (and new Swift version also!), it's a great experience to introduce so many pure functions into own code.

I wanted to ask something related to FAQ mentioned in Wiki - Is Mobius an alternative to MV*?. Assuming that we will use ViewModel from AAC as a VM layer, I was wondering how proper configuration of controller would look like in this layer. I found that it would be a good practice to separate the Mobius from the view elements completely and transfer it to the VM layer. It would have many benefits: view can observe only view model parts, not the whole model, events from UI can be transformed to business logic events or even effect handlers would be separated from the view layer. So I made a simple ViewModel:

class MyViewModel : ViewModel() {
    
    private lateinit var controller: MobiusLoop.Controller<MyModel, MyEvent>
    
    fun onCreate() {
        controller = // initialize controller here
        controller.connect(RxConnectables.fromTransformer { /* handle model here */ })
    }
    
    fun onStart() {
        controller.start()
    }
    
    fun onStop() {
        controller.stop()
    }
    
    fun onDestroy() {
        controller.disconnect()
    }
}

As you can see ViewModel needs to be aware of view lifecycle in order to properly connect/disconnect or start/stop the loop (method names could be different, but I named in this way to reflect the lifecycle methods in this example). It looks a little bit like a boiler plate to put these loop management in every ViewModel. I'm wondering, is there any drawback to extend the loop management to ViewModel lifecycle methods (init and onCleared) as it survives configuration changes? Or maybe it's not a good idea to setup controller inside ViewModel at all? I realize that Mobius gives a lot of flexibility and it's not a scope of the library to force any architecture constraints, but any opinions/help would be very appreciated in this topic.

Gradle sync issue in Android Studio 3.3.2

When I try to sync the downloaded Mobius project, my sync fails with the following error message.

ERROR: No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

Installed version of Android Studio

Android Studio 3.3.2
Build #AI-182.5107.16.33.5314842, built on February 16, 2019
JRE: 1.8.0_152-release-1248-b01 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.13.6

I was able to fix this problem by changing the android build tools Gradle plugin version from 3.0.1 to 3.1.0.

Can send a PR if required.

Proguard rules?

When building the app using Proguard I get the following errors. Any hints on what to add in my proguard file?

com.spotify.mobius.First: can't find referenced class com.google.auto.value.AutoValue |  
-- | --
com.spotify.mobius.Next: can't find referenced class com.google.auto.value.AutoValue |  
com.spotify.mobius.extras.Program: can't find referenced class com.google.auto.value.AutoValue |  
com.spotify.mobius.extras.patterns.InnerUpdate: can't find referenced class com.google.auto.value.AutoValue |  
org.slf4j.LoggerFactory: can't find referenced class org.slf4j.impl.StaticLoggerBinder |  
org.slf4j.MDC: can't find referenced class org.slf4j.impl.StaticMDCBinder |  
org.slf4j.MarkerFactory: can't find referenced class org.slf4j.impl.StaticMarkerBinder |  
there were 17 unresolved references to classes or interfaces. |  
Exception while processing task java.io.IOException: Please correct the above warnings first. |  

Android Architecture Lifecycle support?

Correct me if I'm wrong, but judging by the wiki, one has to override Activity/Fragment lifecycle methods in order to start/stop/disconnect mobius controller. This boilerplate code could be avoided with a Lifecycle support. So far I ended up with a simple kotlin extension function:

class MobiusLifecycleObserver<MODEL, EVENT>(
	lifecycle: Lifecycle,
	private val controller: MobiusLoop.Controller<MODEL, EVENT>
) : LifecycleObserver {

	init {
		lifecycle.addObserver(this)
	}

	@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
	fun start() = controller.start()

	@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
	fun stop() = controller.stop()

	@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
	fun disconnect() = controller.disconnect()
}

fun <MODEL, EVENT>MobiusLoop.Controller<MODEL, EVENT>.bind(lifecycle: Lifecycle) {
	MobiusLifecycleObserver(lifecycle, this)
}

that could be used like this:

	controller.connect(...)
	controller.bind(lifecycle)

It works like a charm but I'm wondering if a built-in framework support would be beneficial.

Modelling dilemmas

While using Mobius in more complex features I come upon scenarios where I am not sure how to optimally define my model. For instance:

  1. I keep a boolean flag in my model in order to know what side effect to trigger (Effect1 vs Effect2) and this boolean flag is updated based on a particular event. However, whenever I get the event I need to update the flag (and thus the model) and this triggers a re-rendering of the view, although it's not needed. Can I avoid this?

  2. I need to update only parts of the view that really changed because some views are costly to re-render (markers on a map). What's the best approach here? If I create a boolean flag indicating what has changed, I will have then to update the flag back to false everywhere I am modifying the model as a result of events coming. This doesn't seem right especially if I have several boolean flags for various things. Alternatively, I thought of triggering a side effect which clears the flag immediately after setting it to true. Thoughts?

To sum up, I need some best practices on how to selectively render only parts of the view, how to deal with one-time boolean flags and how to update some model fields without re-rendering of the view.

Infinite Scrolling with Mobius

What would be the recommended approach to tackle RecylerView infinite scrolling with Mobius? I'm wondering if you guys have any experience/tips to share on the subject.

100% Java?

Thoughts about adopting Kotlin for mobius? I understand Kotlin can be used with mobius. It'd be nice if mobius was written in Kotlin.

Clarification on Mobius Controller

    @pettermahlen We are mostly using mobius loops with viewmodel across our app. 

What is the use case for a mobius controller?

Can Mobius Controller be thought of an equivalent to VM with loop?

Could you give me actual example use cases for Using Mobius controller over VM with loop?

Originally posted by @rajdheepak in #157 (comment)

Proguard Rules missing

Hi!

Thanks for this wonderful library. Can you guys help with proguard rules for the dependencies in this library?

Long monitor contention warning

I have this warning in my logs and I was wondering if I should be concerned. Unfortunately I could not reproduce it. What would trigger such a warning and how can I mitigate this?

Long monitor contention with owner main (27632) at void java.lang.Object.wait!(long, int)(Object.java:-2) waiters=0 in void com.spotify.mobius.MobiusLoop$3.accept(java.lang.Object) for 109ms

mobius-android and jetpack compose (type-erasure and neighboring loops)

I'm succesfully using Mobius in an Android Kotlin project with MobiusLoopViewModel and Fragments.
For a new project, I am trying to get Mobius running with Kotlin, Jetpack Compose and MobiusLoopViewModel.
The loop is placed within the Viewmodel. Instead of using a Fragment as a host for the ViewModel, a Composable is used.
I created two loops in two Composables, let's call them LoopOne and LoopTwo.
LoopOne is inititialized first, followed by LoopTwo.

Model updates are to be received in the Composable like this:

val modelState: LoopTwoModel by viewModel.models.observeAsState(initialModel)

On the first model update, a class cast exception is thrown in the Composable at that position:
"LoopOneModel cannot be cast to LoopTwoModel"

It seems that the combination of Compose and Mobius leads to some strange type mix-up.
When I move LoopTwo to an Activity it all works as expected, but in a Composable it does not.

I added mobius-android as a local library for better insights.
In the MobiusLoopViewModel class, when observing the loop (Line 100) the wrong LoopOne type is received instead of LoopTwo.

Line 100:

loop.observe(this::onModelChanged);

This wrong type is then posted as LiveData:

Line 230:

private void onModelChanged(M model) {
	modelData.postValue(model);
}

This is probably caused by Compose and Java interoperabilty issues.
But I'd still like to share my results and ask for suggestions here.

Mobius with Android lifcycle

Hello All.. I have Presenter with two lifcycle OnAttachToView and onDetachFromView which will called for Fragment Lifecycle OnStart and OnStop. The question is "Do i really need the on Pause and On Resume Events for starting and stopping the loop?"

Migrate wiki to GitHub Pages + MkDocs ❤️

Hi! I've been using this awesome library for over two years now for work (and sometimes for personal use). One of my former colleagues even went through all of the trouble and red-tape of open sourcing Flowbius 🤯

We've been using the wiki as our main point of reference when working with Mobius and for when we onboard folks into the team. It's great. I have no complaints. The wiki is very well organized and does the job just fine. However, I think we can improve on it 😁

Why migrate the wiki to GitHub Pages + MkDocs?

Why "fix" something that is not broken? This is one of the all-time most frequently asked questions in software development (IMO). As I mentioned, the wiki is fine. I am not saying the wiki "needs to be fixed". So then why?

MkDocs, in particular the Material for MkDocs...

I will not talk about the pros and cons of GitHub wiki vs GitHub Pages. It's up to you to do that research if you care. It's mostly just preference and opinions, IMO anyways 🤭

You get this work for free

I think that you are all aware of MkDocs and have probably considered or are considering using it. Maybe ya'll just don't have the time to do it or just can't be bothered to fix something that ain't broke. Or maybe you even prefer the wiki and are consciously making the decision to use it instead of GitHub Pages + MkDocs.

Either way, I've already done the work for you 🤗. It's been a pleasure to be able to have the opportunity to do this work for one of my all-time favorite FOSS projects. I had fun doing it. As a bonus, I learned even more about Mobius in the process. Regardless of whether you merge my PR or not, thanks for everything 🍻

If you do decide to migrate, you can just merge my PR (or just use it as reference). More info including demo videos and next steps are in the PR.

#154

Here is what it looks like in my fork; https://vestrel00.github.io/mobius/

Expose model observable to render view

Thanks for this awesome library! We are now heavily using Mobius in all our apps. Currently, we render views like this,

RxConnectables.fromTransformer(
     { upstreamModelObs: Observable<M> ->
        val modelDisposable = upstreamModelObs.distinctUntilChanged().subscribe {
            renderView(it)
        }
        this.eventsObservable()
                .doOnDispose { modelDisposable.dispose() }
    }
)

Is there any better way to subscribe to upstream model observable to render views with Rx? I'm not sure if subscribing to observable inside transformer is the right way to consume models.

In mobius-android-sample, I can see that views are rendered this way,

screen shot 2018-10-31 at 6 36 21 pm

What is the proper way to render views when using Rx library?

Consumer is not called when effect is dispatched in Init

I've encountered some issues with dispatching effects from Init function.

Let's say that Init looks like this:

override fun init(model: MyModel): First<MyModel, MyEffect> {
    return first(model,  effects(SomeEffect))
}

and we add Consumer like this:

RxMobius.subtypeEffectHandler<MyEffect, MyEvent>()
    .addConsumer(SomeEffect::class.java, { doSomething() }, mainThread())
    .build()

Now the problem is that doSomething() is never called. Interestingly when I use io() scheduler everything works fine. Can someone give a clue what's the reason?

Crash in MutableLiveQueue

There is crash in MutableLiveQueue, please look at the stack trace below

java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.lifecycle.Observer.onChanged(java.lang.Object)' on a null object reference
at com.spotify.mobius.android.MutableLiveQueue.lambda$post$0(MutableLiveQueue.java:124)
at com.spotify.mobius.android.-$$Lambda$MutableLiveQueue$vOf0H-vBAqo2vnbWBOWfiBCGWLc.run
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:198)
at android.app.ActivityThread.main(ActivityThread.java:6732)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

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.