mirror of
https://github.com/ProtonMail/protoncore_android.git
synced 2026-06-14 09:54:49 +00:00
Add observe to SharedPreferences util
This commit is contained in:
@@ -25,7 +25,7 @@ plugins {
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
libVersion = Version(0, 2, 1)
|
||||
libVersion = Version(0, 2, 2)
|
||||
|
||||
android()
|
||||
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
* This file is part of Proton Technologies AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.util.android.sharedpreferences
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* @return [Flow] that emits at every change in the receiver [SharedPreferences]
|
||||
*/
|
||||
fun SharedPreferences.observe(): Flow<SharedPreferencesChangeValue> = callbackFlow {
|
||||
|
||||
// Emit initial value
|
||||
send(SharedPreferencesChangeValue(this@observe, null))
|
||||
|
||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||
offer(SharedPreferencesChangeValue(sharedPreferences, key))
|
||||
}
|
||||
registerOnSharedPreferenceChangeListener(listener)
|
||||
awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [Flow] that emits at every change related to the given [key] in the receiver [SharedPreferences]
|
||||
*/
|
||||
inline fun <reified T> SharedPreferences.observe(key: String): Flow<T?> =
|
||||
observe()
|
||||
.map { it.sharedPreferences.get<T>(key) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
/**
|
||||
* The model emitted then from [SharedPreferences.observe] on each change
|
||||
* @param key the key of the changed value
|
||||
* it can be null only at initial emission, before any change happened
|
||||
*/
|
||||
data class SharedPreferencesChangeValue(
|
||||
val sharedPreferences: SharedPreferences,
|
||||
val key: String?
|
||||
)
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
* This file is part of Proton Technologies AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.util.android.sharedpreferences
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/*
|
||||
* Runs with Robolectric as we can't reliably mock listeners in a easy way
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.P])
|
||||
class ObserveTest : CoroutinesTest {
|
||||
|
||||
private val prefs = ApplicationProvider.getApplicationContext<Context>()
|
||||
.getSharedPreferences("test", Context.MODE_PRIVATE)
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
prefs.clearAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observe all emits initial value`() = coroutinesTest {
|
||||
|
||||
// given
|
||||
prefs["string"] = "hello"
|
||||
prefs["int"] = 10
|
||||
|
||||
val expected = mapOf(
|
||||
"string" to "hello",
|
||||
"int" to 10
|
||||
)
|
||||
|
||||
// when
|
||||
val flow = prefs.observe()
|
||||
|
||||
// then
|
||||
assertEquals(expected, flow.first().sharedPreferences.all)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observe all emits every change`() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val flow = prefs.observe()
|
||||
val result = mutableListOf<Pair< String?, Map<String, Any?> >>()
|
||||
|
||||
val expected = listOf(
|
||||
null to emptyMap(),
|
||||
"string" to mapOf(
|
||||
"string" to "hello"
|
||||
),
|
||||
"int" to mapOf(
|
||||
"string" to "hello",
|
||||
"int" to 10,
|
||||
),
|
||||
"boolean" to mapOf(
|
||||
"boolean" to true,
|
||||
"string" to "hello",
|
||||
"int" to 10,
|
||||
)
|
||||
)
|
||||
|
||||
// when
|
||||
val job = launch {
|
||||
flow.map { it.key to it.sharedPreferences.all.toMap() }.take(4).toList(result)
|
||||
}
|
||||
prefs["string"] = "hello"
|
||||
prefs["int"] = 10
|
||||
prefs["boolean"] = true
|
||||
|
||||
// then
|
||||
@Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
|
||||
assertEquals(expected, result)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observe single emits initial value`() = coroutinesTest {
|
||||
|
||||
// given
|
||||
prefs["string"] = "hello"
|
||||
|
||||
// when
|
||||
val flow = prefs.observe<String>(key = "string")
|
||||
|
||||
// then
|
||||
assertEquals("hello", flow.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observe single emits every change`() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val flow = prefs.observe<String>(key = "string")
|
||||
val result = mutableListOf<String?>()
|
||||
|
||||
val expected = listOf(
|
||||
null,
|
||||
"hello",
|
||||
"hi"
|
||||
)
|
||||
|
||||
// when
|
||||
val job = launch {
|
||||
flow.take(3).toList(result)
|
||||
}
|
||||
prefs["string"] = "hello"
|
||||
prefs["string"] = "hi"
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `observe single does not emit for unrelated changes`() = coroutinesTest {
|
||||
|
||||
// given
|
||||
val flow = prefs.observe<String>(key = "string")
|
||||
val result = mutableListOf<String?>()
|
||||
|
||||
val expected = listOf(
|
||||
null,
|
||||
"hello",
|
||||
)
|
||||
|
||||
// when
|
||||
val job = launch {
|
||||
flow.take(2).toList(result)
|
||||
}
|
||||
prefs["other"] = "hi"
|
||||
prefs["string"] = "hello"
|
||||
|
||||
// then
|
||||
assertEquals(expected, result)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user