** Over the past month, the English-speaking community has been rehashing the topic of how to properly trigger state initialization in ViewModel. First there was a discussion on Twitter, then here which resulted in an article, on Reddit where they threw the whole kitchen sink at the author in the comments, on YouTube they chewed through it again, and finished it all off with the author writing a second article with answers (serious over-engineering) to popular questions. And it's all really interesting, quite telling.
The task is this: there's a screen, it has some state, at startup some process is triggered, for example an internet request, which will change that state. The question is: where should all this be done? Let's note right away that we're only talking about Compose, so zoomers are trying to invent a solution to a problem they didn't even have to solve before.
The first solution, which according to the author's poll is used by about a third of people: LaunchedEffect(Unit) { viewModel.load() }. That is, the Composable function itself triggers a method call on the first recomposition. Pros: business logic is decoupled from object creation. Cons: you can forget; the view commands the viewmodel; when the view is recreated everything reloads.
The second option is more popular - use the init section of ViewModel. Pros: you'll never forget to call the init section. Cons: business logic (launching coroutines) is formally part of object creation and the consequences that follow.
Then a guy from Google came and says: actually both of these are antipatterns, we have all the recommendations in the documentation. The recommendations are that the screen state should be a cold flow that starts all initialization logic when the view subscribes to it. And to avoid recreating the flow each time, wrap the StateFlow with some strategy like SharingStarted.WhileSubscribed(5_000). They say our grandfathers had this back in LiveData days.
Pros: business logic is decoupled from object creation. No need to do anything additional manually. Survives configuration changes. Cons: forget about everything else, but we won't tell you about that. 🏥
It seems to me this line with 5 seconds is some kind of meme, because they wouldn't seriously write this in recommendations. The idea is that state will survive view recreation like screen rotations, but if no one is subscribed to it for "a long time", the flow can be killed. Reliable as a Swiss watch. You go to another screen, come back after 10 seconds - everything reloads. They argue that the impact of reloading is usually insignificant. No comment. Even if you cache the whole world, it's still unnecessary work to restore the state that was killed for no reason.
Screen state is something more complex than a data class created once. Loading may require parameters that came from screen arguments or from the UI. Maybe we'll want to refresh the loading with some swipe. Usually ViewModel updates it from a bunch of different methods and that's normal. I don't really want to go back to 2017 combining all these data streams into one huge reactive chain, I'm already used to being able to write simple code instead. In my opinion this solution gets worse and worse with each small complication.
To be fair, if we want something similar without these drawbacks, besides WhileSubscribed there are strategies SharingStarted.Lazily or Eagerly. I have much fewer questions about them, here's a good article with similar thoughts, at least they don't break UX. Even in Google's documentation at the links above this is mentioned, but for some reason the only code examples visible are with the first one. 😱
And how do you write such things? For me init + MutableStateFlow is simpler and clearer in general always. Shot myself in the foot once, but that's a separate story.