# compose

Ricardo C.

08/03/2020, 6:07 PM
Is there any example project that combines backstack + keeping screen state + dependency injection via params? ie, a regular app we're used to. more than the simple examples


08/03/2020, 6:08 PM
Have you seen Jetnews ( or are you looking for something even more complex than that?

Zach Klippenstein (he/him) [MOD]

08/03/2020, 6:12 PM
This is probably overkill, but Square’s Workflow library is very DI friendly and (theoretically) integrates very well with Compose: Unfortunately there’s currently an issue with a bug in the IR backend that prevents compiling workflows and compose code in the same module, but you can use an older version of the library with Compose fine.

Ricardo C.

08/03/2020, 6:16 PM
hm... last time I checked JetNews it didn't have DI or backstack. But seems like it's still missing a backstack and keeping state per backstack entry 🤔
Tbh I had checked workflow before but I don't find it very easy to get into. it has a bit of a steep learning curve 😕
I see that JetNews does injection via params but it's very manual. It only touches the graph at a very high level:
Copy code
            navigationViewModel = navigationViewModel,
            interestsRepository = appContainer.interestsRepository,
            postsRepository = appContainer.postsRepository
From this point below it's manual. I've been trying to do all of these automatically with dagger but always hit some issue that I don't know how to get around
I've managed to inject everything automatically into "screen" classes that have a render composable but I can't figure how to access the navigator. and can't pass it through ambients because it's generic
was hoping to find some more mature example done by someone that knows more than me 😅

Zach Klippenstein (he/him) [MOD]

08/03/2020, 6:28 PM
Can your screens take callbacks for navigation, instead of passing the navigator through directly?

Ricardo C.

08/03/2020, 6:33 PM
I don't think so. I was trying to map destinations -> screens using multibinds. I was trying to avoid going into the big when route
Even with assisted inject, I would have to have a multibinds of factories. but the parameters for each factory could be different, so I would have to use the big when

Adrian Blanco

08/03/2020, 7:25 PM
I've tried some small work with it, but it's also architecturally a really messy problem to get right :p


08/03/2020, 9:33 PM
You can have “injection” with ambients
A parent provides an ambient with a value and a very deep child can consume it


08/04/2020, 4:32 AM
You can check out my home project: Square workflow + compose + Koin Di + backstack
👏 1
👍 1

Joost Klitsie

08/04/2020, 10:50 AM
You can pass on a dependency object down the ambient line. I did a dependency injection with Kodein and Compose, here are the helper functions:
Copy code
val DIAmbient = ambientOf { DI {} }

inline fun <reified T : Any> instance(): DIProperty<T> = DIAmbient.current.instance()

inline fun <reified A : Any, reified T : Any> instance(arg: A?): DIProperty<T> = arg?.let {
    DIAmbient.current.instance<A, T>(arg = it)
} ?: instance()

object EmptyProps

fun composeSubDI(
    diBuilder: DI.MainBuilder.() -> Unit,
    props: Any = EmptyProps,
    content: @Composable() () -> Unit
) = Providers(
    DIAmbient provides remember(
        calculation = { subDI(DIAmbient.current, init = diBuilder) }),
    children = content
Usage is simple:
Copy code
fun loginComponent() {
    composeSubDI(diBuilder = {
    }) {
        val viewModel by instance<LoginContract.ViewModel>()
        val viewState = viewModel.viewState.collectAsState(AndroidUiDispatcher.Main).value
        Column {
            OutlinedTextField(value = viewState.userName, onValueChange = viewModel::updateUserName, label = {})
            OutlinedTextField(value = viewState.password, onValueChange = viewModel::updatePassword, label = {})
            OutlinedButton(onClick = viewModel::onLoginPressed) { Text("Login") }
basically this will create sub DI objects that are scoped to the compose scope they are in
It is just important to make sure that for example the ViewModel binding is a singleton (within the scope) 🙂