Joost Klitsie
07/06/2020, 2:04 PMBaseComponent
in itself basically is a lifecycleOwner, observes the parent's lifecycleowner (so copies those and will call on_destroy when onDispose is called on itself) and creates the dependency graph (by extending the parent's dependency graph). Does anyone have any pointers here? 🙂Foso
07/06/2020, 2:36 PMJoost Klitsie
07/06/2020, 2:41 PMLeland Richardson [G]
07/06/2020, 4:46 PMJoost Klitsie
07/06/2020, 4:54 PMLeland Richardson [G]
07/06/2020, 4:57 PMJoost Klitsie
07/06/2020, 5:11 PM-Login
--Welcome
--Registration
--Username/Password
-MainOverview
--News Feed
---Article
----Comment
-Friends
--List
---Messaging
---Details
-User settings
--Name change
--Logout
Usually I would make my navgraphs resemble that, with nested graphs.
So I have the following: Every screen here is a fragment. I inject a ViewModel for every screen. The screens do not know anything about their position, only their parent component controls them. Basically any dependency injection will inherit from the parent component the dependencies and that is how we can inject things in every screen.
So how would I achieve an architecture in compose? I cannot just inject all the viewmodels into my single activity and then pass all of that as arguments down the composable graph. (or should I indeed do this? Expose the entire viewtree through 1 app state). Like if I turn them into classes that are components in themself, I can do injection per component, have 1 ViewModel per component etc. But how do I achieve a tidy architecture with compose if we do not have these components where we can inject things in between?Leland Richardson [G]
07/06/2020, 5:15 PMJoost Klitsie
07/06/2020, 5:17 PMLeland Richardson [G]
07/06/2020, 5:26 PMnewInstance
method of injecting with kodeinJoost Klitsie
07/06/2020, 5:56 PMval ParentDI = ambientOf<DI> { DI {} }
val DI = ambientOf<DI> { DI {} }
@Composable
inline fun <reified T : Any> inject(): DIProperty<T> = DI.current.instance()
@Composable
fun myModule() = subDI(ParentDI.current) {
bind() from singleton {
Controller()
}
}
val RootKodeinContext = DI {
bind() from singleton {
ParentController()
}
}
class ParentController() {
val state = MutableStateFlow(Random.nextInt())
}
class Controller() {
val state = MutableStateFlow("I am magic")
init {
CoroutineScope(Dispatchers.Main).launch {
while (true) {
state.value = System.currentTimeMillis().toString().takeLast(4)
delay(1000)
}
}
}
}
// Provide a context at the root.... or anywhere in the tree you want
@Composable
fun App() {
Providers(
DI provides RootKodeinContext
) {
Column {
val parentController by inject<ParentController>()
parentController.state.collectAsState().value.let {
Text(text = it.toString())
}
Providers(ParentDI provides DI.current) {
Child()
}
}
}
}
@Composable
fun Child() {
// constructs and remembers a Controller based on current kodein context
Providers(
DI provides myModule()
) {
val parentController by inject<ParentController>()
parentController.state.collectAsState().value.let {
Text(text = it.toString())
}
val controller by inject<Controller>()
controller.state.collectAsState().value.let {
// use controller
Text(it)
}
}
}
Leland Richardson [G]
07/06/2020, 9:23 PMmyModule()
in your example?Joost Klitsie
07/06/2020, 9:27 PMval ParentDI = ambientOf<DI> { (ContextAmbient.current.applicationContext as DIAware).di }
`Leland Richardson [G]
07/06/2020, 9:37 PM@Composable
fun subContainer(
bind: KodeinScope.() -> Unit,
content: @Composable () -> Unit
) = Providers(
ParentDI provides subDI(ParentDI.current, bind),
content
)
subDI
produces a new object every time then it will recompose all of the composables that use inject
areEquivalent
method in the ambientOf
declarationJoost Klitsie
07/07/2020, 6:30 AM@Composable
fun subContainer(
bind: DI.MainBuilder.() -> Unit,
content: @Composable () -> Unit
) = Providers(
DI provides subDI(DI.current, init = bind),
children = content
)
If it is only called 1 time, I do not need to make a comparison with for example the context (because I'd have to maintain those myself but I'd rather not)
ambientOf<DI>({ old, new ->....
In theory (tm) the DI object is only build 1 time per "component" and shouldn't change during that component is alive. (so if I would remove and add a "component" from/to the graph then of course it gets reinitialized, but that is fine)
Then it'll look something like this:
val DI = ambientOf { DI {} }
@Composable
inline fun <reified T : Any> inject(): DIProperty<T> = DI.current.instance()
@Composable
fun composeSubDI(
bind: DI.MainBuilder.() -> Unit,
content: @Composable () -> Unit
) = Providers(
DI provides subDI(DI.current, init = bind),
children = content
)
// Provide a context at the root.... or anywhere in the tree you want
@Composable
fun App() {
Providers(
// Inherit from application
DI provides subDI((ContextAmbient.current.applicationContext as DIAware).di) { // We could move out the contents of this somewhere else
bind() from singleton {
ParentController()
}
}
) {
Column {
val parentController by inject<ParentController>()
parentController.state.collectAsState().value.let {
Text(text = it.toString())
}
Child()
}
}
}
val myDIBuilder : DI.MainBuilder.() -> Unit = {
bind() from singleton {
Controller("Some amazing text", false)
}
}
@Composable
fun Child() { // <-- My amazing child component
composeSubDI(bind = myDIBuilder) { // <-- should take care of its own DI stuff
val controller by inject<Controller>()
controller.state.collectAsState().value.let {
Text(it)
}
SomeOtherChild()
}
}
I think it is important to make it singletons (in kodein a singleton is only a singleton in the scope, not for the entire application) so it will just use the DI object to return the same instance all the time.
I didn't see the DI object changing during redrawing the text because of state changes, so I guess this would be doable for now like this.