Does Simple-Stack supports hot-reload?
I've been using Simple-Stack and Compose Integration quite a lot lately. While both work normally in production without any issues, I'd like to know if there's something we can do to fix hot-reload.
I know that hot-reload is still a dream for Android devs. Still, on Android Studio we have live edit for Compose that surprisingly works fine in the latest version of Android Studio but I've been facing crashes whenever I edit some Composable:
java.lang.IllegalStateException: State change completion cannot be called multiple times!
at com.zhuinden.simplestack.NavigationCore$1.stateChangeComplete(NavigationCore.java:714)
at com.zhuinden.simplestackcomposeintegration.core.ComposeStateChanger$DetermineDisplayedScreens$1.invokeSuspend(ComposeIntegrationCore.kt:336
I forgot to open Github notifications for a while, the question is valid, but I don't know what lifecycle calls Android Studio is trying to do at first glance for live-edit to work.
I would imagine this to happen only if they re-execute onCreate() on an Activity that already exists, which is.. unprecedented. 🤔
Yeah I haven't investigated this yet but I'll keep you posted if I find something
I feel like the only way to figure this one out is System.out.println()ing in various functions.
Although I would NOT be surprised if this is unique to something in the compose integration for how completionCallback.stateChangeComplete() is called.
@lcszc have you found anything yet? i have not tried using compose hot-reload honestly if not for the github notification, I just completely forgot about this.
@Zhuinden, I'm sorry for my delay in checking this out! I just now figured out what's happening.
Gladly the Activity is not recreated. Instead setContent is recomposed, forcing your app to be redrawn entirely.
This is the setup that was crashing the app on live edits:
// Your Activity....
private lateinit var backstack: Backstack
private lateinit var composeStateChanger: ComposeStateChanger
private val backPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
backstack.goBack()
}
}
// Then this in onCreate
composeStateChanger = ComposeStateChanger()
// and this under setContent {}
BackstackProvider(backstack) {
Theme {
Box(Modifier.fillMaxSize()) {
composeStateChanger.RenderScreen()
}
}
}
This is the known setup for using Simple-Stack, which I always use so I thought it was fine. I guess this is what you recommend in the docs as well.
Anyway, to make live edits to work without issues, I've used the Compose-friendly version of Simple-Stack that allows me to customize it the way I need:
// This is your setContent {}
val composeStateChanger = remember {
ComposeStateChanger()
}
val asyncStateChanger = remember(composeStateChanger) {
AsyncStateChanger(composeStateChanger)
}
val backstack = rememberBackstack(asyncStateChanger) {
createBackstack(
scopedServices = DefaultServiceProvider(),
globalServices = app.globalServices,
initialKeys = History.of(SplashKey())
)
}
BackstackProvider(backstack) {
Theme {
Box(Modifier.fillMaxSize()) {
composeStateChanger.RenderScreen()
}
}
}
This is the result:
https://github.com/user-attachments/assets/e319c30c-4710-4b1e-b47e-b5cc70584705
I'd betComposeNavigator works fine but I haven't tried it