Berlin — January 2017 ©

Reducing ViewModel provision boilerplate in Google’s GithubBrowserSample

Mina Kamel
ProAndroidDev
3 min readJan 19, 2018

--

In a previous post, I’ve discussed how to inject parameters to view model constructor using ViewModelProvider.Factory and dagger. But is there a better way to avoid having to write a custom implementation to ViewModelProvider.Factory for every view model?

Yes, there is

In the aforementioned Github Browser Sample, you can find a generic implementation of ViewModelProvider.Factory that saves us implementing one with every view model.

The way it’s used afterwards in SearchFragment, RepoFragment and UserFragment looks like this:

In addition, we have to add a bind method for each view model we add to the app in the ViewModelModule.

Good step, but is there more?

Well, at this point. We would still need to write (No, don’t fool yourself: copy/paste) the factory injection and viewModel provision within each fragment/activity a long with adding a method in the ViewModelModule as mentioned.

With the sheer amount of these fragments and activities that our apps usually have, we might want to think of a smarter way of handling the view model. Here we go:

Let’s start by making Fragments (and Activities) a little bit generic by adding a BaseFragment class that takes <T extends ViewModel> as generic parameter. In addition, we’ll inject a generic viewModel to it.

We should change SearchFragment, RepoFragment and UserFragment in the sample to extend from BaseFragment and remove the injected factory and view model provisioning from each.

ViewModelUtil?

In the sample code, a ViewModelUtil class is used for creating view model factory for testing purposes. We’ll make use of it!

Let’s move it to production code (or make another copy of it). And then use it in our BaseFragment to create the factory. The BaseFragment should now look like this:

If you want to go fancy, you can add @Inject to the ViewModelUtil constructor and make createFor non-static then inject it here. #TeamDagger

You may have noticed that the view model is not set to the returned value of ViewModelProviders.of but that’s fine. After the viewModel is injected, calling of() will ensure the instance has been created.

Now in each child fragment, viewModel will be ready to be directly used without further coding.

Tip: Ideally, factory creation and view model provisioning should be done in fragment’s onViewCreated specially if the activity hosts multiple fragments.

No need for GithubViewModelFactory or ViewModelModule!

The ViewModelModule was used to provide a map of view models through dagger that is used by the GithubViewModelFactory. With this new setup, no need for either. All you need is to just use the viewModel instance directly in your fragment/activity!

Fixing tests

If you are like me, not a big fan of instrumentation tests and would rather focus on unit tests, you’re pretty much done here. If you’re not, you may want to continue reading.

The changes broke the SearchFragmentTest, RepoFragmentTest and UserFragmentTest for two reasons:

  1. viewModelFactory is no longer a member of the child fragment but rather of the BaseFragment. To fix that, simply remove this line (we’ll find another way to bridge the view model):
searchFragment.viewModelFactory = ViewModelUtil.createFor(viewModel);

2. The mocked view model in each test has nothing to do with the generic. view model we added to BaseFragment. Therefore we get this NPE when we run tests:

java.lang.NullPointerException: Attempt to invoke virtual method ‘java.lang.Class java.lang.Object.getClass()’ on a null object reference

To fix we can add this method to the BaseFragment:

This allows us to hook the mocked view model to the fragment in the init method of the tests:

That’s it, tests fixed!

Bonus: Kotlinify!

If you’re using Kotlin, the ViewModelUtil can be replaced by a nice extension method that looks like this:

And the onActivityCreated method would look like this:

With this structure developers can easily hop into working on the logic of their views and view models with almost zero boilerplate code to prepare for that. Time to just code!

--

--