Skip to content

Commit e05db82

Browse files
vkryachkorlazo
andauthored
Added more docs (#4437)
* Describe dependencies * deploy * fix typo * fixes * updates * toc * fix * updates * configure callouts * undo deploy from branch * Apply suggestions from code review Co-authored-by: Rodrigo Lazo <rlazo@users.noreply.github.com> Co-authored-by: Rodrigo Lazo <rlazo@users.noreply.github.com>
1 parent fa88eee commit e05db82

File tree

10 files changed

+403
-173
lines changed

10 files changed

+403
-173
lines changed

contributor-docs/_config.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ mermaid:
4949
# Enable or disable heading anchors
5050
heading_anchors: true
5151

52+
callouts_level: quiet
53+
callouts:
54+
highlight:
55+
color: yellow
56+
important:
57+
title: Important
58+
color: blue
59+
new:
60+
title: New
61+
color: green
62+
note:
63+
title: Note
64+
color: purple
65+
warning:
66+
title: Warning
67+
color: red
68+
5269
# Back to top link
5370
back_to_top: true
5471
back_to_top_text: "Back to top"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
has_children: true
3+
---
4+
5+
# Best Practices

contributor-docs/components/components.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
---
22
has_children: true
3+
permalink: /components
34
---
45

56
# Firebase Components
7+
{: .no_toc}
8+
9+
1. TOC
10+
{:toc}
611

712
Firebase is known for being easy to use and requiring no/minimal configuration at runtime.
813
Just adding SDKs to the app makes them discover each other to provide additional functionality,
@@ -126,7 +131,7 @@ At this point `FirebaseApp` will instantiate them and use the `ComponentRuntime`
126131

127132
* **Component A depends on Component B** if `B` depends on an `interface` that `A` implements.
128133
* **For any Interface I, only one component is allowed to implement I**(with the exception of
129-
[Multibindings]({{ site.baseurl }}{% link components/multibindings.md %})). If this invariant is violated, the container will
134+
[Set Dependencies]({{ site.baseurl }}{% link components/dependencies.md %}#set-dependencies)). If this invariant is violated, the container will
130135
fail to start at runtime.
131136
* **There must not be any dependency cycles** among components. See Dependency Cycle Resolution on how this limitation can
132137
be mitigated

contributor-docs/components/dependencies.md

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,186 @@ parent: Firebase Components
33
---
44

55
# Dependencies
6+
{: .no_toc}
67

7-
TODO
8+
1. TOC
9+
{:toc}
10+
11+
This page gives an overview of the different dependency types supported by the Components Framework.
12+
13+
## Background
14+
15+
As discussed in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), in order
16+
for a `Component` to be injected with the things it needs to function, it has to declare its dependencies.
17+
These dependencies are then made available and injected into `Components` at runtime.
18+
19+
Firebase Components provide different types of dependencies.
20+
21+
## Lazy vs Eager dependencies
22+
23+
When it comes to initialize a component, there are 2 ways of provide its dependencies.
24+
25+
### Direct Injection
26+
27+
With this type of injection, the component gets an instance of its dependency directly.
28+
29+
```kotlin
30+
class MyComponent(private val dep : MyDep) {
31+
fun someMethod() {
32+
dep.use();
33+
}
34+
}
35+
```
36+
37+
As you can see above the component's dependency is passed by value directly,
38+
which means that the dependency needs to be fully initialized before
39+
it's handed off to the requesting component. As a result `MyComponent` may have to pay the cost
40+
of initializing `MyDep` just to be created.
41+
42+
### Lazy/Provider Injection
43+
44+
With this type of injection, instead of getting an instance of the dependency directly, the dependency
45+
is passed into the `Component` with the help of a `com.google.firebase.inject.Provider`
46+
47+
```java
48+
public interface Provider<T> { T get(); }
49+
```
50+
51+
```kotlin
52+
class MyComponent(private val dep : Provider<MyDep>) {
53+
fun someMethod() {
54+
// Since all components are singletons, each call to
55+
// get() will return the same instance.
56+
dep.get().use();
57+
}
58+
}
59+
```
60+
61+
On the surface this does not look like a big change, but it has an important side effect. In order to create
62+
an instance of `MyComponent`, we don't need to initialize `MyDep` anymore. Instead, initialization can be
63+
delayed until `MyDep` is actually used.
64+
65+
It is also benefitial to use a `Provider` in the context of [Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery).
66+
See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for more details.
67+
68+
## Required dependencies
69+
70+
This type of dependency informs the `ComponentRuntime` that a given `Component` cannot function without a dependency.
71+
When the dependency is missing during initialization, `ComponentRuntime` will throw a `MissingDependencyException`.
72+
This type of dependency is useful for built-in components that are always present like `Context`, `FirebaseApp`,
73+
`FirebaseOptions`, [Executors]({{ site.baseurl }}{% link components/executors.md %}).
74+
75+
To declare a required dependency use one of the following in your `ComponentRegistrar`:
76+
77+
```java
78+
// Required directly injected dependency
79+
.add(Dependency.required(MyDep.class))
80+
// Required lazily injected dependency
81+
.add(Dependency.requiredProvider(MyOtherDep.class))
82+
.factory( c -> new MyComponent(c.get(MyDep.class), c.getProvider(MyOtherDep.class)))
83+
.build();
84+
```
85+
86+
## Optional Dependencies
87+
88+
This type of dependencies is useful when your `Component` can operate normally when the dependency is not
89+
available, but can have enhanced functionality when present. e.g. `Firestore` can work without `Auth` but
90+
provides secure database access when `Auth` is present.
91+
92+
To declare an optional dependency use the following in your `ComponentRegistrar`:
93+
94+
```java
95+
.add(Dependency.optionalProvider(MyDep.class))
96+
.factory(c -> new MyComponent(c.getProvider(MyDep.class)))
97+
.build();
98+
```
99+
100+
The provider will return `null` if the dependency is not present in the app.
101+
102+
{: .warning }
103+
When the app uses [Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery),
104+
`provider.get()` will return your dependency when it becomes available. To support this use case, don't store references to the result of `provider.get()` calls.
105+
106+
See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for details
107+
108+
{: .warning }
109+
See Deferred dependencies if you your dependency has a callback based API
110+
111+
## Deferred Dependencies
112+
113+
Useful for optional dependencies which have a listener-style API, i.e. the dependent component registers a
114+
listener with the dependency and never calls it again (instead the dependency will call the registered listener).
115+
A good example is `Firestore`'s use of `Auth`, where `Firestore` registers a token change listener to get
116+
notified when a new token is available. The problem is that when `Firestore` initializes, `Auth` may not be
117+
present in the app, and is instead part of a dynamic module that can be loaded at runtime on demand.
118+
119+
To solve this problem, Components have a notion of a `Deferred` dependency. A deferred is defined as follows:
120+
121+
```java
122+
public interface Deferred<T> {
123+
interface DeferredHandler<T> {
124+
@DeferredApi
125+
void handle(Provider<T> provider);
126+
}
127+
128+
void whenAvailable(DeferredHandler<T> handler);
129+
}
130+
```
131+
132+
To use it a component needs to call `Dependency.deferred(SomeType.class)`:
133+
134+
```kotlin
135+
class MyComponent(deferred: Deferred<SomeType>) {
136+
init {
137+
deferred.whenAvailable { someType ->
138+
someType.registerListener(myListener)
139+
}
140+
}
141+
}
142+
```
143+
144+
See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for details
145+
146+
## Set Dependencies
147+
148+
The Components Framework allows registering components to be part of a set, such components are registered explicitly to be a part of a `Set<T>` as opposed to be a unique value of `T`:
149+
150+
```java
151+
// Sdk 1
152+
Component.intoSet(new SomeTypeImpl(), SomeType.class);
153+
// Sdk 2
154+
Component.intoSetBuilder(SomeType.class)
155+
.add(Dependency(SomeDep.class))
156+
.factory(c -> new SomeOtherImpl(c.get(SomeDep.class)))
157+
.build();
158+
```
159+
160+
With the above setup each SDK contributes a value of `SomeType` into a `Set<SomeType>` which becomes available as a
161+
`Set` dependency.
162+
163+
To consume such a set the interested `Component` needs to declare a special kind of dependency in one of 2 ways:
164+
165+
* `Dependency.setOf(SomeType.class)`, a dependency of type `Set<SomeType>`.
166+
* `Dependency.setOfProvider(SomeType.class)`, a dependency of type `Provider<Set<SomeType>>`. The advantage of this
167+
is that the `Set` is not initialized until the first call to `provider.get()` at which point all elements of the
168+
set will get initialized.
169+
170+
{: .warning }
171+
Similar to optional `Provider` dependencies, where an optional dependency can become available at runtime due to
172+
[Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery),
173+
`Set` dependencies can change at runtime by new elements getting added to the set.
174+
So make sure to hold on to the original `Set` to be able to observe new values in it as they are added.
175+
176+
Example:
177+
178+
```kotlin
179+
class MyClass(private val set1: Set<SomeType>, private val set2: Provider<Set<SomeOtherType>>)
180+
```
181+
182+
```java
183+
Component.builder(MyClass.class)
184+
.add(Dependency.setOf(SomeType.class))
185+
.add(Dependency.setOfProvider(SomeOtherType.class))
186+
.factory(c -> MyClass(c.setOf(SomeType.class), c.setOfProvider(SomeOtherType.class)))
187+
.build();
188+
```

contributor-docs/components/multibindings.md renamed to contributor-docs/components/dynamic_modules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
parent: Firebase Components
33
---
44

5-
# Multibindings
5+
# Dynamic Module Support
66

77
TODO

0 commit comments

Comments
 (0)