Categories
android architecture

Android Architecture Components – Looking at ViewModels – Part 2

The Android Architecture components were recently announced at Google I/O 2017. There are a few different components that are a part of these libraries. These components can be used in isolation but work really well when used together. In the previous blog post, we looked at using Room and 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 in onSaveInstanceState() 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 the Lifecycle 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 the AndroidViewModel 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.

  1. Create a class called EventListViewModel. Make sure it extends ViewModel from the Architecture components class. In this class, we are going to migrate the code that was previously placed in the EventListFragment into the ViewModel.
  2. Add the LiveData variable to the EventListViewModel. The EventRepository variable will be injected using Dagger. The EventListViewModel 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;
        }
    }
  3. 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 the ViewModel.
    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 new EventListViewModel 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 the EventListFragment). This is where the magic happens. When the device is rotated and the fragment gets recreated, the ViewModel 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 the Bundle and restore it ourselves.

    The Event data is now obtained from the EventListViewModel. As mentioned previously, by passing this as the first parameter, the LiveData 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.

  4. Create a Dagger CountdownComponent, we will use this to inject the dependencies into the ViewModel.
    @Singleton
    @Component(modules = {CountdownModule.class})
    public interface CountdownComponent {
    
        void inject(EventListViewModel eventListViewModel);
    
        void inject(AddEventViewModel addEventViewModel);
    
        interface Injectable {
            void inject(CountdownComponent countdownComponent);
        }
    }
  5. Create a custom ViewModelProvider.NewInstanceFactory that will be used to instantiate the ViewModel. When the ViewModelProvider needs to create a new instance of a ViewModel it will use the create 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;
        }
    }
  6. To delete an Event, we can add a method to the ViewModel. This will delegate to the EventRepository 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

Thanks to Erik Hellman and Yigit Boyar for proof-reading this post.