Skip to content

Commit 7edaec4

Browse files
authored
Add Component documentation (#4429)
* Add components documentation * switch to light theme * undo workflow change * remove direct boot mode reference
1 parent 00ea7ee commit 7edaec4

File tree

8 files changed

+206
-15
lines changed

8 files changed

+206
-15
lines changed

contributor-docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ It describes how Firebase works on Android and provides guidance on how to build
77

88
- [Development Environment Setup]({{ site.baseurl }}{% link onboarding/env_setup.md %})
99
- [Creating a new SDK]({{ site.baseurl }}{% link onboarding/new_sdk.md %})
10-
- [How Firebase works]({{ site.baseurl }}{% link how_firebase_works.md %})
10+
- [How Firebase works]({{ site.baseurl }}{% link how_firebase_works.md %})

contributor-docs/_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ remote_theme: just-the-docs/[email protected]
66
plugins:
77
- jekyll-remote-theme
88

9-
color_scheme: dark
9+
color_scheme: light
1010

1111
# Aux links for the upper right navigation
1212
aux_links:
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
has_children: true
3+
---
4+
5+
# Firebase Components
6+
7+
Firebase is known for being easy to use and requiring no/minimal configuration at runtime.
8+
Just adding SDKs to the app makes them discover each other to provide additional functionality,
9+
e.g. `Firestore` automatically integrates with `Auth` if present in the app.
10+
11+
* Firebase SDKs have required and optional dependencies on other Firebase SDKs
12+
* SDKs have different initialization requirements, e.g. `Analytics` and `Crashlytics` must be
13+
initialized upon application startup, while some are initialized on demand only.
14+
15+
To accommodate these requirements Firebase uses a component model that discovers SDKs present in the app,
16+
determines their dependencies and provides them to dependent SDKs via a `Dependency Injection` mechanism.
17+
18+
This page describes the aforementioned Component Model, how it works and why it's needed.
19+
20+
## Design Considerations
21+
22+
### Transparent/invisible to 3p Developers
23+
24+
To provide good developer experience, we don't want developers to think about how SDKs work and interoperate internally.
25+
Instead we want our SDKs to have a simple API surface that hides all of the internal details.
26+
Most products have an API surface that allows developers to get aninstance of a given SDK via `FirebaseFoo.getInstance()`
27+
and start using it right away.
28+
29+
### Simple to use and integrate with for component developers
30+
31+
* The component model is lightweight in terms of integration effort. It is not opinionated on how components are structured.
32+
* The component model should require as little cooperation from components runtime as possible.
33+
* It provides component developers with an API that is easy to use correctly, and hard to use incorrectly.
34+
* Does not sacrifice testability of individual components in isolation
35+
36+
### Performant at startup and initialization
37+
38+
The runtime does as little work as possible during initialization.
39+
40+
## What is a Component?
41+
42+
A Firebase Component is an entity that:
43+
44+
* Implements one or more interfaces
45+
* Has a list of dependencies(required or optional). See [Dependencies]({{ site.baseurl }}{% link components/dependencies.md %})
46+
* Has initialization requirements(e.g. eager in default app)
47+
* Defines a factory creates an instance of the component’s interface given it's dependencies.
48+
(In other words describes how to create the given component.)
49+
50+
Example:
51+
52+
```java
53+
// Defines a component that is registered as both `FirebaseAuth` and `InternalAuthProvider`.
54+
Component<FirebaseAuth> auth = Component.builder(FirebaseAuth.class, InternalAuthProvider.class)
55+
// Declares dependencies
56+
.add(Dependency.required(FirebaseOptions.class))
57+
// Defines a factory
58+
.factory(container -> new FirebaseAuth(container.get(FirebaseOptions.class)))
59+
.eagerInDefaultApp() // alwaysEager() or lazy(), lazy is the default.
60+
.build()
61+
```
62+
63+
All components are singletons within a Component Container(e.g. one instance per FirebaseApp).
64+
There are however SDKs that need the ability to expose multiple objects per FirebaseApp,
65+
for example RTBD(as well as Storage and Firestore) has multidb support which allows developers
66+
to access one or more databases within one FirebaseApp. To address this requirement,
67+
SDKs have to register their components in the following form(or similar):
68+
69+
```java
70+
// This is the singleton holder of different instances of FirebaseDatabase.
71+
interface RtdbComponent {
72+
FirebaseDatabase getDefault();
73+
FirebaseDatabase get(String databaseName);
74+
}
75+
```
76+
77+
As you can see in the previous section, components are just values and don't have any behavior per se,
78+
essentially they are just blueprints of how to create them and what dependencies they need.
79+
80+
So there needs to be some ComponentRuntime that can discover and wire them together into a dependency graph,
81+
in order to do that, there needs to be an agreed upon location where SDKs can register the components they provide.
82+
83+
The next 2 sections describe how it's done.
84+
85+
## Component Registration
86+
87+
In order to define the `Components` an SDK provides, it needs to define a class that implements `ComponentRegistrar`,
88+
this class contains all component definitions the SDK wants to register with the runtime:
89+
90+
```java
91+
public class MyRegistrar implements ComponentRegistrar {
92+
/// Returns a one or more Components that will be registered in
93+
/// FirebaseApp and participate in dependency resolution and injection.
94+
@Override
95+
public Collection<FirebaseComponent<?>> getComponents() {
96+
Arrays.asList(Component.builder(MyType.class)
97+
/* ... */
98+
.build());
99+
}
100+
}
101+
```
102+
103+
## Component Discovery
104+
105+
In addition to creating the `ComponentRegistrar` class, SDKs also need to add them to their `AndroidManifest.xml` under `ComponentDiscoveryService`:
106+
107+
```xml
108+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
109+
<application>
110+
<service android:name="com.google.firebase.components.ComponentDiscoveryService"
111+
android:exported="false">
112+
<meta-data
113+
android:name="com.google.firebase.components:com.google.firebase.foo.FirebaseFooRegistrar"
114+
android:value="com.google.firebase.components.ComponentRegistrar" />
115+
</service>
116+
</application>
117+
</manifest>
118+
```
119+
120+
When the final app is built, manifest registrar entries will all end up inside the above `service` as metadata key- value pairs.
121+
At this point `FirebaseApp` will instantiate them and use the `ComponentRuntime` to construct the component graph.
122+
123+
## Dependency resolution and initialization
124+
125+
### Definitions and constraints
126+
127+
* **Component A depends on Component B** if `B` depends on an `interface` that `A` implements.
128+
* **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
130+
fail to start at runtime.
131+
* **There must not be any dependency cycles** among components. See Dependency Cycle Resolution on how this limitation can
132+
be mitigated
133+
* **Components are initialized lazily by default**(unless a component is declared eager) and are initialized when requested
134+
by an application either directly or transitively.
135+
136+
The initialization phase of the FirebaseApp will consist of the following steps:
137+
138+
1. Get a list of available FirebaseComponents that were discovered by the Discovery mechanism
139+
2. Topologically sort components based on their declared dependencies - failing if a dependency cycle is detected or multiple implementations are registered for any interface.
140+
3. Store a map of {iface -> ComponentFactory} so that components can be instantiated on demand(Note that component instantiation does not yet happen)
141+
4. Initialize EAGER components or schedule them to initialize on device unlock, if in direct boot mode.
142+
143+
Below is an example illustration of the state of the component graph after initialization:
144+
145+
```mermaid
146+
flowchart TD
147+
Analytics --> Installations
148+
Auth --> Context
149+
Auth --> FirebaseOptions
150+
Context[android.os.Context]
151+
Crashlytics --> Installations
152+
Crashlytics --> FirebaseApp
153+
Crashlytics --> FirebaseOptions
154+
Crashlytics -.-> Analytics
155+
Crashlytics --> Context
156+
Database -.-> Auth
157+
Database --> Context
158+
Database --> FirebaseApp
159+
Database --> FirebaseOptions
160+
Firestore -.-> Auth
161+
Messaging --> Installations
162+
Messaging --> FirebaseOptions
163+
Messaging --> Context
164+
RemoteConfig --> FirebaseApp
165+
RemoteConfig --> Context
166+
RemoteConfig --> Installations
167+
168+
169+
classDef eager fill:#4db66e,stroke:#4db6ac,color:#000;
170+
classDef transitive fill:#4db6ac,stroke:#4db6ac,color:#000;
171+
classDef always fill:#1a73e8,stroke:#7baaf7,color:#fff;
172+
173+
class Analytics eager
174+
class Crashlytics eager
175+
class Context always
176+
class FirebaseOptions always
177+
class FirebaseApp always
178+
class Installations transitive
179+
```
180+
181+
There are **2 explicitly eager** components in this example: `Crashlytics` and `Analytics`.
182+
These components are initialized when `FirebaseApp` is initialized. `Installations` is initialized eagerly because
183+
eager components depends on it(see Prefer Lazy dependencies to avoid this as mush as possible).
184+
`FirebaseApp`, `FirebaseOptions` and `Android Context` are always present in the Component Container and are considered initialized as well.
185+
186+
*The rest of the components are left uninitialized and will remain so until the client application requests them or an eager
187+
component initializes them by using a Lazy dependency.*
188+
For example, if the application calls `FirebaseDatabase.getInstance()`, the container will initialize `Auth` and `Database`
189+
and will return `Database` to the user.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
parent: Firebase Components
3+
---
4+
5+
# Dependencies
6+
7+
TODO
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
parent: Firebase Components
3+
---
4+
5+
# Multibindings
6+
7+
TODO

contributor-docs/how_firebase_works.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@ public class MyRegistrar implements ComponentRegistrar {
7878

7979
This registrar is then registered in `AndroidManifest.xml` of the SDK and is used by `FirebaseApp` to discover all components and construct the dependency graph.
8080

81-
More details in [Firebase Components]({{ site.baseurl }}{% link in_depth/components.md %})).
81+
More details in [Firebase Components]({{ site.baseurl }}{% link components/components.md %})).

contributor-docs/in_depth/components.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

contributor-docs/in_depth/in_depth.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)