Skip to content

Add Dynamic loading support to fiam. #2535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions firebase-inappmessaging/firebase-inappmessaging.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'com.google.auto.value:auto-value-annotations:1.6.6'

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

Expand All @@ -144,8 +144,9 @@ dependencies {

testImplementation 'org.mockito:mockito-core:1.10.19'
testImplementation "com.google.truth:truth:$googleTruthVersion"
testImplementation 'junit:junit:4.12'
testImplementation 'androidx.test:runner:1.2.0'
testImplementation 'junit:junit:4.13.1'
testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation ("org.robolectric:robolectric:$robolectricVersion") {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ public void setUp() {
.testForegroundFlowableModule(new TestForegroundFlowableModule(foregroundNotifier))
.applicationModule(new ApplicationModule(application))
.appMeasurementModule(
new AppMeasurementModule(analyticsConnector, firebaseEventSubscriber))
new AppMeasurementModule(
p -> p.handle(() -> analyticsConnector), firebaseEventSubscriber))
.testSystemClockModule(new TestSystemClockModule(NOW))
.programmaticContextualTriggerFlowableModule(
new ProgrammaticContextualTriggerFlowableModule(
Expand Down Expand Up @@ -313,7 +314,7 @@ public void onAnalyticsNotification_notifiesSubscriber() {
public void onAppOpen_whenAnalyticsAbsent_notifiesSubscriber() {
TestUniversalComponent analyticsLessUniversalComponent =
universalComponentBuilder
.appMeasurementModule(new AppMeasurementModule(null, firebaseEventSubscriber))
.appMeasurementModule(new AppMeasurementModule(handler -> {}, firebaseEventSubscriber))
.build();
TestAppComponent appComponent =
appComponentBuilder.universalComponent(analyticsLessUniversalComponent).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.google.firebase.inappmessaging.internal.injection.modules.ApplicationModule;
import com.google.firebase.inappmessaging.internal.injection.modules.GrpcClientModule;
import com.google.firebase.inappmessaging.internal.injection.modules.ProgrammaticContextualTriggerFlowableModule;
import com.google.firebase.inject.Deferred;
import com.google.firebase.installations.FirebaseInstallationsApi;
import com.google.firebase.platforminfo.LibraryVersionComponent;
import java.util.Arrays;
Expand All @@ -60,7 +61,7 @@ public List<Component<?>> getComponents() {
.add(Dependency.required(FirebaseInstallationsApi.class))
.add(Dependency.required(FirebaseApp.class))
.add(Dependency.required(AbtComponent.class))
.add(Dependency.optional(AnalyticsConnector.class))
.add(Dependency.deferred(AnalyticsConnector.class))
.add(Dependency.required(TransportFactory.class))
.add(Dependency.required(Subscriber.class))
.factory(this::providesFirebaseInAppMessaging)
Expand All @@ -72,7 +73,8 @@ public List<Component<?>> getComponents() {
private FirebaseInAppMessaging providesFirebaseInAppMessaging(ComponentContainer container) {
FirebaseApp firebaseApp = container.get(FirebaseApp.class);
FirebaseInstallationsApi firebaseInstallations = container.get(FirebaseInstallationsApi.class);
AnalyticsConnector analyticsConnector = container.get(AnalyticsConnector.class);
Deferred<AnalyticsConnector> analyticsConnector =
container.getDeferred(AnalyticsConnector.class);
Subscriber firebaseEventsSubscriber = container.get(Subscriber.class);

Application application = (Application) firebaseApp.getApplicationContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.google.firebase.inappmessaging.internal;

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

@Override
// fiam uses an AnalyticsConnector proxy that is Deferred-aware so it's safe to suppress.
@SuppressLint("InvalidDeferredApiUse")
public void subscribe(FlowableEmitter<String> emitter) {
Logging.logd("Subscribing to analytics events.");
handle =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.inappmessaging.internal;

import android.os.Bundle;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.firebase.analytics.connector.AnalyticsConnector;
import com.google.firebase.inject.Deferred;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Proxy connector that delegates to analytics when it's available.
*
* <p>For a subset of functionality it also caches calls and propagates them to analytics once it
* loads.
*/
public class ProxyAnalyticsConnector implements AnalyticsConnector {
private volatile Object instance;

public ProxyAnalyticsConnector(Deferred<AnalyticsConnector> analyticsConnector) {
instance = analyticsConnector;
analyticsConnector.whenAvailable(connectorProvider -> instance = connectorProvider.get());
}

private AnalyticsConnector safeGet() {
Object result = instance;
if (result instanceof AnalyticsConnector) {
return (AnalyticsConnector) result;
}
return null;
}

@Override
public void logEvent(@NonNull String s, @NonNull String s1, @NonNull Bundle bundle) {
AnalyticsConnector connector = safeGet();
if (connector != null) {
connector.logEvent(s, s1, bundle);
}
}

@Override
public void setUserProperty(@NonNull String s, @NonNull String s1, @NonNull Object o) {
AnalyticsConnector connector = safeGet();
if (connector != null) {
connector.setUserProperty(s, s1, o);
}
}

// Not implemented since it's not used by fiam
@NonNull
@Override
public Map<String, Object> getUserProperties(boolean b) {
return Collections.emptyMap();
}

@NonNull
@Override
public AnalyticsConnectorHandle registerAnalyticsConnectorListener(
@NonNull String s, @NonNull AnalyticsConnectorListener analyticsConnectorListener) {
Object result = instance;
if (result instanceof AnalyticsConnector) {
return ((AnalyticsConnector) result)
.registerAnalyticsConnectorListener(s, analyticsConnectorListener);
}
@SuppressWarnings("unchecked")
Deferred<AnalyticsConnector> deferred = (Deferred<AnalyticsConnector>) result;
return new ProxyAnalyticsConnectorHandle(s, analyticsConnectorListener, deferred);
}

// Not implemented since it's not used by fiam.
@Override
public void setConditionalUserProperty(
@NonNull ConditionalUserProperty conditionalUserProperty) {}

// Not implemented since it's not used by fiam.
@Override
public void clearConditionalUserProperty(
@NonNull String s, @Nullable String s1, @Nullable Bundle bundle) {}

// Not implemented since it's not used by fiam.
@NonNull
@Override
public List<ConditionalUserProperty> getConditionalUserProperties(
@NonNull String s, @Nullable String s1) {
return Collections.emptyList();
}

// Not implemented since it's not used by fiam.
@Override
public int getMaxUserProperties(@NonNull String s) {
return 0;
}

private static class ProxyAnalyticsConnectorHandle implements AnalyticsConnectorHandle {
private static final Object UNREGISTERED = new Object();

@GuardedBy("this")
private Set<String> eventNames = new HashSet<>();

private volatile Object instance;

private ProxyAnalyticsConnectorHandle(
String s,
AnalyticsConnectorListener listener,
Deferred<AnalyticsConnector> analyticsConnector) {
analyticsConnector.whenAvailable(
connectorProvider -> {
Object result = instance;
if (result == UNREGISTERED) {
return;
}
AnalyticsConnector connector = connectorProvider.get();
// Now that analytics is available:

// register the listener with analytics.
AnalyticsConnectorHandle handle =
connector.registerAnalyticsConnectorListener(s, listener);
instance = handle;

// propagate registered event names to analytics.
synchronized (ProxyAnalyticsConnectorHandle.this) {
if (!eventNames.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding some comments here.

handle.registerEventNames(eventNames);
eventNames = new HashSet<>();
}
}
});
}

@Override
public void unregister() {
Object result = instance;
if (result == UNREGISTERED) {
return;
}

if (result != null) {
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
handle.unregister();
}
instance = UNREGISTERED;
synchronized (this) {
eventNames.clear();
}
}

@Override
public void registerEventNames(@NonNull Set<String> set) {
Object result = instance;
if (result == UNREGISTERED) {
return;
}

if (result != null) {
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
handle.registerEventNames(set);
return;
}
synchronized (this) {
eventNames.addAll(set);
}
}

@Override
public void unregisterEventNames() {
Object result = instance;
if (result == UNREGISTERED) {
return;
}
if (result != null) {
AnalyticsConnectorHandle handle = (AnalyticsConnectorHandle) result;
handle.unregisterEventNames();
return;
}
synchronized (this) {
eventNames.clear();
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

import com.google.firebase.analytics.connector.AnalyticsConnector;
import com.google.firebase.events.Subscriber;
import com.google.firebase.inappmessaging.internal.StubAnalyticsConnector;
import com.google.firebase.inappmessaging.internal.ProxyAnalyticsConnector;
import com.google.firebase.inject.Deferred;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
Expand All @@ -29,13 +30,12 @@
@Module
public class AppMeasurementModule {

private AnalyticsConnector analyticsConnector;
private Subscriber firebaseEventsSubscriber;
private final AnalyticsConnector analyticsConnector;
private final Subscriber firebaseEventsSubscriber;

public AppMeasurementModule(
AnalyticsConnector analyticsConnector, Subscriber firebaseEventsSubscriber) {
this.analyticsConnector =
analyticsConnector != null ? analyticsConnector : StubAnalyticsConnector.instance;
Deferred<AnalyticsConnector> analyticsConnector, Subscriber firebaseEventsSubscriber) {
this.analyticsConnector = new ProxyAnalyticsConnector(analyticsConnector);
this.firebaseEventsSubscriber = firebaseEventsSubscriber;
}

Expand Down
Loading