Dagger + Android ViewModels

A different story

Miroslav Kacera
AndroidPub

--

There’s many articles dealing with ViewModels on Android especially after the Architecture Components library was released some time ago. And also many of them are trying to help you to set up the Dagger to create them for you. So why yet another article you may ask. 😃

A bit of history

Well, in the company I’m working in (Sygic — check it out 😉), we have been using MVVM for some time and adopted Architecture Components pretty early. And we were quite happy with the way things are working within this libs. But there has always been one downside.

Our apps are not really small ones and to be honest never fit into “tutorial examples” bucket. Creating ViewModels like this ViewModelProviders.of(this).get(NotDoingMuchViewModel::class.java)was never our case. What we needed were the factories… like lot of them and ugly ones as well. 🐙

In that time we have already been using Dagger so most of the stuff could have been injected directly but there was always at least one trouble-making parameter we were not able to squeeze out of the Dagger. Imagine code like this:

In this approach you let the Dagger take care of the parameters for the factory and the login, which Dagger have no idea about, you set with the setter. But in my opinion, this is not something you want. You are exposing public setter so the login variable can be set more than one time or you forget to set it at all. Giving you nothing but trouble. If we are doing DI we do it right. Right? 🙌

We really wanted to find a way to set the login with the constructor so there are no loose ends with setters we need you to call exactly one time and in the right time, otherwise everything blows into your face.🔥 But at that time we weren’t quite there yet. In that time our ViewModels were constructed like this:

What the hell, right? Well, imagine what was at the beginning of that file. Let me show you:

Ok, not every ViewModel was like this but you know… you have the idea. Most of the injected stuff was not used at all in that file just sent to the factory. We were not able to get rid of this because not everything could be injected so we were not able to let Dagger handle the factory creation for us.

Inject uninjectable

There has to be a better way, hasn’t it? Back then I knew there is an approach not many of us are really using and that is assisted injection. There are two main libs known to me doing this — AutoFactory from Google and AssistedInject from Square about which Jake Wharton had a great talk at droidcon London. I have experimented with both of them as they both have a slightly different approach to the factory generation. Unfortunately, none were able to achieve exactly what I’ve wanted. But still they were a priceless source of inspiration the moment I decided to write my own annotation processor for this.

I don’t want to go into details about the annotation processing and code generation as that’s not the point of this article and I could write a standalone article about annotation processors alone. You can check the processor here if you are interested. The point is to pick up @AutoFactory and @Assisted annotations and generate appropriate code we can use to inject ViewModels with assisted parameters.

Getting the hands dirty

So with this little background, I can show you how it looks now.

This is an example of a ViewModel which needs a bunch of parameters Dagger can provide and one it can’t. So it is not only annotated with @Autofactory annotation but also has one @Assisted argument. What’s important we are not declaring the ViewModelProvider.Factory for this ViewModel anywhere. It’s generated by the processor mentioned earlier. It’s just annotations on ViewModel’s side.

Then when we need to get the actual instance of the ViewModel we can now call one-liner like this:

Looking much better isn’t it? The viewModelOf method is actually our helper method to make the creation little less verbose but hiding the real syntax, so for the completeness, this is the method:

You can see the injected ViewModelFactory which takes assisted params and with the help of ViewModelProviders from Architecture Components it returns the requested ViewModel.

But how?

(Thanks to annotation processor) every generated factory implements our ViewModelCreatorFactory interface which is just a simple interface providing create method for the beforementioned ViewModelFactory.

Now last thing missing is how Dagger actually provides ViewModelFactory and how the factory knows how to construct actual ViewModels. This is basically a “standard stuff” described in many other articles dealing with ViewModel injections as well.

We need to bind ViewModelFactory to Android’s ViewModelProvider.Factory and all our ViewModels into Map for Dagger.

That’s about it. Well maybe except one treacherous thing biting me to the ass along the way.

The Java - Kotlin uncertainty

As you can see some of the code is in Kotlin some in Java. This is not just me unable to decide which language to use. There is an ugly reason for that, and if you try to rewrite it to let say to pure Kotlin, you will fail spectacularly as I did.

Annotation processing for Kotlin is done by kapt while in java files it’s done, well by java. The problem is these two guys know nothing about each other, and both are doing their stuff anytime they want resulting in NoClassDefFoundError errors. Leaving you wonder how for Christ’s sake it can’t find the file laying there on your file system just fine.

Well, it turned out the file was generated by java but just too late for kapt to pick it up. The solution was to rewrite Dagger components and modules to java, so it is processed at the same time as our custom annotations. 😎

We have a project on GitHub (which you can check out as well), using this approach to create great mapping library over our powerful Sygic SDK. The project’s not production ready yet (in the time of writing), but everything mentioned here is there, doing heavy lifting for us. Here’s the link https://github.com/Sygic/sygic-maps-kit-android.

Thanks for reading and please leave a comment about what you think about all of this. 👏

--

--