Skip to content

Commit

Permalink
feat(firebase_analytics): retrieves appInstanceId property on nativ…
Browse files Browse the repository at this point in the history
…e platforms if available (#8689)

* fix(example): Bump compileSdkVersion for the example Android app to fix compilation

* feat(firebase_analytics): add appInstanceId property

This value is needed to send server-side events through the Measurement Protocol as a part of the same session, and also useful for debugging to pair up with the data visible in DebugView and BigQuery.
  • Loading branch information
willbryant committed Jul 22, 2022
1 parent f020167 commit 7132d77
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 1 deletion.
Expand Up @@ -10,6 +10,7 @@
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.analytics.FirebaseAnalytics;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
Expand Down Expand Up @@ -130,6 +131,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
case "Analytics#setDefaultEventParameters":
methodCallTask = setDefaultEventParameters(call.arguments());
break;
case "Analytics#getAppInstanceId":
methodCallTask = handleGetAppInstanceId();
break;
default:
result.notImplemented();
return;
Expand Down Expand Up @@ -312,6 +316,21 @@ private Task<Void> setDefaultEventParameters(final Map<String, Object> arguments
return taskCompletionSource.getTask();
}

private Task<String> handleGetAppInstanceId() {
TaskCompletionSource<String> taskCompletionSource = new TaskCompletionSource<>();

cachedThreadPool.execute(
() -> {
try {
taskCompletionSource.setResult(Tasks.await(analytics.getAppInstanceId()));
} catch (Exception e) {
taskCompletionSource.setException(e);
}
});

return taskCompletionSource.getTask();
}

@Override
public Task<Map<String, Object>> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) {
TaskCompletionSource<Map<String, Object>> taskCompletionSource = new TaskCompletionSource<>();
Expand Down
Expand Up @@ -26,7 +26,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'

android {
compileSdkVersion 29
compileSdkVersion 31

lintOptions {
disable 'InvalidPackage'
Expand Down
Expand Up @@ -113,6 +113,20 @@ class _MyHomePageState extends State<MyHomePage> {
setMessage('setUserProperty succeeded');
}

Future<void> _testAppInstanceId() async {
String? id = await widget.analytics.appInstanceId;
if (id != null) {
setMessage('appInstanceId succeeded: $id');
} else {
setMessage('appInstanceId failed, consent declined');
}
}

Future<void> _testResetAnalyticsData() async {
await widget.analytics.resetAnalyticsData();
setMessage('resetAnalyticsData succeeded');
}

AnalyticsEventItem itemCreator() {
return AnalyticsEventItem(
affiliation: 'affil',
Expand Down Expand Up @@ -303,6 +317,14 @@ class _MyHomePageState extends State<MyHomePage> {
onPressed: _testSetUserProperty,
child: const Text('Test setUserProperty'),
),
MaterialButton(
onPressed: _testAppInstanceId,
child: const Text('Test appInstanceId'),
),
MaterialButton(
onPressed: _testResetAnalyticsData,
child: const Text('Test resetAnalyticsData'),
),
Text(
_message,
style: const TextStyle(color: Color.fromARGB(255, 0, 155, 0)),
Expand Down
Expand Up @@ -195,6 +195,29 @@ void testsMain() {
}
},
);

test('appInstanceId', () async {
if (kIsWeb) {
await expectLater(
FirebaseAnalytics.instance.appInstanceId,
throwsA(isA<UnimplementedError>()),
);
} else {
final result = await FirebaseAnalytics.instance.appInstanceId;
expect(result, isNull);

await expectLater(
FirebaseAnalytics.instance.setConsent(
analyticsStorageConsentGranted: true,
adStorageConsentGranted: false,
),
completes,
);

final result2 = await FirebaseAnalytics.instance.appInstanceId;
expect(result2, isA<String>());
}
});
});
}

Expand Down
Expand Up @@ -70,6 +70,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
[self setConsent:call.arguments withMethodCallResult:methodCallResult];
} else if ([@"Analytics#setDefaultEventParameters" isEqualToString:call.method]) {
[self setDefaultEventParameters:call.arguments withMethodCallResult:methodCallResult];
} else if ([@"Analytics#getAppInstanceId" isEqualToString:call.method]) {
[self getAppInstanceIdWithMethodCallResult:methodCallResult];
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -142,6 +144,11 @@ - (void)setDefaultEventParameters:(id)arguments
result.success(nil);
}

- (void)getAppInstanceIdWithMethodCallResult:(FLTFirebaseMethodCallResult *)result {
NSString *appInstanceID = [FIRAnalytics appInstanceID];
result.success(appInstanceID);
}

#pragma mark - FLTFirebasePlugin

