https://kotlinlang.org logo
#compose
Title
# compose
r

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
j

jim

08/03/2020, 6:08 PM
Have you seen Jetnews (https://github.com/android/compose-samples/tree/master/JetNews) or are you looking for something even more complex than that?
z

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: https://github.com/square/workflow-kotlin-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.
r

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
AppContent(
            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 😅
z

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?
r

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
a

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 https://github.com/adrianblancode/Cheddar
f

Fudge

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
p

popalay

08/04/2020, 4:32 AM
You can check out my home project: Square workflow + compose + Koin Di + backstack https://github.com/Popalay/Tracktor-ComposeUI
👏 1
👍 1
j

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 {} }

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

@Composable
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

@Composable
fun composeSubDI(
    diBuilder: DI.MainBuilder.() -> Unit,
    props: Any = EmptyProps,
    content: @Composable() () -> Unit
) = Providers(
    DIAmbient provides remember(
        props,
        calculation = { subDI(DIAmbient.current, init = diBuilder) }),
    children = content
)
Usage is simple:
Copy code
@Composable
fun loginComponent() {
    composeSubDI(diBuilder = {
        import(loginViewModule())
    }) {
        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) 🙂