SharedPreferences with PrefeRx

Most Android developers use SharedPreferences. It’s a great way of storing something simple across app screens or even sessions. As this simple tool may be perfect for many app developers, it may not be that handy and powerful to use when the app becomes more complex. Therefore, at Vinted, our SharedPreferences evolved into something we really liked and deemed worth sharing with the whole community - PrefeRx - a reactive SharedPreferences library for Kotlin. It allows not only to manage primitives, enums and other serializable objects with ease, but adds all the RxJava goodness. Let’s go through some examples of what this library can actually do for you.

The snippet below is common usage of SharedPreferences in most Android apps.

val shareTooltipSeen = sharedPreferences.getBoolean(Preferences.SHARE_TOOLTIP_SEEN, false)
if(!shareTooltipSeen) {
  showShareTooltip()
}

fun showShareTooltip() {
  sharedPreferences
        .edit()
        .putBoolean(Preferences.SHARE_TOOLTIP_SEEN, true)
        .apply()

  ...
}

Now lets say you need to do that 10 more times. In different places. For the same property. That could become really verbose and unmaintainable really fast. For this reason we created preference wrappers for primitive preferences: BooleanPreference, IntPreference and others. With the handy Kotlin extension function all you need to do now is to pass the property key and default value. Define it once in your Dagger module

@Singleton
@Provides
@Named(Preferences.SHARE_TOOLTIP_SEEN)
fun provideBooleanPreference(sharedPreferences : SharedPrefereces) : BooleanPreference {
  return sharedPreferences.booleanPreference(Preferences.SHARE_TOOLTIP_SEEN, false)
}

and inject anywhere

@field[Inject Named(Preferences.SHARE_TOOLTIP_SEEN)]
lateinit var shareTooltipSeen : BooleanPreference

if(!shareTooltipSeen.get()) {
  showShareTooltip()
}

fun showShareTooltip() {
  shareTooltipSeen.set(true)

  ...
}

Furthermore, such wrappers allowed us to add a few useful features that standard SharedPrefereces does not support: RxJava integration, Enum and object serialization with default values if preference was not yet initialized or value was deleted.

RxJava integration is extremely convenient to listen for changes throughout the app by combining initial preference value with observable to get further updates. Just don’t forget to dispose!

val disposable = Observable.just(unreadMessageCountPreference.get())
      .concatWith(unreadMessageCountPreferenc.onChangeObservable)
      .subscribe { unreadCount ->
          <Update UI>
      }

Quick and easy Enum or any object serialization

notificationFrequency.set(Frequency.ALL)

currentUserPreference.set(getCurrentUser());

for more usage examples see here.

But injecting each property separately could become noisy fast. That is why we introduced the AppPreferences interface, where we declare all properties and then inject it in BaseFragment.

interface AppPreferences {
  val sessionCounter: IntPreference
  val photoTipsSeen: BooleanPreference
  val userSession: ObjectPreference<SessionData>
}

class AppPreferencesImpl(val sharedPreferences: SharedPrefereces) : AppPreferences {
  override val sessionCounter by lazy {
      sharedPreferences.intPreference(SESSION_COUNTER, 0)
  }
}

And that’s it. No time consuming annotation processing, no dependency on activity or fragment - how SharedPrefereces is supposed to be. Hope you’ll enjoy it as much as we do!