android / android-ktx

A set of Kotlin extensions for Android app development.
https://android.github.io/android-ktx/core-ktx/
7.47k stars 564 forks source link

SharedPreferences delegates #284

Open jmfayard opened 6 years ago

jmfayard commented 6 years ago

Request: abstract with a properties delegate how to get and update the data stored in the SharedPreferences

It's not merely about "reducing boilerplate" but also more importantly about decoupling the app logic from the android framework.

Example

interface CurrentUser {
    var userId: Int
    var fullName: String
    var backendToken: String
}

class SharedPrefsCurrentUser(
   val preferences: SharedPreferences
) : CurrentUser {
    override var userId by preferences.bindPreference(default = -1)
    override var fullName by preferences.bindPreference(default = "")
    override var backendToken by preferences.bindPreference(default = "")
}

data class TestCurrentUser(
    override var userId: String = -1,
    override var fullName: String = "",
    override var backendToken: String = ""
) : CurrentUser

There are of course already libraries providing that (I'm using curently kotlin-jetpack )

But it's such a useful pattern for a core function that the more the better. The alternative is people doing stupid mocking dance, having a mocked context that returned a mocked shared preferences, configured to return "blah" when getString("foo") is called. Compare that with using a data class!

tobiasschuerg commented 6 years ago

I like this approach: https://hackernoon.com/kotlin-delegates-in-android-development-part-1-50346cf4aed7

which results in:

class MyClass(prefs: SharedPreferences) {
    var count by prefs.int()
}

still having teh possibility to specify the key and default manually

Ioane5 commented 6 years ago

Yes, unfortunately it does not handle nullability issues.

I think the best option is following.

class SharedPrefsCurrentUser(
   val preferences: SharedPreferences
) : CurrentUser {
    override var userId : Int by preferences.int().defaultBy(0)
    override var fullName : String by preferences.string().defaultBy(0)
    override var backendToken : String? by preferences.string()
}

I think default binder should create delegate for nullable. Only when you provide default, should it create not-null.