Проблема в том, что мое приложение совершенно случайно начинает давать сбой при запуске, обычно после слишком много раз очистки пользовательских данных. Я понятия не имею, почему это происходит, поскольку, похоже, оно срабатывает даже без вмешательства в мой репозиторий 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) },
)
}
}
}
Код: Выделить всё
...
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