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 write 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 prefs but I have it copied from here:

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

Refactoring 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 commit that transaction. Instead, we can compose a higher-order function that would accept a function to be performed by the editor. I am not going to discuss how you can use lambda expressions on Android. You can either enable Jack or use retrolambda.

Here, the Performer is a functional interface and edit method takes an anonymous function as an argument:

Refactoring 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(). Now code 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 the higher-order function is unnecessary. We can not refactor those things because of limitations of the language.

Converting to Kotlin

Refactoring 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.

Refactoring 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 typecast 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. As a result, even if we don’t pass the default value, it will take from the right-hand side of the elvis operator.

Refactoring 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 just 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:

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!