Skip to content

Commit 0a5b6ba

Browse files
authored
Add Dynamic loading support to fiam. (#2535)
* Add Dynamic loading support to fiam. The change adds Analytics load detection. * Fix lint error. * Downgrade mockito back. * Address review comments.
1 parent 8c69256 commit 0a5b6ba

File tree

8 files changed

+343
-101
lines changed

8 files changed

+343
-101
lines changed

firebase-inappmessaging/firebase-inappmessaging.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ dependencies {
130130
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
131131
implementation 'com.google.auto.value:auto-value-annotations:1.6.6'
132132

133-
implementation('com.google.firebase:firebase-measurement-connector:18.0.0') {
133+
implementation('com.google.firebase:firebase-measurement-connector:18.0.2') {
134134
exclude group: 'com.google.firebase', module: 'firebase-common'
135135
}
136136

@@ -144,8 +144,9 @@ dependencies {
144144

145145
testImplementation 'org.mockito:mockito-core:1.10.19'
146146
testImplementation "com.google.truth:truth:$googleTruthVersion"
147-
testImplementation 'junit:junit:4.12'
148-
testImplementation 'androidx.test:runner:1.2.0'
147+
testImplementation 'junit:junit:4.13.1'
148+
testImplementation 'androidx.test:runner:1.3.0'
149+
testImplementation 'androidx.test.ext:junit:1.1.2'
149150
testImplementation ("org.robolectric:robolectric:$robolectricVersion") {
150151
exclude group: 'com.google.protobuf', module: 'protobuf-java'
151152
}

firebase-inappmessaging/src/androidTest/java/com/google/firebase/inappmessaging/FirebaseInAppMessagingFlowableTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ public void setUp() {
265265
.testForegroundFlowableModule(new TestForegroundFlowableModule(foregroundNotifier))
266266
.applicationModule(new ApplicationModule(application))
267267
.appMeasurementModule(
268-
new AppMeasurementModule(analyticsConnector, firebaseEventSubscriber))
268+
new AppMeasurementModule(
269+
p -> p.handle(() -> analyticsConnector), firebaseEventSubscriber))
269270
.testSystemClockModule(new TestSystemClockModule(NOW))
270271
.programmaticContextualTriggerFlowableModule(
271272
new ProgrammaticContextualTriggerFlowableModule(
@@ -313,7 +314,7 @@ public void onAnalyticsNotification_notifiesSubscriber() {
313314
public void onAppOpen_whenAnalyticsAbsent_notifiesSubscriber() {
314315
TestUniversalComponent analyticsLessUniversalComponent =
315316
universalComponentBuilder
316-
.appMeasurementModule(new AppMeasurementModule(null, firebaseEventSubscriber))
317+
.appMeasurementModule(new AppMeasurementModule(handler -> {}, firebaseEventSubscriber))
317318
.build();
318319
TestAppComponent appComponent =
319320
appComponentBuilder.universalComponent(analyticsLessUniversalComponent).build();

firebase-inappmessaging/src/main/java/com/google/firebase/inappmessaging/FirebaseInAppMessagingRegistrar.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.firebase.inappmessaging.internal.injection.modules.ApplicationModule;
4040
import com.google.firebase.inappmessaging.internal.injection.modules.GrpcClientModule;
4141
import com.google.firebase.inappmessaging.internal.injection.modules.ProgrammaticContextualTriggerFlowableModule;
42+
import com.google.firebase.inject.Deferred;
4243
import com.google.firebase.installations.FirebaseInstallationsApi;
4344
import com.google.firebase.platforminfo.LibraryVersionComponent;
4445
import java.util.Arrays;
@@ -60,7 +61,7 @@ public List<Component<?>> getComponents() {
6061
.add(Dependency.required(FirebaseInstallationsApi.class))
6162
.add(Dependency.required(FirebaseApp.class))
6263
.add(Dependency.required(AbtComponent.class))
63-
.add(Dependency.optional(AnalyticsConnector.class))
64+
.add(Dependency.deferred(AnalyticsConnector.class))
6465
.add(Dependency.required(TransportFactory.class))
6566
.add(Dependency.required(Subscriber.class))
6667
.factory(this::providesFirebaseInAppMessaging)
@@ -72,7 +73,8 @@ public List<Component<?>> getComponents() {
7273
private FirebaseInAppMessaging providesFirebaseInAppMessaging(ComponentContainer container) {
7374
FirebaseApp firebaseApp = container.get(FirebaseApp.class);
7475
FirebaseInstallationsApi firebaseInstallations = container.get(FirebaseInstallationsApi.class);
75-
AnalyticsConnector analyticsConnector = container.get(AnalyticsConnector.class);
76+
Deferred<AnalyticsConnector> analyticsConnector =
77+
container.getDeferred(AnalyticsConnector.class);
7678
Subscriber firebaseEventsSubscriber = container.get(Subscriber.class);
7779

7880
Application application = (Application) firebaseApp.getApplicationContext();

firebase-inappmessaging/src/main/java/com/google/firebase/inappmessaging/internal/AnalyticsEventsManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.inappmessaging.internal;
1616

17+
import android.annotation.SuppressLint;
1718
import android.text.TextUtils;
1819
import com.google.common.annotations.VisibleForTesting;
1920
import com.google.firebase.analytics.connector.AnalyticsConnector;
@@ -97,6 +98,8 @@ private class AnalyticsFlowableSubscriber implements FlowableOnSubscribe<String>
9798
AnalyticsFlowableSubscriber() {}
9899

99100
@Override
101+
// fiam uses an AnalyticsConnector proxy that is Deferred-aware so it's safe to suppress.
102+
@SuppressLint("InvalidDeferredApiUse")
100103
public void subscribe(FlowableEmitter<String> emitter) {
101104
Logging.logd("Subscribing to analytics events.");
102105
handle =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.inappmessaging.internal;
16+
17+
import android.os.Bundle;
18+
import androidx.annotation.GuardedBy;
19+
import androidx.annotation.NonNull;
20+
import androidx.annotation.Nullable;
21+
import com.google.firebase.analytics.connector.AnalyticsConnector;
22+
import com.google.firebase.inject.Deferred;
23+
import java.util.Collections;
24+
import java.util.HashSet;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Set;
28+
29+
/**
30+
* Proxy connector that delegates to analytics when it's available.
31+
*
32+
* <p>For a subset of functionality it also caches calls and propagates them to analytics once it
33+
* loads.
34+
*/
35+
public class ProxyAnalyticsConnector implements AnalyticsConnector {
36+
private volatile Object instance;
37+
38+
public ProxyAnalyticsConnector(Deferred<AnalyticsConnector> analyticsConnector) {
39+
instance = analyticsConnector;
40+
analyticsConnector.whenAvailable(connectorProvider -> instance = connectorProvider.get());
41+
}
42+
43+
private AnalyticsConnector safeGet() {
44+
Object result = instance;
45+
if (result instanceof AnalyticsConnector) {
46+
return (AnalyticsConnector) result;
47+
}
48+
return null;
49+
}
50+
51+
@Override
52+
public void logEvent(@NonNull String s, @NonNull String s1, @NonNull Bundle bundle) {
53+
AnalyticsConnector connector = safeGet();
54+
if (connector != null) {
55+
connector.logEvent(s, s1, bundle);
56+
}
57+
}
58+
59+
@Override
60+
public void setUserProperty(@NonNull String s, @NonNull String s1, @NonNull Object o) {
61+
AnalyticsConnector connector = safeGet();
62+
if (connector != null) {
63+
connector.setUserProperty(s, s1, o);
64+
}
65+
}
66+
67+
// Not implemented since it's not used by fiam
68+
@NonNull
69+
@Override
70+
public Map<String, Object> getUserProperties(boolean b) {
71+
return Collections.emptyMap();
72+
}
73+
74+
@NonNull
75+
@Override
76+
public AnalyticsConnectorHandle registerAnalyticsConnectorListener(
77+
@NonNull String s, @NonNull AnalyticsConnectorListener analyticsConnectorListener) {
78+
Object result = instance;
79+
if (result instanceof AnalyticsConnector) {
80+
return ((AnalyticsConnector) result)
81+
.registerAnalyticsConnectorListener(s, analyticsConnectorListener);
82+
}
83+
@SuppressWarnings("unchecked")
84+
Deferred<AnalyticsConnector> deferred = (Deferred<AnalyticsConnector>) result;
85+
return new ProxyAnalyticsConnectorHandle(s, analyticsConnectorListener, deferred);
86+
}
87+
88+
// Not implemented since it's not used by fiam.
89+
@Override
90+
public void setConditionalUserProperty(
91+
@NonNull ConditionalUserProperty conditionalUserProperty) {}
92+
93+
// Not implemented since it's not used by fiam.
94+
@Override
95+
public void clearConditionalUserProperty(
96+
@NonNull String s, @Nullable String s1, @Nullable Bundle bundle) {}
97+
98+
// Not implemented since it's not used by fiam.
99+
@NonNull
100+
@Override
101+
public List<ConditionalUserProperty> getConditionalUserProperties(
102+
@NonNull String s, @Nullable String s1) {
103+
return Collections.emptyList();
104+
}
105+
106+
// Not implemented since it's not used by fiam.
107+
@Override
108+
public int getMaxUserProperties(@NonNull String s) {
109+
return 0;
110+
}
111+
112+
private static class ProxyAnalyticsConnectorHandle implements AnalyticsConnectorHandle {
113+
private static final Object UNREGISTERED = new Object();
114+
115+
@GuardedBy("this")
116+
private Set<String> eventNames = new HashSet<>();
117+
118+
private volatile Object instance;
119+
120+
private ProxyAnalyticsConnectorHandle(
121+
String s,
122+
AnalyticsConnectorListener listener,
123+
Deferred<AnalyticsConnector> analyticsConnector) {
124+
analyticsConnector.whenAvailable(
125+
connectorProvider -> {
126+
Object result = instance;
127+
if (result == UNREGISTERED) {
128+
return;
129+
}
130+
AnalyticsConnector connector = connectorProvider.get();
131+
// Now that analytics is available:
132+
133+
// register the listener with analytics.
134+
AnalyticsConnectorHandle handle =
135+
connector.registerAnalyticsConnectorListener(s, listener);
136+
instance = handle;
137+
138+
// propagate registered event names to analytics.
139+
synchronized (ProxyAnalyticsConnectorHandle.this) {
140+
if (!eventNames.isEmpty()) {
141+
handle.registerEventNames(eventNames);
142+
eventNames = new HashSet<>();
143+
}
144+
}
145+
});
146+
}
147+
148+
@Override
149+
public void unregister() {
150+
Object result = instance;
151+
if (result == UNREGISTERED) {
152+
return;
153+
}
154+
155+
if (result != null) {
156+
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
157+
handle.unregister();
158+
}
159+
instance = UNREGISTERED;
160+
synchronized (this) {
161+
eventNames.clear();
162+
}
163+
}
164+
165+
@Override
166+
public void registerEventNames(@NonNull Set<String> set) {
167+
Object result = instance;
168+
if (result == UNREGISTERED) {
169+
return;
170+
}
171+
172+
if (result != null) {
173+
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
174+
handle.registerEventNames(set);
175+
return;
176+
}
177+
synchronized (this) {
178+
eventNames.addAll(set);
179+
}
180+
}
181+
182+
@Override
183+
public void unregisterEventNames() {
184+
Object result = instance;
185+
if (result == UNREGISTERED) {
186+
return;
187+
}
188+
if (result != null) {
189+
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
190+
handle.unregisterEventNames();
191+
return;
192+
}
193+
synchronized (this) {
194+
eventNames.clear();
195+
}
196+
}
197+
}
198+
}

firebase-inappmessaging/src/main/java/com/google/firebase/inappmessaging/internal/StubAnalyticsConnector.java

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

firebase-inappmessaging/src/main/java/com/google/firebase/inappmessaging/internal/injection/modules/AppMeasurementModule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616

1717
import com.google.firebase.analytics.connector.AnalyticsConnector;
1818
import com.google.firebase.events.Subscriber;
19-
import com.google.firebase.inappmessaging.internal.StubAnalyticsConnector;
19+
import com.google.firebase.inappmessaging.internal.ProxyAnalyticsConnector;
20+
import com.google.firebase.inject.Deferred;
2021
import dagger.Module;
2122
import dagger.Provides;
2223
import javax.inject.Singleton;
@@ -29,13 +30,12 @@
2930
@Module
3031
public class AppMeasurementModule {
3132

32-
private AnalyticsConnector analyticsConnector;
33-
private Subscriber firebaseEventsSubscriber;
33+
private final AnalyticsConnector analyticsConnector;
34+
private final Subscriber firebaseEventsSubscriber;
3435

3536
public AppMeasurementModule(
36-
AnalyticsConnector analyticsConnector, Subscriber firebaseEventsSubscriber) {
37-
this.analyticsConnector =
38-
analyticsConnector != null ? analyticsConnector : StubAnalyticsConnector.instance;
37+
Deferred<AnalyticsConnector> analyticsConnector, Subscriber firebaseEventsSubscriber) {
38+
this.analyticsConnector = new ProxyAnalyticsConnector(analyticsConnector);
3939
this.firebaseEventsSubscriber = firebaseEventsSubscriber;
4040
}
4141

0 commit comments

Comments
 (0)