How to finish activity from ViewModel using Androi

2019-04-20 08:52发布

问题:

I try to figure out how to in best way finish an Activity from ViewModel. I found the one way to do this using LiveData object and emitting "signal".

I have a doubt that this solution has a overhead. So is it right solution or I should use more accurate?

So go to example: let's suppose that in an app is an activity MainActivity and view model like below:

 class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        model.shouldCloseLiveData.observe(this, Observer { finish() })

    }
 }

and as a companion to the MainActivity is a MainViewModel like below:

class MainViewModel(app: Application) : AndroidViewModel(app) {

  val shouldCloseLiveData = MutableLiveData<Void>()

  fun someAction(){
    shouldCloseLiveData.postValue(null)
  }

}

回答1:

I share your feelings that this solution does not look tidy for two reasons. First using a MutableLiveData object to signal an event is a workaround. No data is changed. Second exposing LiveData to the outside of the view model violates the principle of encapsulation in general.

I am still surprised about this ugly concept of android. They should provide an option to observe the view model instead of it's internal LiveData objects.

I experimented with WeakReferences to implement the observer pattern. This was unstable. In an unpredictable manner the referent of the WeakReference was lost (null), in wich cases it was not possible to call finish(). This was surprising as I don't think the activity is garbage collected while running.

So this is an partly answer by exclusion. The observer pattern implemented as WeakReference seems to be no alternative to your suggestion.

A wonder if it is legitimate implement the observer pattern by hard references, if I remove the references during onStop(), or onDestroy(). I asked this question here.



回答2:

I agree that there doesn't seem to be a good solution for this, and your suggestion does work pretty well. But I would suggest the following.

Since you are using Kotlin you could pass a function from your activity to the viewmodel like this:

ViewModel:

class MainViewModel(app: Application) : AndroidViewModel(app) {

    fun someAction(block: () -> Unit) {
        // do stuff
        block()
    }
}

Activity: here the button (and clicklistener) is used as an example but this can be anywhere in the activity's code.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        myButton.setOnClickListener {
            model.someAction() {
                finish()
            }
        }
    }
}

the block function will essentially act as a callback.



回答3:

I had a similar problem: I had two activities (A and B) with its view models connected to an object (a table in a database): from an observable of a live data I had to navigate to another activity B (from A to B). The problem was, after invoking the new activity B, the observable in B change the value in the observed object.. activity A was still live, its live data call again the navigation code to B.. in an infinite loop.

After some research, I realized running a finish method does not mean the activity is really destroyed.

The solution is, in the observable code, remove from the live data the observables linked to specific activity.

liveData.removeObservers(activity);

I show it in the following snippet code. It's written in Java, but I think you have no problem to read it. With this, I solved my problem.

public class LockActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...    

        mViewModel = ViewModelProviders.of(this).get(ConfigurationViewModel.class);

        LiveData<Configurazione> liveData = mViewModel.getConfiguration();
        liveData.observe(this, config-> {
            // this code will be executed even another activity is in front of
            // screen
            boolean validToken = (config.getToken()!=null);

            if (!tokenValido) {
                intent = LoginActivity.createIntent(this);
            } else {
                intent = MainActivity.createIntent(this);
            }

            // this line remove the observable, so even activity will be destroied with calm, it is not a problem, the code is no more executed
            liveData.removeObservers(this);
        });
    }

    ...
}

I think you can easily adapt to this situation to your code. I hope it helps.