October 21, 2024
Chicago 12, Melborne City, USA
Android

How do I force the UI to reload with Jetpack Compose?


I have a GameState object which gets updated whenever the state of the game changes, and some of the UI doesn’t update properly. I would like to just call a reload function whenever the data reloads so I don’t have to worry about it not updating.

package dev.madcatter.lostworld

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import dev.madcatter.lostworld.data.GameState
import dev.madcatter.lostworld.ui.Console
import dev.madcatter.lostworld.ui.Turns
import dev.madcatter.lostworld.ui.theme.LostWorldTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

    private lateinit var myApp: LostWorldApp
    private lateinit var gameState: MutableState<GameState>
    private var updateTicker: MutableState<Int> = mutableStateOf(0)

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        myApp = application as? LostWorldApp ?: throw IllegalStateException("Application class is not LostWorldApp")
        gameState = mutableStateOf(myApp.gameState!!)

        enableEdgeToEdge()
        setContent {
            LostWorldTheme {
                MainContent()
            }
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun MainContent() {
        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(text = "Lost World") },
                    actions = {
                        IconButton(onClick = {
                            gameState.value.debug("---\n\nReload button clicked.")
                            reloadGameState()
                        }) {
                            Icon(
                                imageVector = Icons.Default.Refresh,
                                contentDescription = "Reload"
                            )
                        }
                        IconButton(onClick = {
                            startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
                        }) {
                            Icon(
                                imageVector = Icons.Default.Settings,
                                contentDescription = "Settings"
                            )
                        }
                    }
                )
            },
            content = { innerPadding ->
                // Main content here
                Row(modifier = Modifier.padding(innerPadding)) {
                    Turns(
                        gameState.value,
                        modifier = Modifier
                            .padding(16.dp)
                            .weight(0.25f)
                            .fillMaxHeight()
                    )

                    VerticalDivider(
                        color = MaterialTheme.colorScheme.outline,
                        modifier = Modifier
                            .width(1.dp)
                            .align(Alignment.CenterVertically)
                            .fillMaxHeight(0.85f)
                    )

                    Console(
                        gameState.value,
                        modifier = Modifier
                            .padding(16.dp)
                            .weight(0.75f)
                            .fillMaxHeight()
                    )
                }
            }
        )
    }

    override fun onStart() {
        super.onStart()

        // Run the reload after a short delay using lifecycleScope
        lifecycleScope.launch(Dispatchers.Main) {
            delay(200) // Wait 200 milliseconds, or adjust as necessary
            reloadGameState()
        }
    }

    private fun reloadGameState() {
        gameState.value.reload()
        gameState.value = myApp.gameState!!
        updateTicker.value++
    }
}

What I tried:

  • I used onStart() to reload gameState whenever the activity comes to the foreground, adding a small delay to let the UI settle.
  • To refresh the UI, I reassigned gameState, which sometimes resulted in the output being cleared.
  • Reassigning gameState led to issues with clearing state, like losing console output.
  • Now I’m exploring how to best update only necessary properties of gameState to trigger a UI refresh without losing existing data.



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video