将 SharedPreferences 中的值公开为 Flow

tos*_*gic 11 kotlin android-jetpack-compose kotlin-flow kotlin-stateflow

我正在尝试让显示缩放功能与 JetPack Compose 一起使用。我有一个 ViewModel 将共享首选项值公开为流,但这绝对是不正确的,如下所示:

@HiltViewModel
class MyViewModel @Inject constructor(
    @ApplicationContext private val context: Context
) : ViewModel() {
    private val _densityFactor: MutableStateFlow<Float> = MutableStateFlow(1.0f)
    val densityFactor: StateFlow<Float>
        get() = _densityFactor.asStateFlow()

    private fun getDensityFactorFromSharedPrefs(): Float {
        val sharedPreference = context.getSharedPreferences(
            "MY_PREFS",
            Context.MODE_PRIVATE
        )
        return sharedPreference.getFloat("density", 1.0f)
    }

    // This is what I look at and go, "this is really bad."
    private fun densityFactorFlow(): Flow<Float> = flow {
        while (true) {
            emit(getDensityFactorFromSharedPrefs())
        }
    }

    init {
        viewModelScope.launch(Dispatchers.IO) {
            densityFactorFlow().collectLatest {
                _densityFactor.emit(it)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的可组合项:

@Composable
fun MyPageRoot(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = hiltViewModel()
) {
    val densityFactor by viewModel.densityFactor.collectAsState(initial = 1.0f)

    CompositionLocalProvider(
        LocalDensity provides Density(
            density = LocalDensity.current.density * densityFactor
        )
    ) {
        // Content
    }
}
Run Code Online (Sandbox Code Playgroud)

这里有一个滑块,我想用手指滑动它来设置显示缩放比例(滑块位于 MyPageRoot 的内容之外,并且当用户使用滑块时不会更改屏幕上的大小)。

@Composable
fun ScreenDensitySetting(
    modifier: Modifier = Modifier,
    viewModel: SliderViewModel = hiltViewModel()
) {
    var sliderValue by remember { mutableStateOf(viewModel.getDensityFactorFromSharedPrefs()) }

    Text(
        text = "Zoom"
    )
    Slider(
        value = sliderValue,
        onValueChange = { sliderValue = it },
        onValueChangeFinished = { viewModel.setDisplayDensity(sliderValue) },
        enabled = true,
        valueRange = 0.5f..2.0f,
        steps = 5,
        colors = SliderDefaults.colors(
            thumbColor = MaterialTheme.colors.secondary,
            activeTrackColor = MaterialTheme.colors.secondary
        )
    )
}
Run Code Online (Sandbox Code Playgroud)

滑块可组合项有自己的视图模型

@HiltViewModel
class PersonalizationMenuViewModel @Inject constructor(
    @ApplicationContext private val context: Context
) : ViewModel() {
    fun getDensityFactorFromSharedPrefs(): Float {
        val sharedPreference = context.getSharedPreferences(
            "MY_PREFS",
            Context.MODE_PRIVATE
        )
        return sharedPreference.getFloat("density", 1.0f)
    }

    fun setDisplayDensity(density: Float) {
        viewModelScope.launch {
            val sharedPreference = context.getSharedPreferences(
                "MEAL_ASSEMBLY_PREFS",
                Context.MODE_PRIVATE
            )
            val editor = sharedPreference.edit()
            editor.putFloat("density", density)
            editor.apply()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道我需要将所有共享首选项代码移至一个类中。但是我该如何编写流程,以便在值发生变化时从共享首选项中提取?我觉得我需要某种监听器,但对 Android 开发来说非常陌生。

Ten*_*r04 18

你的评论是对的,这确实很糟糕。:) 您应该创建一个 OnSharedPreferenceChangeListener,以便它对更改做出反应,而不是锁定 CPU 来不断地先发制人地检查它。

用于callbackFlow将听众转换为流程。你可以这样使用它:

fun SharedPreferences.getFloatFlowForKey(keyForFloat: String) = callbackFlow<Float> {
    val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
        if (keyForFloat == key) {
            trySend(getFloat(key, 0f))
        }
    }
    registerOnSharedPreferenceChangeListener(listener)
    if (contains(keyForFloat)) {
        send(getFloat(keyForFloat, 0f)) // if you want to emit an initial pre-existing value
    }
    awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
}.buffer(Channel.UNLIMITED) // so trySend never fails
Run Code Online (Sandbox Code Playgroud)

然后你的 ViewModel 变成:

@HiltViewModel
class MyViewModel @Inject constructor(
    @ApplicationContext private val context: Context
) : ViewModel() {

    private val sharedPreference = context.getSharedPreferences(
        "MY_PREFS",
        Context.MODE_PRIVATE
    )

    val densityFactor: StateFlow<Float> = sharedPreferences
        .getFloatFlowForKey("density")
        .stateIn(viewModelScope, SharingStarted.Eagerly, 1.0f)
}
Run Code Online (Sandbox Code Playgroud)

  • 不知道 SP 有内置侦听器功能。现在这很有意义......至少我的直觉是好的,尽管我的知识很差:) (2认同)