My current tech obsessions are: Realm, Dagger and Unit Testing. Therefore, I'm always looking for opportunities to improve my code in some way that involves one or more of the above. That being said, I realized that the recommended way of handling migrations in Realm could be improved significantly by means of Dagger 2.
We're going to be refactoring the following Migration class:
With only two version updates, we already have a decent sized method to deal with. What's more if you didn't start out by creating tests for your migrations, once this method gets much longer you probably never will. But all is not lost, Dagger's Multibinding Support is coming to the rescue. Let's take a look!
The first thing we're going to do is create a new Interface, VersionMigration. This will have only the following:
The migrate method will take in a DynamicRealm instance and a long which represents the previous version of the schema that you want to migrate from.
With this available, we can now create two VersionMigration classes that implement our new Interface. Here's the implementation for the Version1Migration class:
On line 12, we implement the migrate method. Notice that we've mostly just pasted in the same code that we had in our original Migration class. The key difference is on line 16, where we use the getObjectSchema method to retrieve the schema instead of grabbing it directly. I'll explain why we did it this way momentarily.
Next we are going to create a new Dagger module, named MigrationsModule. Then by means of the following annotations: @Provides, @IntoMap, and @IntKey I'm defining how I want my VersionMigrations to be created and injected. The combination of these annotations allows all of the VersionMigration Providers to be injected into a Map. The key to the map will be an Integer that corresponds to the previous schema version that the given VersionMigration should be used for.
For example, if my previous schema version is 2 and the current schema version is 3, then I would use the Version2Migration class. There would be no need for me to use any other VersionMigration. However, if my previous schema version is 1 and the current schema version is 3, then I would use both the Version1Migration and Version2Migration classes. This will all come together once we take a look at our updated Migration class.
Our class has now been "Daggerized"! We start out by having a Map of VersionMigration Providers injected into our constructor on line 7. Recall that in our MigrationsModule we used those three annotations: @Provides, @IntoMap, and @IntKey. Based on that, Dagger is clever enough to gather both of our Providers together and store them in a Map that uses the Integer constant we defined as the key.
Moving down to our migrate method. There on line 15 we have a simple for loop that starts with the oldVersion and goes until we get to the newVersion. Keep in mind, that the oldVersion corresponds to the schema version that is active on the user's device. It's the version that we want to migrate from, so that we can be on the newVersion. The main Dagger awesomeness happens on lines 17 - 22. We look in our versionsMigrations Map for the correct Provider using the loop variable value. Then we return an instance of the appropriate VersionMigration and execute it's migrate method. That's it. This means that no matter how many migrations we need in the future we don't have to bother this class anymore.
What's more we also have our migration logic isolated into testable bits. Here's a look at some unit tests for the Version1Migration class using JUnit and Mockito.
Remember I said, I would explain the use of the getObjectSchema method. Well, there on line 17 when I create my migration object, I override the getObjectSchema method to return a mock of the RealmObjectSchema class. I ran into several weird exceptions when I tried to mock the RealmSchema class directly, but this solution worked just fine.
I hope you can use a similar approach to make your code more testable. All of the code snippets can be found in this gist. Thanks for reading!
If you would like to view additional Android content, I encourage you to check out my video tutorials available on Caster.io.