MVVM pattern and startActivity

Related searches

I recently decided to have a closer look at the new Android Architecture Components that Google released, especially using their ViewModel lifecycle-aware class to a MVVM architecture, and LiveData.

As long as I'm dealing with a single Activity, or a single Fragment, everything is fine.

However, I can't find a nice solution to handle Activity switching. Say, for the sake of a short example, that Activity A has a button to launch Activity B.

Where would the startActivity() be handled?

Following the MVVM pattern, the logic of the clickListener should be in the ViewModel. However, we want to avoid having references to the Activity in there. So passing the context to the ViewModel is not an option.

I narrowed down a couple of options that seem "OK", but was not able to find any proper answer of "here's how to do it.".

Option 1 : Have an enum in the ViewModel with values mapping to possible routing (ACTIVITY_B, ACTIVITY_C). Couple this with a LiveData. The activity would observe this LiveData, and when the ViewModel decides that ACTIVITY_C should be launched, it'd just postValue(ACTIVITY_C). Activity can then call startActivity() normally.

Option 2 : The regular interface pattern. Same principle as option 1, but Activity would implement the interface. I feel a bit more coupling with this though.

Option 3 : Messaging option, such as Otto or similar. ViewModel sends a Broadcast, Activity picks it up and launches what it has to. Only problem with this solution is that, by default, you should put the register/unregister of that Broadcast inside the ViewModel. So doesn't help.

Option 4 : Having a big Routing class, somewhere, as singleton or similar, that could be called to dispatch relevant routing to any activity. Eventually via interface? So every activity (or a BaseActivity) would implement

IRouting { void requestLaunchActivity(ACTIVITY_B); }

This method just worries me a bit when your app starts having a lot of fragments/activities (because the Routing class would become humongous)

So that's it. That's my question. How do you guys handle this? Do you go with an option that I didn't think of? What option do you consider the most relevant and why? What is the recommended Google approach?

PS : Links that didn't get me anywhere 1 - Android ViewModel call Activity methods 2 - How to start an activity from a plain non-activity java class?

NSimon, its great that you start using AAC.

I wrote a issue in the aac's-github before about that.

There are several ways doing that.

One solution would be using a

WeakReference to a NavigationController which holds the Context of the Activity. This is a common used pattern for handling context-bound stuff inside a ViewModel.

I highly decline this for several reasons. First: that usually means that you have to keep a reference to your NavigationController which fixes the context leak, but doesnt solve the architecture at all.

The best way (in my oppinion) is using LiveData which is lifecycle aware and can do all the wanted stuff.

Example:

class YourVm : ViewModel() { 

    val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>()
    fun onClick(item: YourModel) {
        uiEventLiveData.value = item to 3 // can be predefined values
    }
}

After that you can listen inside your view for changes.

class YourFragmentOrActivity { 
     //assign your vm whatever
     override fun onActivityCreated(savedInstanceState: Bundle?) { 
        var context = this
        yourVm.uiEventLiveData.observe(this, Observer {
            when (it?.second) {
                1 -> { context.startActivity( ... ) }
                2 -> { .. } 
            }

        })
    }
}

Take care that ive used a modified MutableLiveData, because else it will always emit the latest result for new Observers which leads to bad behaviour. For example if you change activity and go back it will end in a loop.

class SingleLiveData<T> : MutableLiveData<T>() {

    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }

    companion object {
        private val TAG = "SingleLiveData"
    }
}

Why is that attempt better then using WeakReferences, Interfaces, or any other solution?

Because this event split UI logic with business logic. Its also possible to have multiple observers. It cares about the lifecycle. It doesnt leak anything.

You could also solve it by using RxJava instead of LiveData by using a PublishSubject. (addTo requires RxKotlin)

Take care about not leaking a subscription by releasing it in onStop().

class YourVm : ViewModel() { 
   var subject : PublishSubject<YourItem>  = PublishSubject.create();
}

class YourFragmentOrActivityOrWhatever {
    var composite = CompositeDisposable() 
    onStart() { 
         YourVm.subject 
             .subscribe( { Log.d("...", "Event emitted $it") }, { error("Error occured $it") }) 
               .addTo(compositeDisposable)         
       }   
       onStop() {
         compositeDisposable.clear()
       }
    }

