Hello, is it possible to do function param injecti...
# compose
a
Hello, is it possible to do function param injection on a Composable using Hilt?
For example, If I have a composable deep in the graph that requires a dependency that has been provided by a module, is it possible to simply annotate the function to tell Hilt to inject the dependency for me? Or do I need to go to the very top of the graph, inject there, and pass the dependency all the way down the graph until finally the component that needs it has it?
Copy code
class MyActivity: ComponentActivity {
    @Inject
    lateinit var myDependency: Dependency

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        setContent {
            TopLevel()
        }
    }

}

@Composable
fun TopLevel() {
    CompA() {
        CompB() {
            CompC() {
                CompE() {
                    CompF() {
                        CompG(myDependency) // what's the best way to get the dependency down here?
                    }
                }
            }
        }
    }
}
if my CompG dependency is a ViewModel, then that’s all fine and dandy as I could do something like:
Copy code
@Composable
fun CompG(viewModel: MyViewModel = hiltViewModel()){}
But if it wasn’t a ViewModel and an ordinary dependency, could I do something similar and specify a default value on the param:
Copy code
@Composable
fun CompG(myDependency: Dependency = get(), viewModel: MyViewModel = hiltViewModel()) {}
r
Copy code
CompositionLocalProvider(LocalDependency provides myDependency) {
                    TopLevel()
            }
u can use CompositionLocalProvider
Copy code
val LocalDependency = staticCompositionLocalOf<GenericState> { error("Dependency not provided") }
In CompG
Copy code
CompG() { val myDependency = LocalDependency.current }
a
Thank you for the suggestion, I'll give this a go
r
Try to use hiltViewModel inside
CompG
cause if your
Dependency
is not required anywhere else, it will clear out memory when
CompG
gets disposed.
CompositionLocalProvider
is used to avoid passing information through a lot of composable functions
c
a
Do you have a suggestion on how to pass dependencies deep to a Composable? Or is the sentiment that having to do that breaks modularization of the composable?
c
It probably depends on the Type of dependency. For example, if you want to pass some kind of data or something ,then you likely just want to keep passing that down, but something like Colors then you might be good enough to go with a CompositionLocal. What's your use case? Maybe @jim can chime in once you give your example?
j
@Andrew Leung It breaks modularization of the composable. Just pass it as a parameter:
Copy code
class MyActivity: ComponentActivity {
    @Inject
    lateinit var myDependency: Dependency

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        setContent {
            TopLevel(myDependency)
        }
    }

}

@Composable
fun TopLevel(myDependency: Dependency) {
    CompA() {
        CompB() {
            CompC() {
                CompE() {
                    CompF() {
                        CompG(myDependency) // dependency now available here.
                    }
                }
            }
        }
    }
}
😍 1
a
Right, my example isn't great because it assumes that G is visible to TopLevel. If A composed B, down to G, it means the top level needs to pass the param from component to component, even when perhaps A to F didn't need the dependency.
And since injection happens at the activity level, and if I had a single activity with a large navigation graph, the one activity would need to know all of the screens dependencies, inject it there, and pass all the dependencies down to every screen component's children that need it.
c
You can inject dependencies into Composable "screens"
a
I'd say the react context is analogous to the LocalProvider
c
Yeah, context ~= ComposotionLocal AFAIK
j
@Andrew Leung I actually think your first example was pretty good. People often assume (or complain without trying) that data needs to be passed from layer to layer to layer, but it turns out to be less common than you might expect. Because of the way children lambdas work, the places you need to read data from will happen to commonly be in the same lexical scope. If you find yourself plumbing data deeply, it's usually an indication that you are writing bespoke widgets instead of general purpose widgets which accept children lambdas. Passing data explicitly will implicitly nudge you into a better design that takes advantage of children lambdas for the important bits which allows you to take advantage of lexical scoping.