Android Preferences DataStore随机启动崩溃(NoSuchElementException)求助
问题描述
我的Android应用使用Preferences DataStore存储简单数据(未使用Proto Datastore),多次清除用户数据后,应用会随机出现启动崩溃,即使未操作DataStore仓库也可能触发。已尝试重建项目、从Git重新克隆仓库、移除MainActivity和HomeScreen中大部分仓库调用,但问题仍未解决。
相关代码
MainActivity代码
... val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") class MainActivity : ComponentActivity() { companion object { var isForeground = false } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) isForeground = true qrCodeConfiguration() notificationsConfiguration() setContent { val navController = rememberNavController() val mainviewModel: ComprinhasViewModel = viewModel() val settingsViewModel: SettingsViewModel = viewModel() val receiptsViewModel: ReceiptsViewModel = viewModel() ComprinhasTheme { NavHost(navController = navController, startDestination = "home") { navigation(startDestination = "welcome/username", route = "welcome") { composable("welcome/username") { UsernameScreen { username, newList -> navController.navigate( "welcome/setList/$username/$newList" ) } } composable( route = "welcome/setList/{username}/{newList}", arguments = listOf( navArgument("username") { type = NavType.StringType }, navArgument("newList") { type = NavType.BoolType } ), enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start) }, popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End) } ) { val username = it.arguments?.getString("username") ?: "" val newList = it.arguments?.getBoolean("newList") ?: false val uiFlow = mainviewModel.uiState SetListScreen(uiFlow = uiFlow, newList = newList) {listName, listPassword -> if (newList) { if ( mainviewModel.createList(username, listName, listPassword) ) { navController.popBackStack("home", inclusive = false) } } else { settingsViewModel.updateUserPrefs(username, listName, listPassword) navController.popBackStack("home", inclusive = false) } } } } composable("home") { HomeScreen( viewModel = mainviewModel, toWelcomeScreen = { navController.navigate("welcome") }, toSettingsScreen = { navController.navigate("settings") }, toReceiptsScreen = { navController.navigate("receipts") receiptsViewModel.getReceiptsList() }, showDialog = { navController.navigate("addItem") } ) } composable("settings") { SettingsScreen( appPreferences = settingsViewModel.appPreferences, updateUserPrefs = settingsViewModel::updateUserPrefs, onNavigateBack = navController::popBackStack ) } composable("receipts") { ReceiptsList( receiptsFlow = receiptsViewModel.receiptsList, onQrCodeScan = receiptsViewModel::scanQrCode, uiFlow = receiptsViewModel.uiState, onNavigateBack = { navController.popBackStack() } ) } dialog("addItem") { InputDialog( onDismiss = navController::popBackStack, setValue = { mainviewModel.addShoppingListItem(ShoppingItem(nomeItem = it, adicionadoPor = settingsViewModel.appPreferences.name)) }) } } } } } override fun onStop() { super.onStop() isForeground = false } private fun qrCodeConfiguration() { val moduleInstallCLient = ModuleInstall.getClient(this) val optionalModuleApi = GmsBarcodeScanning.getClient(this) moduleInstallCLient .areModulesAvailable(optionalModuleApi) .addOnSuccessListener { Toast.makeText(this, "模块可用", Toast.LENGTH_SHORT).show() if (!it.areModulesAvailable()) { Toast.makeText(this, "QRCode模块未安装", Toast.LENGTH_SHORT).show() val moduleInstallRequest = ModuleInstallRequest.newBuilder() .addApi(optionalModuleApi) .build() moduleInstallCLient.installModules(moduleInstallRequest) .addOnSuccessListener { Toast.makeText(this, "模块安装完成", Toast.LENGTH_SHORT).show() } .addOnFailureListener { Toast.makeText(this, "模块安装失败", Toast.LENGTH_SHORT) .show() } } else { Toast.makeText(this, "QRCode模块已存在", Toast.LENGTH_SHORT).show() } } .addOnFailureListener { // TODO: 处理获取二维码阅读器失败的情况 } } private fun notificationsConfiguration() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission( this, Manifest.permission.POST_NOTIFICATIONS ) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101) } } // TODO: 拆分以提升UX val name = "商品添加/移除通知" val importance = NotificationManager.IMPORTANCE_DEFAULT val channel = NotificationChannel("list_notifications", name, importance) val notificationManager: NotificationManager = application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } }
HomeScreen代码
... @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( viewModel: ComprinhasViewModel, toWelcomeScreen: () -> Unit, toSettingsScreen: () -> Unit, toReceiptsScreen: () -> Unit, showDialog: () -> Unit, ) { val cartList by viewModel.cartList.collectAsState(initial = emptyList()) val shoppingList by viewModel.shoppingList.collectAsState(initial = emptyList()) val homeState by viewModel.uiState.collectAsState(initial = UiState.LOADING) val scaffoldState = rememberBottomSheetScaffoldState() val scope = rememberCoroutineScope() LaunchedEffect(key1 = 1) { if (viewModel.appPreferences.welcomeScreen) toWelcomeScreen() else viewModel.getShoppingList() } BottomSheetScaffold( topBar = { Surface { TopBar( showDialog = showDialog, toReceiptsScreen = toReceiptsScreen, toSettings = toSettingsScreen, uiState = homeState ) } }, sheetPeekHeight = 115.dp, scaffoldState = scaffoldState, sheetContent = { BottomBar( cartList = cartList, onRemoveItem = { viewModel.removeFromCart(it) }, onClearCart = { viewModel.clearCart() scope.launch { scaffoldState.bottomSheetState.partialExpand() } }, ) } ) {innerPadding -> if (homeState == UiState.LOADING) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { CircularProgressIndicator( modifier = Modifier.width(32.dp) ) } } else { ShoppingList( shoppingList = shoppingList, modifier = Modifier.padding(innerPadding), onMoveToCart = { viewModel.moveToCart(it) }, onDelete = { viewModel.deleteShoppingItem(it) }, ) } } }
DataStore仓库代码
... data class AppPreferences( val welcomeScreen: Boolean, val name: String, val listId: String, val listPassword: String, val lastChanged: Long, ) class PreferencesRepository( private val preferencesDatastore: DataStore<Preferences>, context: Context ) { private object PreferencesKeys { val WELCOME_SCREEN = booleanPreferencesKey("welcome_screen") val USER_NAME = stringPreferencesKey("user_name") val LIST_ID = stringPreferencesKey("list_id") val LIST_PASSWORD = stringPreferencesKey("list_password") val LAST_CHANGED = longPreferencesKey("last_changed") val UI_STATE = intPreferencesKey("ui_state") } val preferencesFlow: Flow<AppPreferences> = preferencesDatastore.data.map { preferences -> val welcomeScreen = preferences[PreferencesKeys.WELCOME_SCREEN] ?: true val name = preferences[PreferencesKeys.USER_NAME] ?: "" val listId = preferences[PreferencesKeys.LIST_ID] ?: "" val listPassword = preferences[PreferencesKeys.LIST_PASSWORD] ?: "" val lastChanged = preferences[PreferencesKeys.LAST_CHANGED] ?: -1 AppPreferences(welcomeScreen, name, listId, listPassword, lastChanged) } suspend fun updateUiState(state: UiState) { preferencesDatastore.edit{ it[PreferencesKeys.UI_STATE] = state.ordinal} } val uiState: Flow<UiState> = preferencesDatastore.data.map { val e = it[PreferencesKeys.UI_STATE] ?: UiState.LOADED enumValues<UiState>().first { it.ordinal == e } } suspend fun updateNameAndListId(name: String, listId: String) { preferencesDatastore.edit { preferences -> preferences[PreferencesKeys.USER_NAME] = name preferences[PreferencesKeys.LIST_ID] = listId } } suspend fun updateUserPrefs(name: String, listId: String, listPassword: String) { preferencesDatastore.edit {preferences -> preferences[PreferencesKeys.WELCOME_SCREEN] = false preferences[PreferencesKeys.USER_NAME] = name preferences[PreferencesKeys.LIST_ID] = listId preferences[PreferencesKeys.LIST_PASSWORD] = listPassword } } }
堆栈跟踪信息
java.util.NoSuchElementException: Array contains no element matching the predicate. at com.example.comprinhas.data.PreferencesRepository$special$$inlined$map$2$2.emit(Emitters.kt:227) at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:87) at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66) at androidx.datastore.core.SingleProcessDataStore$data$1$invokeSuspend$$inlined$map$1$2.emit(Collect.kt:137) at kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$1$1.emit(Limit.kt:40) at kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:396) at kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:114) at androidx.datastore.core.SingleProcessDataStore$data$1$invokeSuspend$$inlined$map$1.collect(SafeCollector.common.kt:114) at kotlinx.coroutines.flow.FlowKt__CollectKt.emitAll(Collect.kt:109) at kotlinx.coroutines.flow.FlowKt.emitAll(Unknown Source:1) at androidx.datastore.core.SingleProcessDataStore$data$1.invokeSuspend(SingleProcessDataStore.kt:117) at androidx.datastore.core.SingleProcessDataStore$data$1.invoke(Unknown Source:8) at androidx.datastore.core.SingleProcessDataStore$data$1.invoke(Unknown Source:4) at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61) at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230) at com.example.comprinhas.data.PreferencesRepository$special$$inlined$map$2.collect(SafeCollector.common.kt:113) at androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1.invokeSuspend(SnapshotFlow.kt:64) at androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1.invoke(Unknown Source:8) at androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1.invoke(Unknown Source:4) at androidx.compose.runtime.SnapshotStateKt__ProduceStateKt$produceState$3.invokeSuspend(ProduceState.kt:150) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81) at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41) at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:211) at android.os.Looper.loop(Looper.java:300) at android.app.ActivityThread.main(ActivityThread.java:8296) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954) Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@55e174a, androidx.compose.runtime.BroadcastFrameClock@164a6bb, StandaloneCoroutine{Cancelling}@24a27d8, AndroidUiDispatcher@ac6f731]
问题原因分析
崩溃的核心原因在PreferencesRepository的uiState流定义中:
val uiState: Flow<UiState> = preferencesDatastore.data.map { val e = it[PreferencesKeys.UI_STATE] ?: UiState.LOADED enumValues<UiState>().first { it.ordinal == e } }
多次清除用户数据后,DataStore中可能残留了无效的枚举序号值(比如旧版本UiState枚举的序号与当前版本不匹配,或者数据清除不彻底导致脏数据),此时调用first()方法会因为找不到匹配的枚举值直接抛出NoSuchElementException。
解决方案
修改uiState流的处理逻辑,增加安全兜底机制,避免找不到匹配枚举时崩溃:
val uiState: Flow<UiState> = preferencesDatastore.data.map { val e = it[PreferencesKeys.UI_STATE] ?: UiState.LOADED.ordinal enumValues<UiState>().firstOrNull { it.ordinal == e } ?: UiState.LOADED }
- 使用
firstOrNull()替代first(),找不到匹配值时返回null而非抛出异常 - 增加兜底逻辑,默认返回
UiState.LOADED - 将默认值改为
UiState.LOADED.ordinal,保证类型与存储的序号一致,避免隐式转换带来的潜在问题
另外,建议在清除用户数据时,确保DataStore的存储文件也被彻底删除,或者在应用首次启动时初始化DataStore的默认值,从根源上避免脏数据残留。
内容的提问来源于stack exchange,提问作者KaduCmK
相关产品推荐
相关产品推荐

