Is there a neat way of injecting classes directly ...
# compose
m
Is there a neat way of injecting classes directly to low-level composables? E.g. if I have a standalone file loading composable (like AsyncImage) and I want to inject a custom okhttp instance from dagger-hilt. Do I need to inject it to activity and then pass down the hierarchy (even with compositionLocalOf)? Or is there a better way of doing that?
s
For AsyncImage in particular, the library already provides you with a CompositionLocal approach by defining your own ImageLoader on the Application itself, as described here https://coil-kt.github.io/coil/getting_started/#image-loaders In that image loader, you can pass in your custom okhttp instance in there, like we do here.
m
Asyncimage is just an example here tbh, I need something for a custom standalone file downloader component.
s
Yeah, and I think it’s a good example showing that your options are either a composition local, or passing it through all the way down. In your case, could it be that your file downloading component can just take in the right state in and be able to call some callbacks, and then you can hook it up together in a way that doesn’t require you to do this? For example, we got a component which expects some content from the backend as well, and is reused across the app in some places, but the compose component itself doesn’t know anything about that, just takes in state and can call the right callbacks. Then on each screen that uses it, we do the wiring that’s required by making those callbacks call something on the screen’s ViewModel, in which you can do all the necessary dependency injection, of your DownloadThingy in your case that is, which can be a singleton provided by your DI
m
Our team builds some reusable components and the idea is that they can be really standalone, i.e. just sit entirely on the outer layer of Clean Architecture without depending on any app's business logic and thus not crossing the boundaries of other CA layers (e.g. viewmodels, domain etc). AsyncImage is a well-known example of such a component - it only provides a UI-facing interface, but of course it does a lot of computation, data access etc under the hood. Lots of other components can be designed like this and thus be reused across different applications easily. So code-design-wise I know what I want to do, however the outer CA layer encompasses both UI and networking and platform etc. and I still sometimes need to inject some platform-dependent stuff like okhttpclient to the compose component. This is why I'm looking for the right way of doing that - a bit like how there exists an elegant hilt api to inject viewmodels to the composables, I was wondering if other classes can be injected as well.
s
Yeah, not aware of other alternatives myself, if you really want to put effort into this, maybe take a look at how ViewModels are provided by hilt for example, to see if you can do something similar. Otherwise go with a Local like Coil does. With all that said however, regarding scoping such intense work as downloading something outside of anything else, how do you plan to test this, and how do you plan to make this work reliable enough without it relying on the composable itself being or not being on the screen?
m
take a look at how ViewModels are provided by hilt
Yes, it's a good idea, thanks 🙂 As for the testing, the file downloader is more of a state holder that can be attached to some kind of UI and the state holder can be easily testable. Think about it this way - file downloading has an input of (source, destination) and an output of the current progress of the file download. It can be completely decoupled from the app's logic and therefore be a standalone components without traversing the app's architecture / CA layers. More on that pattern: https://developer.android.com/jetpack/compose/state#managing-state
s
Right, and who does the actual downloading in your case? Where is that job scoped?
m
scope is injected, either derived from the composable (if we want to cancel the download when user leaves the composable) or app scope - the state holder does not care. downloading is a stateless class exposing a
flow
of the download. launched by the state holder keeping the scope