- (void)didReinitializeFirebaseCore:(void (^_Nonnull)(void))completion {
Expand Down
Expand Up @@ -69,6 +69,12 @@ class FirebaseAnalytics extends FirebasePluginPlatform {
return _delegate.isSupported();
}

/// Retrieves the app instance id from the service, or null if consent has
/// been denied.
Future<String?> get appInstanceId {
return _delegate.getAppInstanceId();
}

/// Logs a custom Flutter Analytics event with the given [name] and event [parameters].
Future<void> logEvent({
required String name,
Expand Down
Expand Up @@ -1195,6 +1195,19 @@ void main() {
],
);
});

test('appInstanceId', () async {
var _ = await analytics!.appInstanceId;
expect(
methodCallLog,
<Matcher>[
isMethodCall(
'Analytics#getAppInstanceId',
arguments: null,
)
],
);
});
});
});
}
3 changes: 3 additions & 0 deletions packages/firebase_analytics/firebase_analytics/test/mock.dart
Expand Up @@ -20,6 +20,9 @@ void setupFirebaseAnalyticsMocks([Callback? customHandlers]) {
.setMockMethodCallHandler((MethodCall methodCall) async {
methodCallLog.add(methodCall);
switch (methodCall.method) {
case 'Analytics#getAppInstanceId':
return 'ABCD1234';

default:
return false;
}
Expand Down
Expand Up @@ -168,6 +168,15 @@ class MethodChannelFirebaseAnalytics extends FirebaseAnalyticsPlatform {
}
}

@override
Future<String?> getAppInstanceId() {
try {
return channel.invokeMethod<String?>('Analytics#getAppInstanceId');
} catch (e, s) {
convertPlatformException(e, s);
}
}

@override
Future<void> setSessionTimeoutDuration(Duration timeout) async {
try {
Expand Down
Expand Up @@ -68,6 +68,11 @@ abstract class FirebaseAnalyticsPlatform extends PlatformInterface {
throw UnimplementedError('isSupported() is not implemented');
}

/// Retrieves the app instance id from the service.
Future<String?> getAppInstanceId() {
throw UnimplementedError('getAppInstanceId() is not implemented');
}

/// Logs the given event [name] with the given [parameters].
Future<void> logEvent({
required String name,
Expand Down
Expand Up @@ -22,6 +22,9 @@ void main() {
methodCallLogger.add(call);

switch (call.method) {
case 'Analytics#getAppInstanceId':
return 'ABCD1234';

default:
return true;
}
Expand Down Expand Up @@ -117,6 +120,19 @@ void main() {
);
});

test('getAppInstanceId', () async {
await analytics.getAppInstanceId();
expect(
methodCallLogger,
<Matcher>[
isMethodCall(
'Analytics#getAppInstanceId',
arguments: null,
),
],
);
});

test('logEvent', () async {
await analytics.logEvent(
name: 'test-event',
Expand Down
Expand Up @@ -21,6 +21,9 @@ void setupFirebaseAnalyticsMocks([Callback? customHandlers]) {
.setMockMethodCallHandler((MethodCall methodCall) async {
methodCallLog.add(methodCall);
switch (methodCall.method) {
case 'Analytics#getAppInstanceId':
return 'ABCD1234';

default:
return false;
}
Expand Down
Expand Up @@ -198,6 +198,19 @@ void main() {
),
);
});

test('throws if .getAppInstanceId() not implemented', () async {
await expectLater(
() => firebaseAnalyticsPlatform.getAppInstanceId(),
throwsA(
isA<UnimplementedError>().having(
(e) => e.message,
'message',
'getAppInstanceId() is not implemented',
),
),
);
});
});
}

Expand Down
Expand Up @@ -135,4 +135,11 @@ class FirebaseAnalyticsWeb extends FirebaseAnalyticsPlatform {
'setDefaultEventParameters() is not supported on web',
);
}

@override
Future<String?> getAppInstanceId() async {
throw UnimplementedError(
'getAppInstanceId() is not supported on web',
);
}
}
23 changes: 23 additions & 0 deletions tests/test_driver/firebase_analytics/firebase_analytics_e2e.dart
Expand Up @@ -220,5 +220,28 @@ void setupTests() {
}
},
);

test('appInstanceId', () async {
if (kIsWeb) {
await expectLater(
FirebaseAnalytics.instance.appInstanceId,
throwsA(isA<UnimplementedError>()),
);
} else {
final result = await FirebaseAnalytics.instance.appInstanceId;
expect(result, isNull);

await expectLater(
FirebaseAnalytics.instance.setConsent(
analyticsStorageConsentGranted: true,
adStorageConsentGranted: false,
),
completes,
);

final result2 = await FirebaseAnalytics.instance.appInstanceId;
expect(result2, isA<String>());
}
});
});
}

1 comment on commit 7132d77

@noodles
Copy link

Choose a reason for hiding this comment

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

I can't run the sample code after these changes.
Isn't the Firebase Instance ID API deprecated?
https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId

Reverting back to the previous version and changing DefaultFirebaseConfig.platformOptions to DefaultFirebaseOptions.currentPlatform got this working for me with the latest version of FlutterFire.

Please sign in to comment.