Thanks for those references! I fully agree with you about the drawbacks of ViewModels. Especially in a KMM project, being forced to use Android VMs just to get the benefits of retention across configuration changes (+SavedStateHandle against process death), seems sub-optimal.
One interesting point is the one about CoroutineScopes. I used to pass in a scope initially because you know structured concurrency seems to be a great thing to use to cancel the asynchronous stuff that might go on in a business logic component. I then realized that all you need is an external lifecycle and the business logic component can simply manage their own CoroutineScope(s) tied to that lifecycle. I'm working on tests atm and so far I haven't seen a need to pass in my own scope, I simply do a lifecycle.onDestroy() to cancel whatever is going on in the bloc.
Addendum: I had to decide between a CoroutineScope (which would be used to create an internal bloc lifecycle) and a Lifecycle object (which is used to create internal CoroutineScopes). IMO using a lifcecycle is more flexible (more states than just start/stop) and a bit less opinionated.