Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public class FlutterFirebaseMessagingPlugin
private Observer<RemoteMessage> remoteMessageObserver;
private final LiveData<String> liveDataToken = FlutterFirebaseTokenLiveData.getInstance();
private Observer<String> tokenObserver;
private final LiveData<String> liveDataRegistered =
FlutterFirebaseRegisteredLiveData.getInstance();
private Observer<String> registeredObserver;
private final LiveData<String> liveDataUnregistered =
FlutterFirebaseUnregisteredLiveData.getInstance();
private Observer<String> unregisteredObserver;

private RemoteMessage initialMessage;
// We store the initial notification in a separate variable
Expand All @@ -93,10 +99,14 @@ private void initInstance(BinaryMessenger messenger) {
channel.invokeMethod("Messaging#onMessage", content);
};
tokenObserver = token -> channel.invokeMethod("Messaging#onTokenRefresh", token);
registeredObserver = fid -> channel.invokeMethod("Messaging#onRegistered", fid);
unregisteredObserver = fid -> channel.invokeMethod("Messaging#onUnregistered", fid);
// We remove these observers in the onDetachedFromEngine method. Using "observeForever()"
// allows us to use without a LifecycleOwner.
liveDataRemoteMessage.observeForever(remoteMessageObserver);
liveDataToken.observeForever(tokenObserver);
liveDataRegistered.observeForever(registeredObserver);
liveDataUnregistered.observeForever(unregisteredObserver);

registerPlugin(channelName, this);
}
Expand All @@ -111,6 +121,8 @@ public void onAttachedToEngine(FlutterPluginBinding binding) {
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
liveDataToken.removeObserver(tokenObserver);
liveDataRemoteMessage.removeObserver(remoteMessageObserver);
liveDataRegistered.removeObserver(registeredObserver);
liveDataUnregistered.removeObserver(unregisteredObserver);
}

@Override
Expand Down Expand Up @@ -184,6 +196,38 @@ private Task<Map<String, Object>> getToken() {
return taskCompletionSource.getTask();
}

private Task<Void> register() {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

cachedThreadPool.execute(
() -> {
try {
Tasks.await(FirebaseMessaging.getInstance().register());
taskCompletionSource.setResult(null);
} catch (Exception e) {
taskCompletionSource.setException(e);
}
});

return taskCompletionSource.getTask();
}

private Task<Void> unregister() {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

cachedThreadPool.execute(
() -> {
try {
Tasks.await(FirebaseMessaging.getInstance().unregister());
taskCompletionSource.setResult(null);
} catch (Exception e) {
taskCompletionSource.setException(e);
}
});

return taskCompletionSource.getTask();
}

private Task<Void> subscribeToTopic(Map<String, Object> arguments) {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

Expand Down Expand Up @@ -567,6 +611,12 @@ public void onMethodCall(final MethodCall call, @NonNull final Result result) {
case "Messaging#getToken":
methodCallTask = getToken();
break;
case "Messaging#register":
methodCallTask = register();
break;
case "Messaging#unregister":
methodCallTask = unregister();
break;
case "Messaging#subscribeToTopic":
methodCallTask = subscribeToTopic(call.arguments());
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ public void onNewToken(@NonNull String token) {
FlutterFirebaseTokenLiveData.getInstance().postToken(token);
}

@Override
public void onRegistered(@NonNull String installationId) {
FlutterFirebaseRegisteredLiveData.getInstance().postFid(installationId);
}

@Override
public void onUnregistered(@NonNull String installationId) {
FlutterFirebaseUnregisteredLiveData.getInstance().postFid(installationId);
}

@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
// Added for commenting purposes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.firebase.messaging;

import androidx.lifecycle.LiveData;

public class FlutterFirebaseRegisteredLiveData extends LiveData<String> {
private static FlutterFirebaseRegisteredLiveData instance;

public static FlutterFirebaseRegisteredLiveData getInstance() {
if (instance == null) {
instance = new FlutterFirebaseRegisteredLiveData();
}
return instance;
}

public void postFid(String fid) {
postValue(fid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.firebase.messaging;

import androidx.lifecycle.LiveData;

public class FlutterFirebaseUnregisteredLiveData extends LiveData<String> {
private static FlutterFirebaseUnregisteredLiveData instance;

public static FlutterFirebaseUnregisteredLiveData getInstance() {
if (instance == null) {
instance = new FlutterFirebaseUnregisteredLiveData();
}
return instance;
}

public void postFid(String fid) {
postValue(fid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="firebase_messaging_installation_id_enabled"
android:value="true" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// ignore_for_file: deprecated_member_use

import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
Expand Down Expand Up @@ -285,6 +287,79 @@ void main() {
); // only run for manual testing
});

group('register()', () {
test(
'emits a FID through onRegistered',
() async {
final completer = Completer<String>();
late StreamSubscription<String> subscription;

subscription = messaging.onRegistered.listen((fid) {
if (!completer.isCompleted) {
completer.complete(fid);
}
});

await messaging.register();

final fid = await completer.future.timeout(
const Duration(seconds: 30),
);
expect(fid, isNotEmpty);

await subscription.cancel();
},
// iOS CI does not have the push/APNs setup needed for FID registration.
skip: kIsWeb ||
(defaultTargetPlatform == TargetPlatform.iOS && skipTestsOnCI) ||
(defaultTargetPlatform != TargetPlatform.android &&
defaultTargetPlatform != TargetPlatform.iOS),
);
});

group('unregister()', () {
test(
'emits the unregistered FID through onUnregistered',
() async {
final registeredCompleter = Completer<String>();
final unregisteredCompleter = Completer<String>();

final registeredSubscription = messaging.onRegistered.listen((fid) {
if (!registeredCompleter.isCompleted) {
registeredCompleter.complete(fid);
}
});
final unregisteredSubscription =
messaging.onUnregistered.listen((fid) {
if (!unregisteredCompleter.isCompleted) {
unregisteredCompleter.complete(fid);
}
});

await messaging.register();
final registeredFid = await registeredCompleter.future.timeout(
const Duration(seconds: 30),
);

await messaging.unregister();
final unregisteredFid = await unregisteredCompleter.future.timeout(
const Duration(seconds: 30),
);

expect(registeredFid, isNotEmpty);
expect(unregisteredFid, registeredFid);

await registeredSubscription.cancel();
await unregisteredSubscription.cancel();
},
// iOS CI does not have the push/APNs setup needed for FID registration.
skip: kIsWeb ||
(defaultTargetPlatform == TargetPlatform.iOS && skipTestsOnCI) ||
(defaultTargetPlatform != TargetPlatform.android &&
defaultTargetPlatform != TargetPlatform.iOS),
);
});

group('subscribeToTopic()', () {
test(
'successfully subscribes from topic',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>FirebaseMessagingInstallationIdEnabled</key>
<true/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
Expand Down
Loading
Loading