Skip to content

Commit bdb4f33

Browse files
authored
Integrate interop with Crashlytics sdk (#5286)
Integrate RC interop with Crashlytics SDK: 1. Adding Crashlytics subscriber for RC interop 2. Adding Deferred Proxy for RC interop 3. Unit tests
1 parent 6187887 commit bdb4f33

File tree

11 files changed

+323
-11
lines changed

11 files changed

+323
-11
lines changed

firebase-crashlytics/firebase-crashlytics.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ dependencies {
7373
implementation 'com.google.firebase:firebase-measurement-connector:18.0.2'
7474
implementation "com.google.android.gms:play-services-tasks:18.0.1"
7575
implementation project(':firebase-sessions')
76+
implementation project(':firebase-config-interop')
7677

7778
javadocClasspath 'com.google.code.findbugs:jsr305:3.0.2'
7879

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2023 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.crashlytics.internal;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.mockito.Mockito.times;
19+
import static org.mockito.Mockito.verify;
20+
21+
import com.google.firebase.crashlytics.internal.metadata.RolloutAssignment;
22+
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
23+
import com.google.firebase.remoteconfig.interop.rollouts.RolloutsState;
24+
import java.util.ArrayList;
25+
import java.util.HashSet;
26+
import java.util.List;
27+
import java.util.Set;
28+
import org.junit.Before;
29+
import org.junit.Test;
30+
import org.mockito.ArgumentCaptor;
31+
import org.mockito.Captor;
32+
import org.mockito.Mock;
33+
import org.mockito.MockitoAnnotations;
34+
35+
public class CrashlyticsRemoteConfigListenerTest {
36+
private static final String ROLLOUT_ASSIGNMENT_JSON_1 =
37+
"{"
38+
+ "\"rolloutId\":\"rollout_1\","
39+
+ "\"variantId\":\"control\","
40+
+ "\"parameterKey\":\"my_feature\","
41+
+ "\"parameterValue\":\"false\","
42+
+ "\"templateVersion\":1"
43+
+ "}";
44+
45+
private static final String ROLLOUT_ASSIGNMENT_JSON_2 =
46+
"{"
47+
+ "\"rolloutId\":\"rollout_2\","
48+
+ "\"variantId\":\"enabled\","
49+
+ "\"parameterKey\":\"my_another_feature\","
50+
+ "\"parameterValue\":\"false\","
51+
+ "\"templateVersion\":1"
52+
+ "}";
53+
54+
private static List<RolloutAssignment> CRASHLYTICS_ROLLOUT_ASSIGNMENT_SINGLE_ELEMENT_LIST;
55+
56+
static {
57+
CRASHLYTICS_ROLLOUT_ASSIGNMENT_SINGLE_ELEMENT_LIST = new ArrayList<>();
58+
CRASHLYTICS_ROLLOUT_ASSIGNMENT_SINGLE_ELEMENT_LIST.add(
59+
RolloutAssignment.create("rollout_1", "my_feature", "false", "control", 1));
60+
}
61+
62+
private static List<RolloutAssignment> CRASHLYTICS_ROLLOUT_ASSIGNMENT_MULTIPLE_ELEMENTS_LIST;
63+
64+
static {
65+
CRASHLYTICS_ROLLOUT_ASSIGNMENT_MULTIPLE_ELEMENTS_LIST = new ArrayList<>();
66+
CRASHLYTICS_ROLLOUT_ASSIGNMENT_MULTIPLE_ELEMENTS_LIST.add(
67+
RolloutAssignment.create("rollout_1", "my_feature", "false", "control", 1));
68+
CRASHLYTICS_ROLLOUT_ASSIGNMENT_MULTIPLE_ELEMENTS_LIST.add(
69+
RolloutAssignment.create("rollout_2", "my_another_feature", "false", "enabled", 1));
70+
}
71+
72+
@Mock private UserMetadata userMetadata;
73+
74+
@Captor private ArgumentCaptor<ArrayList<RolloutAssignment>> captor;
75+
76+
private CrashlyticsRemoteConfigListener listener;
77+
78+
@Before
79+
public void setup() throws Exception {
80+
MockitoAnnotations.openMocks(this);
81+
listener = new CrashlyticsRemoteConfigListener(userMetadata);
82+
}
83+
84+
@Test
85+
public void testListener_onChangeUpdateRCInteropClassToCrashlyticsClass() throws Exception {
86+
RolloutsState rolloutsState = createInteropRolloutsStateWithSingleElement();
87+
listener.onRolloutsStateChanged(rolloutsState);
88+
89+
verify(userMetadata).updateRolloutsState(CRASHLYTICS_ROLLOUT_ASSIGNMENT_SINGLE_ELEMENT_LIST);
90+
}
91+
92+
@Test
93+
public void testListener_onChangeUpdateRCInteropClassToCrashlyticsClassMultipleTimes()
94+
throws Exception {
95+
RolloutsState rolloutsState = createInteropRolloutsStateWithSingleElement();
96+
listener.onRolloutsStateChanged(rolloutsState);
97+
98+
RolloutsState newRolloutsState = createInteropRolloutsStateWithMultipleElements();
99+
listener.onRolloutsStateChanged(newRolloutsState);
100+
101+
verify(userMetadata, times(2)).updateRolloutsState(captor.capture());
102+
assertThat(captor.getAllValues().get(1).size()).isEqualTo(2);
103+
}
104+
105+
private RolloutsState createInteropRolloutsStateWithSingleElement() throws Exception {
106+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment assignment =
107+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.create(
108+
ROLLOUT_ASSIGNMENT_JSON_1);
109+
Set<com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment> rolloutAssignmentsSet =
110+
new HashSet<>();
111+
rolloutAssignmentsSet.add(assignment);
112+
113+
RolloutsState rolloutsState = RolloutsState.create(rolloutAssignmentsSet);
114+
return rolloutsState;
115+
}
116+
117+
private RolloutsState createInteropRolloutsStateWithMultipleElements() throws Exception {
118+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment assignment1 =
119+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.create(
120+
ROLLOUT_ASSIGNMENT_JSON_1);
121+
122+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment assignment2 =
123+
com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment.create(
124+
ROLLOUT_ASSIGNMENT_JSON_2);
125+
Set<com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment> rolloutAssignmentsSet =
126+
new HashSet<>();
127+
128+
rolloutAssignmentsSet.add(assignment1);
129+
rolloutAssignmentsSet.add(assignment2);
130+
131+
RolloutsState rolloutsState = RolloutsState.create(rolloutAssignmentsSet);
132+
return rolloutsState;
133+
}
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2023 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.crashlytics.internal;
16+
17+
import static org.mockito.ArgumentMatchers.anyObject;
18+
import static org.mockito.ArgumentMatchers.eq;
19+
import static org.mockito.Mockito.verify;
20+
21+
import androidx.annotation.NonNull;
22+
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
23+
import com.google.firebase.inject.Deferred;
24+
import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.mockito.Mock;
28+
import org.mockito.MockitoAnnotations;
29+
30+
public class RemoteConfigDeferredProxyTest {
31+
32+
@Mock private FirebaseRemoteConfigInterop interop;
33+
34+
@Mock private UserMetadata userMetadata;
35+
36+
@Before
37+
public void setUp() throws Exception {
38+
MockitoAnnotations.openMocks(this);
39+
}
40+
41+
@Test
42+
public void testProviderProxyCallsThroughProvidedValue() {
43+
RemoteConfigDeferredProxy proxy =
44+
new RemoteConfigDeferredProxy(
45+
new Deferred<FirebaseRemoteConfigInterop>() {
46+
@Override
47+
public void whenAvailable(
48+
@NonNull Deferred.DeferredHandler<FirebaseRemoteConfigInterop> handler) {
49+
handler.handle(() -> interop);
50+
}
51+
});
52+
proxy.setupListener(userMetadata);
53+
verify(interop).registerRolloutsStateSubscriber(eq("firebase"), anyObject());
54+
}
55+
}

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreInitializationTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponentDeferredProxy;
3232
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
3333
import com.google.firebase.crashlytics.internal.DevelopmentPlatformProvider;
34+
import com.google.firebase.crashlytics.internal.RemoteConfigDeferredProxy;
3435
import com.google.firebase.crashlytics.internal.analytics.UnavailableAnalyticsEventLogger;
3536
import com.google.firebase.crashlytics.internal.breadcrumbs.DisabledBreadcrumbSource;
3637
import com.google.firebase.crashlytics.internal.persistence.FileStore;
@@ -137,7 +138,8 @@ public CrashlyticsCore build() {
137138
new UnavailableAnalyticsEventLogger(),
138139
fileStore,
139140
crashHandlerExecutor,
140-
mock(CrashlyticsAppQualitySessionsSubscriber.class));
141+
mock(CrashlyticsAppQualitySessionsSubscriber.class),
142+
mock(RemoteConfigDeferredProxy.class));
141143
}
142144
}
143145

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponentDeferredProxy;
3434
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
3535
import com.google.firebase.crashlytics.internal.DevelopmentPlatformProvider;
36+
import com.google.firebase.crashlytics.internal.RemoteConfigDeferredProxy;
3637
import com.google.firebase.crashlytics.internal.analytics.UnavailableAnalyticsEventLogger;
3738
import com.google.firebase.crashlytics.internal.breadcrumbs.BreadcrumbHandler;
3839
import com.google.firebase.crashlytics.internal.breadcrumbs.BreadcrumbSource;
@@ -427,7 +428,8 @@ CrashlyticsCore build(Context context) {
427428
new UnavailableAnalyticsEventLogger(),
428429
new FileStore(context),
429430
new SameThreadExecutorService(),
430-
mock(CrashlyticsAppQualitySessionsSubscriber.class));
431+
mock(CrashlyticsAppQualitySessionsSubscriber.class),
432+
mock(RemoteConfigDeferredProxy.class));
431433
return crashlyticsCore;
432434
}
433435
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.firebase.inject.Deferred;
2525
import com.google.firebase.installations.FirebaseInstallationsApi;
2626
import com.google.firebase.platforminfo.LibraryVersionComponent;
27+
import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop;
2728
import com.google.firebase.sessions.FirebaseSessions;
2829
import com.google.firebase.sessions.api.FirebaseSessionsDependencies;
2930
import com.google.firebase.sessions.api.SessionSubscriber;
@@ -49,6 +50,7 @@ public List<Component<?>> getComponents() {
4950
.add(Dependency.required(FirebaseSessions.class))
5051
.add(Dependency.deferred(CrashlyticsNativeComponent.class))
5152
.add(Dependency.deferred(AnalyticsConnector.class))
53+
.add(Dependency.deferred(FirebaseRemoteConfigInterop.class))
5254
.factory(this::buildCrashlytics)
5355
.eagerInDefaultApp()
5456
.build(),
@@ -68,7 +70,15 @@ private FirebaseCrashlytics buildCrashlytics(ComponentContainer container) {
6870

6971
FirebaseSessions firebaseSessions = container.get(FirebaseSessions.class);
7072

73+
Deferred<FirebaseRemoteConfigInterop> remoteConfigInterop =
74+
container.getDeferred(FirebaseRemoteConfigInterop.class);
75+
7176
return FirebaseCrashlytics.init(
72-
app, firebaseInstallations, firebaseSessions, nativeComponent, analyticsConnector);
77+
app,
78+
firebaseInstallations,
79+
firebaseSessions,
80+
nativeComponent,
81+
analyticsConnector,
82+
remoteConfigInterop);
7383
}
7484
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponentDeferredProxy;
2929
import com.google.firebase.crashlytics.internal.DevelopmentPlatformProvider;
3030
import com.google.firebase.crashlytics.internal.Logger;
31+
import com.google.firebase.crashlytics.internal.RemoteConfigDeferredProxy;
3132
import com.google.firebase.crashlytics.internal.common.AppData;
3233
import com.google.firebase.crashlytics.internal.common.BuildIdInfo;
3334
import com.google.firebase.crashlytics.internal.common.CommonUtils;
@@ -41,6 +42,7 @@
4142
import com.google.firebase.crashlytics.internal.settings.SettingsController;
4243
import com.google.firebase.inject.Deferred;
4344
import com.google.firebase.installations.FirebaseInstallationsApi;
45+
import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop;
4446
import com.google.firebase.sessions.FirebaseSessions;
4547
import java.util.List;
4648
import java.util.concurrent.Callable;
@@ -66,7 +68,8 @@ public class FirebaseCrashlytics {
6668
@NonNull FirebaseInstallationsApi firebaseInstallationsApi,
6769
@NonNull FirebaseSessions firebaseSessions,
6870
@NonNull Deferred<CrashlyticsNativeComponent> nativeComponent,
69-
@NonNull Deferred<AnalyticsConnector> analyticsConnector) {
71+
@NonNull Deferred<AnalyticsConnector> analyticsConnector,
72+
@NonNull Deferred<FirebaseRemoteConfigInterop> remoteConfigInteropDeferred) {
7073

7174
Context context = app.getApplicationContext();
7275
final String appIdentifier = context.getPackageName();
@@ -95,6 +98,9 @@ public class FirebaseCrashlytics {
9598
new CrashlyticsAppQualitySessionsSubscriber(arbiter);
9699
firebaseSessions.register(sessionsSubscriber);
97100

101+
RemoteConfigDeferredProxy remoteConfigDeferredProxy =
102+
new RemoteConfigDeferredProxy(remoteConfigInteropDeferred);
103+
98104
final CrashlyticsCore core =
99105
new CrashlyticsCore(
100106
app,
@@ -105,7 +111,8 @@ public class FirebaseCrashlytics {
105111
analyticsDeferredProxy.getAnalyticsEventLogger(),
106112
fileStore,
107113
crashHandlerExecutor,
108-
sessionsSubscriber);
114+
sessionsSubscriber,
115+
remoteConfigDeferredProxy);
109116

110117
final String googleAppId = app.getOptions().getApplicationId();
111118
final String mappingFileId = CommonUtils.getMappingFileId(context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2023 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.crashlytics.internal;
16+
17+
import androidx.annotation.NonNull;
18+
import com.google.firebase.crashlytics.internal.metadata.RolloutAssignment;
19+
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
20+
import com.google.firebase.remoteconfig.interop.rollouts.RolloutsState;
21+
import com.google.firebase.remoteconfig.interop.rollouts.RolloutsStateSubscriber;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Set;
25+
26+
public class CrashlyticsRemoteConfigListener implements RolloutsStateSubscriber {
27+
private final UserMetadata userMetadata;
28+
29+
CrashlyticsRemoteConfigListener(UserMetadata userMetadata) {
30+
this.userMetadata = userMetadata;
31+
}
32+
33+
@Override
34+
public void onRolloutsStateChanged(@NonNull RolloutsState rolloutsState) {
35+
Set<com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment> rolloutAssignmentList =
36+
rolloutsState.getRolloutAssignments();
37+
38+
List<com.google.firebase.crashlytics.internal.metadata.RolloutAssignment>
39+
crashlyticsRolloutAssignmentList = new ArrayList();
40+
for (com.google.firebase.remoteconfig.interop.rollouts.RolloutAssignment assignment :
41+
rolloutAssignmentList) {
42+
RolloutAssignment crashlyticsRolloutAssignment =
43+
RolloutAssignment.create(
44+
assignment.getRolloutId(),
45+
assignment.getParameterKey(),
46+
assignment.getParameterValue(),
47+
assignment.getVariantId(),
48+
assignment.getTemplateVersion());
49+
crashlyticsRolloutAssignmentList.add(crashlyticsRolloutAssignment);
50+
}
51+
userMetadata.updateRolloutsState(crashlyticsRolloutAssignmentList);
52+
Logger.getLogger().d("Updated Crashlytics Rollout State");
53+
}
54+
}

0 commit comments

Comments
 (0)