Also take care that a ViewModel is bound to an Activity OR a Fragment. You can't share a ViewModel between multiple Activities since this would break the "Livecycle-Awareness".

If you need that persist your data by using a database like room or share the data using parcels.

MVVM pattern and startActivity – Arvi Fox, https://stackoverflow.com/questions/46727276/mvvm-pattern-and-startactivity. Sending Events From an MVVM View Model with Kotlin� MVVM pattern and startactivité j'ai récemment décidé d'examiner de plus près les nouveaux composants D'Architecture Android que Google a publié, en particulier en utilisant leur classe ViewModel cycle de vie-aware à une architecture MVVM, et LiveData.

You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity

MVVM pattern and startActivity, Say, for the sake of a short example, that Activity A has a button to launch Activity B. Where would the startActivity() be handled? Following the MVVM pattern, the� Model–view–viewmodel (MVVM) is a software architectural pattern that facilitates the separation of the development of the graphical user interface (the view) – be it via a markup language or GUI code – from the development of the business logic or back-end logic (the model) so that the view is not dependent on any specific model platform.

You can extend your ViewModel from AndroidViewModel, which has the application reference, and start the activity using this context.

Best practice for Android MVVM startActivity, Best practice for Android MVVM startActivity. baseactivity android mvvm android mvvm livedata rxjava with mvvm android android mvvm user profile example MVVM is an evolution of 3 layer architecture. I know I do not have a history to prove this but that’s how I have personally evolved and viewed MVVM. So we will start first with a basic 3 layer architecture, understand the problem of 3 layer architecture and see how MVVM solves the problem and then graduate to create a professional MVVM code .

Invoking startActivity/finish from viewModel : androiddev, To be clear, I'm not referring to cases in which the flow can be passed through the activity - for example when a menu item is clicked, I don't directly call the VM and � MVVM is a pattern that is used while dealing with views created primarily using WPF technology. Therefore, it would help a great deal if you have prior exposure to WPF and its bindings. Previous Page Print Page

How to start activity from the ViewModel � Issue #2 � alphamu , your explanation on the view model is great but I'm facing an issue when showing the toast, snack bar or start another activity. For, example when you click on� When the PublishSubject is invoked, the activity is registered to it and thus can call its own startActivity/finish etc'. While this solution works just fine and maintains SOLID principles and MVVM architectures - the VM isn't aware of the activity, doesn't hold a reference to it et'c, It feels a little to cumbersome.

As we are going to implement the Model View ViewModel design pattern and data binding is a key point of this pattern. So let’s enable data binding. So let’s enable data binding. Again go to app level build.gradle file and enable data binding by adding following code inside android block.

Comments
  • Thanks for the very detailed answer. I was also leaning towards the LiveData approach, however didn't think of your LiveData tweaks. Overall though, it all seems very "hacky", and it feels almost bad to do it this way. Anyway, thanks ! (Edit : can only validate the bounty in 20h)
  • No, it's not hacky. Thats how the observer pattern works. You get a click, push it to your ViewModel and if there's a View (this is guaranteed) then it processes the data. Its not even a patch :) Thats how it work. Click ist just a sample, it can be every "data input".
  • @Suman, the android architecture blueprint describes it in very detail. I think you will be able to find SingleLiveData class implemented here in the link. github.com/googlesamples/android-architecture/blob/…
  • This is exactly the solution I've been using since Android Architecture Components were introduced :)
  • Ill update the solution (java+kotlin) when i find the time very soon. Best, .. Emanuel
  • Check this question it may help you. stackoverflow.com/questions/46727276/…
  • True. However you'd have to Mock the context when doing UnitTesting, which adds extra (unnecessary) work. I ended up with the above mentioned approach, which consists of a LiveData field just for navigation.
  • Both ways are fine, just added another option.
  • Yeah, but if you start your activity using that context you need to set the NEW_TASK flag to the intent, and that's just messy. Application context shouldn't be used to start activities.