【问题标题】:Why updating PreferenceManager doesn't trigger recomposition in Jetpack Compose?为什么更新 PreferenceManager 不会触发 Jetpack Compose 中的重组?
【发布时间】:2021-09-21 05:51:32
【问题描述】:

我正在通过 article 学习 Compose。

无状态可组合是不包含任何状态的可组合。一个简单的实现无状态的方法就是使用状态提升,所以我把代码B换成了代码A,太好了!

文章告诉我:

通过从 HelloContent 中提升状态,可以更轻松地推断可组合性,在不同情况下重用它并进行测试。 HelloContent 与其状态的存储方式是分离的。解耦意味着如果你修改或替换 HelloScreen,你不必改变 HelloContent 的实现方式。

所以我写了Code C,它将name的值存储在一个SharedPreferences中,我认为Code C和Code A一样,但实际上,我不能用Code C输入任何字母,什么代码 C 错了吗?

代码 A

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }
    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

代码 B

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       var name by remember { mutableStateOf("") }
       if (name.isNotEmpty()) {
           Text(
               text = "Hello, $name!",
               modifier = Modifier.padding(bottom = 8.dp),
               style = MaterialTheme.typography.h5
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text("Name") }
       )
   }
}

代码 C

@Composable
fun HelloScreen() {
    var name: String by PreferenceTool( LocalContext.current ,"zipCode", "World")
    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

class PreferenceTool<T>(
    private val context: Context,
    private val name: String,
    private val default: T
) {

    private val prefs: SharedPreferences by lazy {
        PreferenceManager.getDefaultSharedPreferences(context)
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreference(name, default)

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putPreference(name, value)
    }

    @Suppress("UNCHECKED_CAST")
    private fun findPreference(name: String, default: T): T = with(prefs) {
        val res: Any = when (default) {
            is Long -> getLong(name, default)
            is String -> getString(name, default) ?: default
            is Int -> getInt(name, default)
            is Boolean -> getBoolean(name, default)
            is Float -> getFloat(name, default)
            else -> throw IllegalArgumentException("This type can be saved into Preferences")
        }
        res as T
    }

    @SuppressLint("CommitPrefEdits")
    private fun putPreference(name: String, value: T) = with(prefs.edit()) {
        when (value) {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            is Float -> putFloat(name, value)
            else -> throw IllegalArgumentException("This type can't be saved into Preferences")
        }.apply()
    }
}

【问题讨论】:

  • 您好,任何答案都解决了您的问题吗?如果是这样,请使用投票柜台下的复选标记接受它。否则,如果您对他们有任何问题,请告诉我们。

标签: android android-jetpack-compose


【解决方案1】:

代码 C 中缺少一个关键部分,即MutableState

在 Compose 中,修改内容 UI 的唯一方法是更改​​其相应的状态。


您的代码没有备份PreferenceTool 的可变状态对象。因此,通过属性委托使用 setValue 只会修改 SharedPreference(通过调用 putPreference(name, value)),但不会将更改传播到 UI。

operator fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreference(name, default)
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
    putPreference(name, value)
}

为了纠正这种行为,请在PreferenceTool 中添加一个MutableState 对象。

这样,Compose 会检测到更新并相应地更新 UI。

class PreferenceTool<T>(
    private val context: Context,
    private val name: String,
    private val default: T
) {
    private val prefs: SharedPreferences by lazy {
        PreferenceManager.getDefaultSharedPreferences(context)
    }

    private val state = mutableStateOf(findPreference(name, default))

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = state.value

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        state.value = value
        putPreference(name, value)
    }

    @Suppress("UNCHECKED_CAST")
    private fun findPreference(name: String, default: T): T = { ... }

    @SuppressLint("CommitPrefEdits")
    private fun putPreference(name: String, value: T) = { ... }
}

【讨论】:

    【解决方案2】:

    Om 关于您的代码不起作用的原因是完全正确的,他的回答会起作用。

    要了解为什么在撰写中需要MutableState,我建议您从documentation 开始,包括解释基本原则的this youtube video

    PreferenceManager 已弃用,现在您可以改用DataStore

    with compose in 可以这样使用:

    @Composable
    fun <T> rememberPreference(
        key: Preferences.Key<T>,
        defaultValue: T,
    ): MutableState<T> {
        val coroutineScope = rememberCoroutineScope()
        val context = LocalContext.current
        val state = remember {
            context.dataStore.data
                .map {
                    it[key] ?: defaultValue
                }
        }.collectAsState(initial = defaultValue)
    
        return remember {
            object : MutableState<T> {
                override var value: T
                    get() = state.value
                    set(value) {
                        coroutineScope.launch {
                            context.dataStore.edit {
                                it[key] = value
                            }
                        }
                    }
    
                override fun component1() = value
                override fun component2(): (T) -> Unit = { value = it }
            }
        }
    }
    
    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "preferences")
    

    用法:

    var name by rememberPreference(stringPreferencesKey("zipCode"), "World")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-11
      • 2021-12-23
      • 2021-01-05
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 2021-06-01
      • 1970-01-01
      相关资源
      最近更新 更多