Refactoring utility classes with Kotlin : Shared Preferences

If you were developing Android apps in Java until now and haven’t started using Kotlin in production yet, you may have some sort of utility classes for many things like File I/O, Connectivity, Cache, Shared Preferences etc. Today I am going to talk only about Shared Prefs and how we can use Kotlin to refactor our old Java utility classes. This post is intended for anyone including beginners who want to play around Kotlin.

Maybe you have a good solution for shared preferences but I have it copied from here:

It looks ugly. To edit preferences, I had to write same things repetitively. Also, to access and manipulate; there are separate setters and getters for each type. In below steps, we will first try to refactor our Java class and then convert it to Kotlin.


Step 1: Higher-order functions

The first step we can do is to identify a repetitive task that has been performing. In our case, every time we put something in preferences, we open an editor, put value and either calling commit() or apply() over it. Instead, we can compose a higher-order function that would accept a function to be performed by the editor.

Here, the Performer is a functional interface and edit method takes an anonymous function as an argument. After doing that, we can remove some unnecessary code:

Step 2: Using generics

Even after using lambdas, we have methods for each data type, which is not so good. We can use generics to have conditional checks for the type at run time. Either using instanceOf or using parametrizable class(class<?>), we can avoid writing a separate method for each type:

Finally, we can manipulate preferences with just two methods :getValue() and setValue(). Also, our class looks less ugly and concise than previous.

However, this if…else still looks bad. Many things like checking for nulls etc. are omitted. There are three to four parameters to pass in every single method. That interface for higher order function is unnecessary. We can not refactor those things because of limitations of the language.


Converting to Kotlin

Step 3: Using extension functions

In the Java version of our class, we were using all static methods. We can now convert them to powerful extension functions. So, in our case, we will first convert our edit() function as an extension for SharedPreferences:

As we can pass function directly as an argument in Kotlin, we can get rid of that Performer interface. Here I used inline keyword to avoid the cost of higher-order functions. You can read more about it here.

Step 4: Using when expressions and reified types

Using when expressions, we can eliminate all those if…else hell and make our setter look like below:

Well, but what about getValue()? In the Java version, we were using parameterizable class (class<T>). In Kotlin, we can do the same thing but with more elegantly using reified type parameters. To put simply, it will allow us to use type passed as a parameter. You can read more about it here.

Look at the following part:

getInt(key, defaultValue as? Int ?: -1)

Here, as? is a safe cast operator and so it returns null if type cast is failed. ?: is an elvis operator. It simply says: ‘take this value if the original value is null’. In our function parameter, defaultValue is the optional parameter and initialized to null. As a result, even if we don’t pass the default value, it will take from the right-hand side of the elvis operator.

Step 5: operator functions

So, our implementation now looks like below. Notice that I have added methods for accessing default as well as custom preferences and used object declaration to make it work as a singleton.

Wouldn’t it be great if we rename our setter and getter to just set() and get()? Kotlin supports operator overloading and so we can convert function to look like an operator.

Our final implementation:

Now we can use square brackets for setting and getting values. Just two lines of code for manipulating shared preferences.

It looks neat and elegant. Thanks to Kotlin!


Edit : Please refer to following gist for detailed example of usage:

https://gist.github.com/krupalshah/d091be195999a7ddf52c3bb0f04f2bf3


This post was originally published at my blog.