LiveData
. Make sure you read that post before this one, as this is a continuation.
If you can recall from the previous post, the diagram below was given as an indication of how we will be structuring our “Date Countdown” app.
In this post, we will create the EventListViewModel
and AddEventViewModel
shown in the diagram above. You can find all the source code for this post here. Before we dive into creating the ViewModels
, we should first take a look at what a ViewModel
is.
What is a ViewModel?
A ViewModel
is not a new concept, nor is it neccesarily an Android concept. The name ViewModel
came from the MVVM pattern designed by Microsoft in around 2005. With the new Architecture Components, one of the new classes is the ViewModel
class.
ViewModels
are responsible for preparing data for the View
. They expose data to any view that is listening for changes. The ViewModel
class in Android has some specific facts you should keep in mind when using it:
- A
ViewModel
can retain its state across Activity configuration changes. The data it holds is immediately available to the next Activity instance without needing to save data inonSaveInstanceState()
and restore it manually. - A
ViewModel
outlives the specific Activity or Fragment instances. - A
ViewModel
allows easy sharing of data across Fragments (meaning you no longer need to coordinate actions via an activity). - A
ViewModel
will stay in memory until theLifecycle
it’s scoped to goes away permanently – in the case of an Activity, once it finishes; in the case of a Fragment, once it’s detached. - Since a
ViewModel
outlives the Activity or Fragment instances, it shouldn’t reference any Views directly inside of it or hold reference to a context. This can cause memory leaks. - If the
ViewModel
needs an Application context (e.g. to find a system service), it can extend theAndroidViewModel
class and have a constructor that receives the Application in the constructor.
Creating ViewModels for the Date Countdown App
EventListViewModel
The EventListViewModel
class will be used for the list of events that we view when the date countdown app is opened up for the very first time.
- Create a class called
EventListViewModel.
Make sure it extendsViewModel
from the Architecture components class. In this class, we are going to migrate the code that was previously placed in theEventListFragment
into theViewModel
. - Add the
LiveData
variable to theEventListViewModel
. The EventRepository variable will be injected using Dagger. TheEventListViewModel
class should look like this now:public class EventListViewModel extends ViewModel implements CountdownComponent.Injectable { private LiveData<List<Event>> events = new MutableLiveData<>(); @Inject EventRepository eventRepository; @Override public void inject(CountdownComponent countdownComponent) { countdownComponent.inject(this); events = eventRepository.getEvents(); } public LiveData<List<Event>> getEvents() { return events; } }
- Now in the
EventListFragment
, we will want to replace the event loading code we wrote in the previous post by rather getting the list of events through theViewModel
.public class EventListFragment extends Fragment { private EventListViewModel eventListViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { //.. inflate view etc eventListViewModel = ViewModelProviders.of(this, new CountdownFactory(countdownApplication).get(EventListViewModel.class); eventListViewModel.getEvents().observe(this, events -> { adapter.setItems(events); }); return v; } }
The line that contains the
ViewModelProvider.of(..)
will either create a newEventListViewModel
class if one doesn’t exist, or it will fetch the one that exists for the scope that is defined (in this case it is theEventListFragment
). This is where the magic happens. When the device is rotated and the fragment gets recreated, theViewModel
that will be returned here will be the one that was previously used. This allows us to retain the state of the screen without having to manually save information to theBundle
and restore it ourselves.The
Event
data is now obtained from theEventListViewModel
. As mentioned previously, by passingthis
as the first parameter, theLiveData
observable will be managed automatically for you. This means the fragment will take care of disposing the observables when the fragment is no longer used. The observable callback won’t be called if the fragment is not started or resumed. - Create a Dagger
CountdownComponent
, we will use this to inject the dependencies into theViewModel
.@Singleton @Component(modules = {CountdownModule.class}) public interface CountdownComponent { void inject(EventListViewModel eventListViewModel); void inject(AddEventViewModel addEventViewModel); interface Injectable { void inject(CountdownComponent countdownComponent); } }
- Create a custom
ViewModelProvider.NewInstanceFactory
that will be used to instantiate theViewModel
. When theViewModelProvider
needs to create a new instance of aViewModel
it will use thecreate
method defined in the factory below.public class CountdownFactory extends ViewModelProvider.NewInstanceFactory { private CountdownApplication application; public CountdownFactory(CountdownApplication application) { this.application = application; } @Override public <T extends ViewModel> T create(Class<T> modelClass) { T t = super.create(modelClass); if (t instanceof CountdownComponent.Injectable) { ((CountdownComponent.Injectable) t).inject(application.getCountDownComponent()); } return t; } }
- To delete an
Event
, we can add a method to theViewModel.
This will delegate to theEventRepository
to delete the event. In this example, we are using RxJava to delegate to a background thread.public class EventListViewModel extends ViewModel implements CountdownComponent.Injectable { //.. public void deleteEvent(Event event) { eventRepository.deleteEvent(event) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { } @Override public void onComplete() { Timber.d("onComplete - deleted event"); } @Override public void onError(Throwable e) { Timber.e("OnError - deleted event: ", e); } }); } }
We will now use this delete method in the Fragment where we delegate to the ViewModel:
View.OnClickListener deleteClickListener = v -> { Event event = (Event) v.getTag(); eventListViewModel.deleteEvent(event); };
For the AddEventViewModel
implementation, take a look at the full source code example here. We now have a DateCountdown app that loads events from the database using Room
and the ability to add new events. We’ve used ViewModels
to ensure that the data is retained on rotation and to separate our code out from our fragment.
In Summary
The new Android Architecture Components address common scenarios that we haven’t been able to handle easily before. Handling screen rotation is now really easy by using a ViewModel
and the ViewModelProvider
.
Be sure to checkout the full code sample available here to see everything in action and the relevant unit tests. Let me know what your thoughts are on twitter @riggaroo.
References
- ViewModel Documentation
- Android Architecture Components Documentation
- Date Countdown App – Full Code Sample
- Dagger 2 Testing Sample – Chiu-ki Chan
Thanks to Erik Hellman and Yigit Boyar for proof-reading this post.