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)
}
}
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 WeakReference
s 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.
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.
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.