02/25/2023, 11:12 AM
hi, just occurred to me, is the coroutine we pass to ballast ViewModels used solely for lifecycle control? I have my Router set up to be injected by Koin as a single, but for this reason its not really possible to pass it the coroutineScope remembered at the app Composable level, and instead i just have a factory method for the scope. facing this question again for the repositories so i figure now's the time to get this right for me. @Andromadus Naruto’s repo was an interesting example, where the "applicationScope" provided is thus
val AppScope = CoroutineScope(window.asCoroutineDispatcher())
which is a browser thing, but it also makes me wonder what would happen if this coroutine was cancelled for any reason (at a guess, the window as coroutine dispatcher prevents that from happening?)
thinking about my solution,
factory { CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob()) }
"ought to" work, because SupervisorJob means its not getting cancelled on its own any time soon. I just wonder if its at all important for the scope to be explicitly scoped to the App. On one hand, logically i would assume that it doesnt matter because the process is killed when the app closes anyway. but on the other, in that case why would there be alternate solutions like
? as this should be achievable enough without Koin, though maybe it isnt

Casey Brooks

02/25/2023, 2:42 PM
The main reason for passing a CoroutineScope into a ViewModel is to control its lifecycle, as everything that Ballast does will be running on that coroutineScope. Internally, it creates child scopes for the different things it’s doing (handling inputs, sideJobs, interceptors, etc), but they’re all linked to the parent scope such that when the parent scope is cancelled, the ViewModel is also closed. You’re right that a global coroutineScope and the ViewModels running on it will be cancelled when the process goes away, so it’ doesn’t matter too much whether you use a
if you are treating your repositories as Singletons. But if you want them scoped more locally than that, for example, on a
, then that will need to be passed in. So for the specific question of how to provide a Router with Koin, you need to decide what “global” means for your application. If it’s truly global and spans multiple
, then you’ll likely want the Router to be truly global and running on
or your custom injected coroutineScope. But if you want your Router to be tied to the lifecycle of a Composable instead of the Window, then you may want to create the Koin injector directly within the composition as well so that you can manually provide the
to a Module
As an aside, I’ve personally found that I don’t like Koin because the way Compose/Ballast encourage me to write apps is to keep things locally scoped, while Koin is basically global. Yes, you can technically create and pass around a
, but it’s easy to forget that and end up using the global instance, and it becomes cumbersome to use a specific KoinApplication everywhere. So lately, I’ve just been doing everything with manual DI and have had less confusion about how to provide values where I need them. That’s not to say Koin is bad, but rather I’ve found (kind of unintentionally) that Ballast encourages you to think about aspects of your application that you didn’t before, like what does “global” really mean for your application. It was always a problem, even without using Ballast or Compose, but we tended to not think about it, and in doing so could cause some subtle issues with scoping and lifetimes in our apps. Ballast just gives you some tools to help you define those boundaries


02/25/2023, 3:26 PM
thank you! very comprehensive! I'm keen to use koin because Ive been spoiled by Hilt and don't want to think about di very much. my plan is for the router and repos to be truly globally scoped, so that suits my needs fine for the moment. I was already wondering about passing parameters to koin provider functions, so thanks for the link :) looks I would be able to use koin scoped to the composition like how ballast would handle it simply by making it a factory method. that way I could
remember(CoroutineScope) {
   get<ViewModel>(paramsOf(coroutineScope) )
and still let koin provide global components. I suppose the real difficulty comes when some components are scoped longer than others, and would like to be injected in, but are not global. at that point i would just not provide them with koin, seems like you can mix and match ok