NoSuchElementException где-то в DataStoreAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 NoSuchElementException где-то в DataStore

Сообщение Anonymous »

У меня есть приложение для Android, которое хранит простые значения с помощью Preferences DataStore. У меня нет экземпляров Proto Datastore.
Проблема в том, что мое приложение совершенно случайно начинает давать сбой при запуске, обычно после слишком много раз очистки пользовательских данных. Я понятия не имею, почему это происходит, поскольку, похоже, оно срабатывает даже без вмешательства в мой репозиторий DataStore.
Это MainActivity (единственное действие):

Код: Выделить всё

...

val Context.dataStore: DataStore
  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, "Módulos disponíveis", Toast.LENGTH_SHORT).show()

if (!it.areModulesAvailable()) {
Toast.makeText(this, "QRCode não está presente", Toast.LENGTH_SHORT).show()

val moduleInstallRequest = ModuleInstallRequest.newBuilder()
.addApi(optionalModuleApi)
.build()

moduleInstallCLient.installModules(moduleInstallRequest)
.addOnSuccessListener {
Toast.makeText(this, "Módulo instalado", Toast.LENGTH_SHORT).show()
}
.addOnFailureListener {
Toast.makeText(this, "Falha ao instalar módulo", Toast.LENGTH_SHORT)
.show()
}
} else {
Toast.makeText(this, "QRCode está presente", Toast.LENGTH_SHORT).show()
}
}
.addOnFailureListener {
// TODO:  tratar falha na obtencao do leitor de qr code
}
}

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: separação para melhor UX

val name = "Adição e remoção de itens"
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)
}
}
Это мой компонуемый домашний экран:

Код: Выделить всё

...
@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
,
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  = 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 = preferencesDatastore.data.map {
val e = it[PreferencesKeys.UI_STATE] ?: UiState.LOADED
enumValues().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: Массив не содержит элементов, соответствующих предикату. в com.example.comprinhas.data.PreferencesRepository$special$$inlined$map$2$2.emit(Emitters.kt:227) в kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) в kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15) в kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:87) в kotlinx.coroutines.flow.internal. SafeCollector.emit(SafeCollector.kt:66) в androidx.datastore.core.SingleProcessDataStore$data$1$invokeSuspend$$inlined$map$1$2.emit(Collect.kt:137) в kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$1 $1.emit(Limit.kt:40) в kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:396) в kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1.collect(SafeCollector.common. kt:114) в androidx.datastore.core.SingleProcessDataStore$data$1$invokeSuspend$$inlined$map$1.collect(SafeCollector.common.kt:114) в kotlinx.coroutines.flow.FlowKt__CollectKt.emitAll(Collect.kt:109) ) в kotlinx.coroutines.flow.FlowKt.emitAll(Unknown Source:1) в androidx.datastore.core.SingleProcessDataStore$data$1.invokeSuspend(SingleProcessDataStore.kt:117) в androidx.datastore.core.SingleProcessDataStore$data$1.invoke (Неизвестный источник: 8) в androidx.datastore.core.SingleProcessDataStore$data$1.invoke (Неизвестный источник: 4) в kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61) в kotlinx.coroutines.flow.AbstractFlow .collect(Flow.kt:230) по адресу com.example.comprinhas.data.PreferencesRepository$special$$inlined$map$2.collect(SafeCollector.common.kt:113) по адресу androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1. вызватьSuspend(SnapshotFlow.kt:64) на androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1.invoke(Неизвестный источник:8) на androidx.compose.runtime.SnapshotStateKt__SnapshotFlowKt$collectAsState$1.invoke(Неизвестный источник:4) на androidx.compose .runtime.SnapshotStateKt__ProduceStateKt$produceState$3.invokeSuspend(ProduceState.kt:150) в kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) в kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:1) 06) в androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81) в androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41) в androidx.compose.ui .platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57) на android.os.Handler.handleCallback(Handler.java:942) на android.os.Handler.dispatchMessage(Handler.java:99) на android .os.Looper.loopOnce(Looper.java:211) в android.os.Looper.loop(Looper.java:300) в android.app.ActivityThread.main(ActivityThread.java:8296) в java.lang.reflect. Method.invoke(собственный метод) по адресу com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559) по адресу com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954) Подавлено: kotlinx .coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@55e174a, androidx.compose.runtime.BroadcastFrameClock@164a6bb, StandaloneCoroutine{Cancelling}@24a27d8, AndroidUiDispatcher@ac6f731]
Я уже пробовал:
  • пересобрать проект
  • удалить весь проект и снова клонировать его из git
    удалена большая часть вызовов репозитория из MainActivity и HomeScreen


Подробнее здесь: https://stackoverflow.com/questions/785 ... -datastore
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»