I'm trying to use StateFlow to collect data from a...
# compose
s
I'm trying to use StateFlow to collect data from a Room Database like painter resources as part of the entity, which is working fine in standard orientation, but when the screen is rotated the flow is reset and the app crashes with this error:
android.content.res.Resources$NotFoundException: Resource ID #0x0
. I'm trying to work out how I can get this data from the database to maybe persist and be saved through an orientation change, or possibly getting the image composable to wait a second for the flow to emit the data again. Any help would be appreciated (I am fairly new to compose) 🙏
c
Maybe it’s better idea to save the image resources locally and store the persistable uri as string in room database.
c
A little more info will be needed. How do you collect the flow? when do you create the painter? some code snippets will be helpful.
s
Well, the issue I'm having is that everything loads ok the first time I navigate to that page so there's no problem with storing the images. I have a page with a list of characters from a game, you click the icon to open a page that shows their moves. The header of the page contains an image with the characters picture. Upon opening the page the pictures and name data all load properly, but when I flip the orientation to landscape the flow of 'character data' is reset to the initial value which has an painter resource value of 0. I think either I need to save the data somehow when I open the page with remember so that it can keep the values. Or retrieve the data from the database without using a flow so that it doesn't automatically reset to empty when I flip orientation... This is how I'm getting the data:
Copy code
fun getCharacterEntry(name: String) {
        characterState.value = allCharacters.value.first { it.name == name }
    }

    // Repository Functions

    fun getAllCharacterEntries() =
        tekkenDataRepository.getAllCharacters()
            .map { it }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000L),
                initialValue = listOf()
        )
I'll grab some more snippets
This is where I'm collecting the data and passing it to the composable screen
Copy code
val character by comboViewModel.characterState.collectAsState()
    val allCombos by comboViewModel.allCombos.collectAsState()
    val allComboEntries by comboViewModel.comboEntries.collectAsState()
    val allMoves by comboViewModel.allMoves.collectAsState()

// Combo Screen
            composable(route = FlowScreen.Combos.name) {
                ComboScreen(
                    comboViewModel = comboViewModel,
                    deviceType = deviceType,
                    onAddCombo = {
                        addComboViewModel.characterState.value = comboViewModel.characterState.value
                        addComboViewModel.allMoves.value = comboViewModel.allMoves.value
                        addComboViewModel.editing.value = false
                        navController.navigate(FlowScreen.AddCombo.name)
                    },
                    onEditCombo = {
                        addComboViewModel.comboState.value = comboViewModel.comboState.value
                        addComboViewModel.characterState.value = comboViewModel.characterState.value
                        addComboViewModel.allMoves.value = comboViewModel.allMoves.value
                        addComboViewModel.existingCombos.value = comboViewModel.comboEntries.value
                        addComboViewModel.editing.value = true
                        navController.navigate(FlowScreen.AddCombo.name)
                    },
                    navigateBack = navController::navigateUp,
                    character = character,
                    comboDisplayList = allCombos,
                    comboEntryList = allComboEntries,
                    allMoves = allMoves,
                )
            }
This is where I'm calling the painter
Copy code
@Composable
fun Header(
    character: CharacterEntry,
    fontColor: Color,
    onAddCombo: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.fillMaxWidth()
    ) {
        Log.d("", "Loading Character Image...")
        Image(
            painter = painterResource(character.imageId),
            contentDescription = character.name,
            modifier = Modifier
                .size(75.dp)
                .align(Alignment.CenterStart)
        )
        Text(
            text = character.name,
            color = fontColor,
            fontSize = if (character.name.length > 9) 60.sp else 80.sp,
            style = MaterialTheme.typography.displayMedium,
            modifier = modifier.align(Alignment.Center)
        )
        Log.d("", "Loading Icon image")
        IconButton(
            modifier = modifier
                .align(Alignment.CenterEnd)
                .size(75.dp),
            onClick = onAddCombo,
            content = {
                Icon(
                    imageVector = Icons.Default.Add,
                    contentDescription = stringResource(R.string.add_combo),
                    modifier.size(75.dp)
                )
            }
        )
    }
}
As an update, I have solved the issue of persisting the data between orientation changes by using a Datastore Preferences repository that will hold the flow during recomposition and then returning it to the viewmodel's state variables. Not sure if this is the most optimal way of getting the job done but it's working and I'm now trying to get the Combo displayed on the AddComboScreen to do the same. With a lot more working parts it's proving to be quite a challenge haha...