Prefekt / SharedPreferences

Prefekt – Introduction

I am delighted to announce Prefekt – an Android SharedPreference library for Kotlin. The API is extremely clean and easy to consume, and you don’t need to worry about performance because that is all managed by the library itself. In this post we’ll look at how to use Prefekt.

Prefekt is designed to make using SharedPreferences effortless. You will not longer need to even look at the SharedPreference API, let alone have to worry about keeping your loading of SharedPreferences off the UI thread as this is all managed by the library itself. In addition to this you get a callback whenever the value changes even if it was changed from outside of Prefekt. Performance is optimised because of an in-memory cache which keeps the number of times a given value is read from and written to SharedPreferences (which are relatively expensive operations) to an absolute minimum.

To use Prefekt you will first need to add the dependency to your build.gradle:

.
.
.
dependencies {
    .
    .
    .
    implementation 'com.stylingandroid.prefekt:prefekt:1.0.0'
    .
    .
    .
}

You can instantiate a Prefekt object from within either FragmentActivity or a support library Fragment. Internally Prefekt uses the Android Architecture components and so a valid LifecycleOwner is needed. Although Prefekt uses Architecture Components there is absolutely no requirement for you to also use them, but it means that it is only compatible with support library Fragment implementation (which should not be a problem as the platform Fragment implementation is being deprecated). To create a Prefekt instance:

const val KEY_STRING = "STRING"

private const val DEFAULT_STRING = "Hello World!"

class MainActivity : AppCompatActivity() {

    private var stringValue = prefekt(KEY_STRING, DEFAULT_STRING) {
        println("New Value: $it")
    }
    .
    .
    .
}

KEY_STRING is the key under which the value will be stored in SharedfPreferences.
DEFAULT_STRING is the default value and dictates the type. Supported types are: Int, Long, Float, Boolean, and String. Prefekt is strongly typed so you will get a compiler warning if you attempt to use un unsupported type, or if you later try to assign it a value with a differing type.

The lambda will be called once the value has been loaded from SharedPreferences. This may be immediately if the value is already in memory. It will also be called whenever the SharedPreference value changes irrespective of whether it was changed by Prefekt itself or by something else.

If you wish to change the value you call the setValue() function:

stringValue.setValue("New String")

This is a lightweight function and it is perfectly acceptable to call it from the UI thread.

While using the lambda to receive the value is the recommended way of doing things, it is also possible to manually retrieve the value. However, because this can potentially require a disk read, it is not possible to do this with a simple, synchronous call. Instead there are two options. First you can make an asynchronous call with a lambda which will be invoked once the value has been loaded:

stringValue.getValue {
    println("Current value: $it")
}

This is different from how the lambda on the prefekt declaration works in that it will only retrieve a single value, and will not be called whenever the value changes.

Another option is to call a suspend function which needs to be called from a coroutine:

launch(CommonPool) {
    println("Value: ${stringValue.getValue()}")
}

If you attempt to call this function from the UI thread then Prefekt will throw an Exception because this could be a potentially lengthy blocking operation. If in doubt use one of the other methods which cannot block the UI thread.

Finally it is possible to manually subscribe and unsubscribe for updates whenever the value changes:

object StringSubscriber : Subscriber<String> {
    override fun onChanged(newValue: String) {
        println("New Value: $newValue")
    }
}

stringValue.subscribe(StringSubscriber)
.
.
.
stringValue.unsubscribe(StringSubscriber)

That covers the entire API of Prefekt. The basic form of using a declaration lambda to receive value updates, and calling setValue() will be sufficient for the vast majority of use-cases.

While the API itself is pretty simple, that simplicity requires some interesting stuff internally and in the next article in this series we’ll explore some of the internals.

For those interesting in my choice of name, I was looking for something that started with “Pref” as a short form of “Preference” and chanced upon “Prefect”. Being a Kotlin library I decided to change the “C” to a “K” (because it’s what all the cool kids are doing) and the name “Prefekt” was born. The similarity to the name of one of a character in Douglas Adams’ Hitch Hiker’s Guide To The Galaxy series is purely coincidental, but somehow makes the name work even better, for me. With regard to the logo, it is a stylised form of a Prefect’s badge.

Prefekt is released under Apache 2.0 license and the source is available here.

© 2018, Mark Allison. All rights reserved.

Copyright © 2018 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

9 Comments

  1. Hi! This looks very promising. In my app I’ve got two sets of SharedPreferences, one which is included for automatic backups and one which isn’t. Is there a way to get this behaviour with prefekt?

    Cheers!

    1. Currently not. It only supports default SharedPreferences. Is there anything preventing you from using a single SharedPreference?

        1. OK. That’s a use-case I hadn’t considered.

          Leave it with me, I’ll see what I can come up with.

  2. “Performance is optimised because of an in-memory cache which keeps the number of times a given value is read from and written to SharedPreferences (which are relatively expensive operations) to an absolute minimum.”

    Interesting. Doesn’t SharedPreferences already maintain an in-memory cache?

  3. Could you create more examples? I tried to create this:

    private var firstLaunch = prefekt(“FIRST_LAUNCH”, false) {
    println(“New Value: $it”)
    }

    When I try to check with if (firstLaunch.getValue()) it gives me an error. Can you create an example? It’s quite different from the original SharedPreferences.

    1. There’s a working sample app in the repo https://github.com/StylingAndroid/Prefekt/tree/master/app.

      Where are you calling if (firstLaunch.getValue())? If you do it earlier than onResume() or onStart() then it will most likely fail because the Context is not valid at this point. Why do you even need to call firstLaunch.getValue()? In your code snippet, the lambda provided to the prefekt constructor (i.e. println(“New Value: $it”)) will be invoked once the value has been loaded.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.