Skip to content

Instantly share code, notes, and snippets.

@yankeppey
Last active September 14, 2022 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yankeppey/2cd64ae831ace29fe57d3bd3e190ef92 to your computer and use it in GitHub Desktop.
Save yankeppey/2cd64ae831ace29fe57d3bd3e190ef92 to your computer and use it in GitHub Desktop.
A simple Lifecycle-aware component that allows to observe SharedPreferences via Kotlin StateFlow
import android.content.SharedPreferences
import android.util.Log
import eu.buney.coroutines.ApplicationCoroutineScope
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.plus
import javax.inject.Inject
import javax.inject.Singleton
/**
* A simple subscription-aware component that allows to observe a [StateFlow] of [SharedPreferences] in any app's component
* and react accordingly. A simple subscription to [preferences] is enough, no other setup is necessary.
*
* [SharedPreferences.registerOnSharedPreferenceChangeListener]/[SharedPreferences.unregisterOnSharedPreferenceChangeListener] are handled
* internally. When somebody subscribes to [preferences], than the listener is registered, and otherwise when there are no subscribers,
* all the listeners are unregistered.
*
* @author Andrei Buneyeu
*/
@Singleton
class SharedPreferencesFlow @Inject constructor(
@ApplicationCoroutineScope applicationScope: CoroutineScope,
sharedPreferences: SharedPreferences,
) {
companion object {
private val TAG = SharedPreferencesFlow::class.java.simpleName
}
/**
* Flow of all key-value pairs coming from [SharedPreferences], in a form of [Map].
* Even though the Map is guaranteed to be different on each update, the specific key-values may be the same. So when observing for
* specific key-value pairs, ensure you're using [distinctUntilChanged]:
* ```
* preferences
* .map { it[SOME_KEY] } // observing SOME_KEY only
* .distinctUntilChanged() // ensuring that only different values are emitted.
* ```
*/
val preferences: Flow<Map<String, Any?>> = MutableStateFlow<Map<String, Any?>>(mapOf()).apply {
val onSharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, _ ->
value = sharedPreferences.all
}
subscriptionCount
.map { count -> count > 0 } // map count into active/inactive flag
.distinctUntilChanged() // only react to true <-> false changes
.onEach { isActive ->
if (isActive) {
Log.d(TAG, "Fetching the preferences so that the consumer gets the last update...")
emit(sharedPreferences.all)
Log.d(TAG, "Subscription is active, going to register the listener")
sharedPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
} else {
Log.d(TAG, "Subscription is not active, going to unregister the listener")
sharedPreferences.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
}
}
.launchIn(applicationScope + CoroutineName(TAG))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment