How can the delegation pattern scale your workflow with SharedPreferences?
Hey Android & Kotlin lovers, today, I want to share with you an interesting topic of how you can improve your workflow with SharedPrefences by using the delegation pattern. Nevertheless, for understanding how it works and be a good software developer, we need to deep-dive a little bit into theory before start writing some code.
Delegated pattern
Delegation pattern is using everywhere in the development world, and honestly, I convinced that you’re already using it in your projects. Someone even doesn’t know that he is using it. Delegation pattern in software engineering is a way to make the composition as powerful for reuse as an inheritance.
Delegation can be understanding in two ways:
- Explicit - this type can easily be implemented in any object-oriented language.
- Implicit - which requires language-level support for the delegation pattern.
Another one step back for Java developers, but since we are Kotlin developers, and Kotlin supports it natively, that’s mean that we can use Implicit, which is requiring zero boilerplate code.
I will show you the basic delegation pattern on the same example as presented on Kotlin Docs:
interface Base {
fun print()
}class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}// will print: 10.
Mhm…thank you for this lecture, but how it can improve my workflow with SharedPreferences?
Hey, buddy wait a little bit that was theory before the Big Bang :)
Delegated Properties
And here we go, this is the main thing which can improve our workflow with the SharedPreferences and reasonably not only with them 🧐.
I’m pretty sure that you’re already using in your projects at least the Standard Delegated properties, which are predefined by Kotlin standard library, such as Lazy, NotNull, Observable, Map
and others. More about them and how you can use them, you will find in Kotlin Docs.
As you know that in Kotlin we can easily make gettter&settter directly on the property creation level:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
But in some circumstances, it can be produced boilerplate code, which we can prevent by using delegated properties.
I think that it will be better to stop bla-bla-bla, and go over on the concrete example 🤐
Finally, most of the android applications, are using SharedPreferences for saving some data locally without needing to create the local DB. Currently, you can find a lot of different possibilities of how to create the manager for SharedPreferences, nevertheless here I want to show you the manager which is boost by delegated properties.
I will show you the result before the final implementation, and I think that it will be the right stimulus for you to read until the end.🤓
Imagine that we need to save our user identification number during Login either Signup and afterward you will be using it in other parts of your project.
How it will be without mine pattern:
//SharedPrefsManager.ktcompanion object {
const val USER_ID = "user_id"
}var userId: Long
get() = prefs.getLong(USER_ID, 0)
set(value) {
prefs.edit().apply { putLong(USER_ID, value) }.apply()
}
This solution is really too simple but you already see here this future boilerplate code which we are creating by get&set for each variable. And also the prefs.edit().putLong/String/Int/...
function will produce another boilerplate. And with the time it will be looks like that:
How can we prevent it?
Step 1: Create SharedPreferencesProvider.kt
this interface will contain all functions variables for SharedPreferences.
Step 2: Create PreferencesDelegate<T>
it will be our delegate class with getter and setter functions and others. Please read the comments above each method carefully. These comments contain a description of how concrete function works and also explained each parameter. For now, we are not implementing the functionality, but only predefined the functions.
Step 3: Create SomeManager.kt
and SomeMangerImp.kt
:
On that project, I’m using EncryptedSharedPreferences, but in case if you are not using it please remove row 20
and replace EncryptedSharedPreferences.create
with context.getSharedPreferences(getPrefName("userCreds"), Context.MODE_PRIVATE)
.
Okay, 😅 what have we already done?
We have prepared our first SomeManagerImpl.kt
with the interface. Also, we created our delegate, but we still don’t have the functionality above the delegate, let’s do it!
First of all, we will implement the getValue
function:
Let’s read function row by row, for better understanding:
- In the start of the function, we will check if is the reference to the class is right if a class is correct we will go on the next step otherwise
throw RuntimeException
— it has to appear if the developer forgot to implementSharedPreferencesProvider
inSomeManagerImp.kt
. - The next step should be made in a
synchronized
block, which will protect from concurrent execution with different values. First of all, we will check if this property already initialized or not. If it not initialized yet, we will set our variable withthisRef.preferences.all[key] as? T
otherwise, the function will return cached value =variable as? T
.
That’s all with getValue
function, it’s time to add setValue
function:
Also, let’s read this function row by row: start of the function is pretty the same to getValue
, because of that, we will straightly move into a synchronized
block:
- As a first step, we will check if this value is not the same as the already saved variable.
- At the final step, we will cache our variable and save value asynchronously into
SharedPreferences
. Please give attention to the logic during the writing of the new value, if thisvalue
isnull
that’s mean that current value should be removed from preferences, otherwise, it should be written with the custom functionputAny(key, value)
.
How does the putAny
function look like?
Before we will implement this function, you should create a specific Generic<T : Any>
class:
Inside of the companion object, this class contains an overloading operator invoke()
for removing unnecessary duplicate constructor implementation in the place of call, and returns prepared generic’s, argument class. Also, this class contains the checkType(t: Any)
function, which is checking if the value is an instance of the desired class.
Finally, we can create our quite simple SharedPreferences.Editor
extension function, for putting value
with related type into preferences.
Eventually, we are ready to test our implementation.
It depends on you what you are using in your projects Koin either Dagger. You should inject the interface of your manager into the Repository/ViewModel, and using it there:
That’s all, you are ready to run and build your project 😉
Let’s summarize, with mine pattern you will achieve:
- NO boilerplate code in the project ✅
- You will get a reusable and scalable pattern for SharedPreferences and honestly not only for them ✅
- You got some gotchas, such as putAny function, which can be used somewhere else, for instance with bundles (free advice😜) ✅
- We strengthened and improved our knowledge and skills! ✅
Thank you for your attention! | Do not forget to support me with claps 👏🏼
And definitely in the case of some questions, do not hesitate to contact me 🤓.
PP