The Composable function Application creates a NavHostController which defines 2 targets. A StartScreen and a ContentScreen. The StartScreen has just a single button, which triggers a simulated backend request and changes State (using kotlins StateFlow) depending on the state of the request. When the result comes back, the NavControllers navigate method gets called in order to show the returned content on the ContentScreen.
The Problem:
The states Init and Loading work properly, but as soon as the content is supposed to be shown, the ContentScreen gets redrawn in a loop and doesn't stop.
What do I do wrong here?
/** Dependencies
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:1.0.2"
implementation "androidx.compose.ui:ui:1.0.2"
implementation "androidx.compose.material:material:1.0.2"
implementation "androidx.compose.ui:ui-tooling-preview:1.0.2"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation "androidx.navigation:navigation-compose:2.4.0-alpha08"
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07'
implementation 'androidx.activity:activity-compose:1.3.1'
*
* **/
sealed class MainState {
object Init : MainState()
object Loading : MainState()
data class Content(val data: String) : MainState()
}
class MainViewModel : ViewModel() {
private val _state = MutableStateFlow<MainState>(MainState.Init)
val state: StateFlow<MainState> = _state
fun dosomething() {
viewModelScope.launch {
_state.value = MainState.Loading
// emulating some BE call
delay(4000)
_state.value = MainState.Content("some backend result")
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Application()
}
}
}
@Composable
fun Application() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "start") {
composable("start") {
val viewmodel: MainViewModel = viewModel()
val state by viewmodel.state.collectAsState()
when (val state = state) {
is MainState.Content -> navController.navigate("content/${state.data}")
is MainState.Loading -> LoadingScreen()
MainState.Init -> StartScreen()
}
}
composable(
"content/{content}",
arguments = listOf(
navArgument("content") {
type = NavType.StringType
}
)
) {
ContentScreen(content = it.arguments!!.getString("content")!!)
}
}
}
@Composable
fun LoadingScreen() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator()
}
}
@Composable
fun StartScreen(viewmodel: MainViewModel = viewModel()) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Button(onClick = { viewmodel.dosomething() }) {
Text(text = "Click Me!")
}
}
}
@Composable
fun ContentScreen(content: String) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Card(modifier = Modifier.padding(8.dp)) {
Text(text = content)
}
}
}