You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose多关卡Quiz应用:Preference Datastore存档关卡失效求助

基于Jetpack Compose的Quiz应用关卡持久化解决方案(Preference Datastore版)

一、正确的Preference Datastore实现

确保Datastore类遵循单例原则,读写逻辑规范:

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import android.content.Context
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

// 全局唯一的Datastore实例,通过Context扩展属性创建
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "quiz_prefs")

class QuizDataStore(private val context: Context) {
    // 定义存储当前关卡的Key
    private val CURRENT_LEVEL = intPreferencesKey("current_level")

    // 存储关卡:suspend函数,必须在协程中调用
    suspend fun saveCurrentLevel(level: Int) {
        context.dataStore.edit { prefs ->
            prefs[CURRENT_LEVEL] = level
        }
    }

    // 获取关卡:返回Flow,Compose可通过collectAsState监听
    fun getCurrentLevel(): Flow<Int> {
        return context.dataStore.data
            .map { prefs ->
                // 未存储过则默认返回第一关
                prefs[CURRENT_LEVEL] ?: 1
            }
    }
}

二、Compose中结合ViewModel使用

推荐用ViewModel封装数据逻辑,避免在Composable中直接操作Datastore:

1. 创建ViewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class QuizViewModel(private val dataStore: QuizDataStore) : ViewModel() {
    // 暴露给Compose的关卡Flow
    val currentLevel = dataStore.getCurrentLevel()

    // 对外提供保存关卡的方法
    fun saveLevel(level: Int) {
        viewModelScope.launch {
            dataStore.saveCurrentLevel(level)
        }
    }
}

// ViewModel工厂类,用于注入QuizDataStore
class QuizViewModelFactory(private val dataStore: QuizDataStore) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(QuizViewModel::class.java)) {
            return QuizViewModel(dataStore) as T
        }
        throw IllegalArgumentException("未知的ViewModel类")
    }
}

2. 在Compose界面中使用

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun QuizHomeScreen() {
    val context = LocalContext.current
    // 创建ViewModel实例
    val quizViewModel: QuizViewModel = viewModel(
        factory = QuizViewModelFactory(QuizDataStore(context))
    )
    // 收集Flow为State,自动触发UI重组
    val currentLevel = quizViewModel.currentLevel.collectAsState(initial = 1)

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "当前关卡:${currentLevel.value}")

        // 模拟关卡切换,完成后保存下一关
        Button(onClick = {
            quizViewModel.saveLevel(currentLevel.value + 1)
        }) {
            Text(text = "进入下一关")
        }
    }
}

三、常见错误排查

  • Datastore实例重复创建:必须用preferencesDataStore扩展属性创建,不要手动new DataStore实例,否则会抛出异常。
  • 协程调用错误:saveCurrentLevel是suspend函数,不能直接在Compose的点击回调中调用,必须通过viewModelScope或lifecycleScope启动协程。
  • 未正确监听Flow:在Compose中必须用collectAsState收集Flow,否则无法获取最新存储值,也不会触发UI更新。
  • 默认值缺失:获取关卡时一定要设置默认值(如1),否则首次启动会得到null,导致UI报错。
  • Context错误:传递给QuizDataStore的Context必须是有效上下文(如LocalContext.current),不能使用临时Context。

内容的提问来源于stack exchange,提问作者Ayman Ait

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:51:18