ursus
02/28/2026, 4:34 PMinterface Presented {
fun onCreate()
fun onDestroy()
}
that I'd like to create per nav3 screen (NavEntry)
How can I have the onCreate/onDestroy called automatically somehow?
I'm reading up on NavEntryDecorator which sounds like what I want, but I'm not sure how it would access the given `presenter`instance of a given screen?Pablichjenkov
02/28/2026, 8:14 PMursus
02/28/2026, 8:41 PMRetainObserver ookay this is the missing piece
thank you!ursus
02/28/2026, 9:02 PMretain is a top level function, how does it know know to scope the object to "this" nav entry?Pablichjenkov
02/28/2026, 9:24 PMursus
02/28/2026, 11:33 PM00:30:26.141 D AppRoot
00:30:26.178 D home entry=sk.ursus.nav3demo.HomePresenter@691705a // now push profile
00:30:28.517 D profile entry=sk.ursus.nav3demo.ProfilePresenter@7f9d399 // now orientation change
00:30:32.680 D AppRoot
00:30:32.704 D profile entry=sk.ursus.nav3demo.ProfilePresenter@7f9d399 // now back
00:30:36.313 D home entry=sk.ursus.nav3demo.HomePresenter@42c3923 // observe different instance of home vm <-----------ursus
02/28/2026, 11:34 PMentryProvider {
entry<Home> {
val homePresenter = retain { HomePresenterl() }
Log.d("Default", "home entry=$homePresenter")
HomeScreen(
onGoToProfile = { backStack += Profile }
)
}
entry<Profile> {
val profilePresenter = retain { ProfilePresenter() }
Log.d("Default", "profile entry=$profilePresenter")
ProfileScreen(
onBack = { backStack.pop() }
)
}
}ursus
02/28/2026, 11:42 PMRetainedValuesStoreNavEntryDecorator which is not in the library yetPablichjenkov
03/01/2026, 12:17 AMursus
03/01/2026, 12:18 AMonCreate?
@Composable
fun <T : Presenter> retainPresenter(factory: () -> T): T {
return retain {
val presenter = factory()
presenter.onCreate()
RetainedPresenterObserver(presenter)
}.presenter
}
here?Pablichjenkov
03/01/2026, 12:42 AMinit {} . Does it mean the first time it enters composition? - I would use one of the RetainCallback functions.
But I guess is fine placing it where you have it, instead of using init{}ursus
03/01/2026, 12:45 AMinit { .. } , to me it's super sketchy, doing such stuff before the instance is even created
so I delegate such logic to a standalone function once instance was created
and yea semantically it means "presenter is ready".. im coming from fragments so there is was next line after instantiation in onCreateViewPablichjenkov
03/01/2026, 1:17 AMtad
03/01/2026, 4:29 AMretain if the UI runtime handles all configuration changes (all of them get added to configChanges in the manifest)Pablichjenkov
03/01/2026, 4:37 AMtad
03/01/2026, 4:53 AMretain would ordinarily do nothing more than remember.Pablichjenkov
03/01/2026, 4:59 AMtad
03/01/2026, 5:00 AMursus
03/01/2026, 12:40 PMPablichjenkov
03/01/2026, 7:04 PMretain is not meant to cover the process death scenario. Not sure how to do what you want. And yes you are right about the rememberSavable behaviorursus
03/01/2026, 7:07 PMPablichjenkov
03/01/2026, 7:08 PMursus
03/01/2026, 7:54 PMPablichjenkov
03/01/2026, 8:03 PMretainWithSaveable()ursus
03/01/2026, 8:03 PMtad
03/01/2026, 8:07 PMursus
03/01/2026, 8:20 PMPablichjenkov
03/01/2026, 8:20 PMursus
03/01/2026, 8:20 PMursus
03/01/2026, 8:20 PMursus
03/01/2026, 8:21 PMtad
03/01/2026, 8:23 PMursus
03/01/2026, 8:23 PMursus
03/01/2026, 8:23 PMursus
03/01/2026, 8:24 PMtad
03/01/2026, 8:26 PMursus
03/01/2026, 8:26 PMursus
03/01/2026, 8:27 PMtad
03/01/2026, 8:27 PMretain { } in the compositionursus
03/01/2026, 8:29 PMursus
03/01/2026, 8:29 PMtad
03/01/2026, 8:37 PMandroidx-lifecycle components being bolted-on libs... But the issue is that you have all this code in your composition to rememberSaveable this piece of state, retain that piece of state, etc, and VM just encapsulates it for you so you have one viewModel() call.
I do it the way you want in our production app and it is a ton of boilerplate. Now that I'm clued in to this retain stuff I am starting to regret that choice, because we probably don't do things like network calls correctly across config changes, and it's not something that is easily testable so I have no idea until I make something break manually.ursus
03/01/2026, 8:38 PMviewModel() has saving state across process death automatically?tad
03/01/2026, 8:39 PMSavedStateHandleursus
03/01/2026, 8:40 PMtad
03/01/2026, 8:41 PMursus
03/01/2026, 8:42 PMursus
03/01/2026, 8:43 PMursus
03/01/2026, 8:44 PMursus
03/01/2026, 9:03 PMvar savedState by rememberSaveable(stateSaver = PaymentSavedStateSaver) {
mutableStateOf<PaymentSavedState?>(null)
}
val presenter = retain {
PaymentPresenter(
initialState = savedState?.toState() ?: State()
)
}
LaunchedEffect(key1 = presenter) {
presenter.state.collectLatest { latestState ->
savedState = latestState.toSavedState()
}
}
is this lame?ursus
03/01/2026, 9:04 PMSavedStateHandletad
03/01/2026, 9:06 PMretain business. I think a less-ugly option is to replace the by with = and pass the MutableState instance to your presenter. Then you have one source-of-truth.ursus
03/01/2026, 9:11 PMtad
03/01/2026, 9:12 PMtad
03/01/2026, 9:13 PMursus
03/01/2026, 9:41 PMtad
03/01/2026, 9:43 PMursus
03/01/2026, 9:58 PMtad
03/01/2026, 9:59 PMPablichjenkov
03/01/2026, 10:35 PMfun <T, P : Presenter> rememberRetainSavaablePresenter(
presenterFactory: (T) -> P
) : P {}
Then
val presenterX =
rememberRetainSavaablePresenter {
PresenterX(initialState = it)
}tad
03/01/2026, 10:37 PMPablichjenkov
03/01/2026, 10:39 PMtad
03/01/2026, 10:41 PMrememberSaveable<T> and retain<P>tad
03/01/2026, 10:42 PMPablichjenkov
03/01/2026, 10:42 PMtad
03/01/2026, 10:45 PMrememberSaveable shouldn't also retain the created instance; I am struggling to find a case where you would want to recreate on config change but not on process restartPablichjenkov
03/01/2026, 10:48 PMursus
03/01/2026, 10:48 PMursus
03/01/2026, 10:49 PMursus
03/01/2026, 10:49 PMtad
03/01/2026, 10:52 PMPablichjenkov
03/01/2026, 11:08 PMPablichjenkov
03/01/2026, 11:10 PMursus
03/01/2026, 11:16 PMtad
03/01/2026, 11:23 PMursus
03/02/2026, 12:17 AM