Are `Providers`  and `Ambients` like a Dagger inject where I can get a dependency without passing it...
l
Are 
Providers
  and 
Ambients
 like a Dagger inject where I can get a dependency without passing it as a param anywhere? Is there a best practice on their use? https://developer.android.com/reference/kotlin/androidx/compose/runtime/Ambient
j
They are not like Dagger, where dependencies are declared in public API. They are like a service locator, where the dependencies are implementation detail. They're similar to
Context.getSystemService
.
l
But it seems like I can use them to pass any custom data class
Sort of like a Dagger singleton inject
at least based on the
ActiveUser
example in the doc
j
No, in Dagger the dependency would be exposed on the public API and thus validated that it was supplied in order to create the thing. In Compose nothing validates that ambients are provided and you can and will get runtime failures when they are missing. It's a service locator, not a dependency injector.
10
l
ok thanks
r
Hello @jw what is the best practice for injecting in compose project?
a
Well, you get the default value for that ambient if nothing above provided it. It's kind of bad form to declare the default value as
{ error("missing foo") }
but you can if you really want to
Otherwise, yes exactly 🙂
j
Calling
Context.getSystemService(VIBRATOR)
will crash on devices without a vibrator motor if you're not careful! But the last time I remember seeing that was like 2011... Seems like your only choice is an explicit argument or the ambient system, although the Compose i've done is not Compose UI and I have never used ambients so my advice is likely not that useful.
r
When we choose explicit argument there may be a lot of function params as hierarchy tree is very deep in bigger projects
In my opinion there will be parent composable components where lifecycle of dependencies are managed, injected and destroyed and also child composable components with arguments where rendering is done
k
Context.getSystemService
is really a global object that can't be "redefined". Ambients in Compose can be redefined (or provided) at any level in the hierarchy, and that applies to the entire subtree unless some subchild redefines it again.
So it's a bit more powerful. But don't abuse it for passing app data around your composables.
l
What would you use it for? Examples would be appreciated. The example in the doc looks like just passing app data.
k
At the global level, there are ambients for density, ltr/rtl and other global values
But you shouldn't use ambients to hold on to application data. That should be passed explicitly to your composables
1
🙏 1
a
theming information and
Context.getSystemService
-type things are what we tend to use them for in the stock compose libraries
👍 1
k
On the other hand, a concept of "accent color" in Material would be too invasive, if you will, to pass everywhere you want to use it. So as part of theming, it is set and queried in an ambient.
a
Ambients behave very similarly to react's Context; the advice here is nearly all applicable: https://reactjs.org/docs/context.html
4
in particular, the advice to use higher-order components (or in compose's case, composable functions) often removes the pressure to use something like ambients
when more traditional DI comes into play, injecting the objects that you pass to composable functions as normal parameters gets you pretty far
multiple receivers for extension functions is also a direction to watch in this same space if you're looking for DI-like compile-time validation: https://youtrack.jetbrains.com/issue/KT-10468
r
Hi @Adam Powell, So anyway in the middle of component hierarchy tree you will have to pass new dependencies as you need. For this situation we will have to create instances and pass it to child components and destroy on dispose. Am I wrong?
a
managing object lifetimes is yet another layer to the question; generally it's a good idea to keep the same code that creates an object responsible for ensuring it's disposed properly if needed
👍 1
DisposableEffect
is one way to initialize/tear down an object that needs it; we also still need to add some sort of
rememberDisposable
that does it in one go with creating something in a
remember
expression. There are some edge cases where
DisposableEffect
won't clean up an object created in a
remember
expression if the composition pass where the object was created fails
👍 1
v
@Sean McQuillan [G] Tries to answer this exact question (role of dependency injection in a Compose world) here - https://www.droidcon.com/media-detail?video=481211040 Go to 21:28 in the video.
👍🏽 2
j
This thread is long, but we absolutely use Context.getSystemService like ambients today. Layers of our app wrap the context and shadow available types. We use it to propagate theme information, for example.
We've been doing this for many many years now for things which are always available in the view tree such as location information, the navigator, and theme info. As far as I can tell, ambients are exactly the same with sole difference being they're effectively a stream which triggers recomposition on value change whereas system service require polling for changes.
👍 8
c
@Lauren Yew good question and nice to hear the answers above. Thanks for asking it!
👍 1