43

Is there any way to unsubscribe from all topics at once?

I'm using Firebase Messaging to receive push notification from some topics subscribed, and somehow I need to unsubscribe from all topics without unsubscribing one by one. Is that possible?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Lennon Spirlandelli
  • 3,131
  • 5
  • 26
  • 51

9 Answers9

22

You can use Instance API to query all the available topics subscribed to given token and than call multiple request to unsubscribe from all the topics.

However, if you want to stop receiving from all the topics and then the token is not useful at all, you can call FirebaseInstanceId.getInstance().deleteInstanceId() (reference: deleteInstanceId() and surround with a try/catch for a potential IOException) that will reset the instance id and than again you can subscribe to new topics from the new instance id and token.

Hope this helps someone.

jobbert
  • 3,297
  • 27
  • 43
kirtan403
  • 7,293
  • 6
  • 54
  • 97
  • In my case it doesn't work because I don't store any `instance`. The users only get informations from subscribed topics – Lennon Spirlandelli Nov 18 '16 at 16:45
  • @Lennon reseting instance id will remove all the subscribed state. And give you an empty new instance – kirtan403 Nov 18 '16 at 16:51
  • How do you know that deleting the instance all the subscribed topics will be revoked? – Lennon Spirlandelli Nov 18 '16 at 17:15
  • Check the link in my answer. deleteInstanceId() call resets Instance ID and revokes all tokens. After this call you can query your previous token with get request to `https://iid.googleapis.com/iid/info/IID_TOKEN` as mentioned here :https://developers.google.com/instance-id/reference/server#get_information_about_app_instances You will see that your token no longer exists. So it will not be able to receive any event messages. – kirtan403 Nov 18 '16 at 18:21
  • I saw that and it really says all tokens are revoked, but how do I know that if I revoke the token all topics will no longer be subscribed? – Lennon Spirlandelli Nov 21 '16 at 17:30
  • @Lennon After revoking a token try to send a message to that token and it will give you an error. So when a token is deleted, it will not receive any messages.. – kirtan403 Nov 21 '16 at 18:06
  • Thanks. I will check it. – Lennon Spirlandelli Nov 21 '16 at 20:50
  • @kirtan403 if only one user is subscribed to a topic and deletes the app, will he be unsubscribed from the topic? Thus if there was a topic called `weather`(a textview) that has only one user and he unsubscribe then `weather` wont be a topic anymore only a textview until someone else subscribes right? – Peter Haddad Jan 05 '18 at 06:51
  • @PeterHaddad When you delete(uninstall) the app, the instance Id is invalidated. So anything sent to that instance id token, won't be received by the device. And if there are no subscribers to a topic, the topic doesn't exists anymore. – kirtan403 Jan 05 '18 at 11:46
  • @jobbert i tried messaging.unsubscribeFromTopic("new_updates"); but its not working still getting notifications any other way to unsubscribe single user – Mehul Gajjar Mar 21 '18 at 12:48
  • what @kirtan403 says is that you have an unique deviceToken generated with the instanceID with FirebaseMessaging, now, all topics are subscribed to that specific instanceID since all notifications are handled to the user throught that ID. Now, if you refresh this id each time the user logs in, or uninstall and install the app again, you will have a fresh instanceID and so there is not toopics into that deviceID – Gastón Saillén May 11 '20 at 18:15
4

If you want to avoid deleting InstanceId and moreover, to avoid missing some of the subscribed topics when saved in a local database or a remote database due to highly probable buggy implementation.

First get all subscribed topics:

    var options = BaseOptions(headers: {'Authorization':'key = YOUR_KEY'});
    var dio = Dio(options);
    var response = await dio.get('https://iid.googleapis.com/iid/info/' + token,
                             queryParameters: {'details': true});
    Map<String, dynamic> subscribedTopics = response.data['rel']['topics'];

Get your key here: Firebase console -> your project -> project settings -> cloud messaging -> server key

Get your token as:

var firebaseMessaging = FirebaseMessaging();
String token;
firebaseMessaging.getToken().then((value) {
  token = value;
});

Now unsubscribe from all topics:

  Future<void> unsubscribeFromAllTopics() async {
    for (var entry in subscribedTopics.entries) {
      await Future.delayed(Duration(milliseconds: 100)); // throttle due to 3000 QPS limit
      unawaited(firebaseMessaging.unsubscribeFromTopic(entry.key)); // import pedantic for unawaited
      debugPrint('Unsubscribed from: ' + entry.key);
    }
  }

All code is in Dart.

For more information about instance id: https://developers.google.com/instance-id/reference/server

Mutlu Simsek
  • 1,088
  • 14
  • 22
  • Wondering whether it's necessary to throttle 100ms (10 per second) if the limit is 3000 queries per second. It will take at least one second to unsubscribe from 10 topics this way! – Tom Anderson Jul 19 '23 at 05:54
  • It was a safe bet. You can tune the number as always :) – Mutlu Simsek Jul 24 '23 at 15:08
3

I know this is not the best way but it works! You can store list of all topics in Database and then unsubscribe from all topics when user sign-outs

final FirebaseMessaging messaging= FirebaseMessaging.getInstance();
      FirebaseDatabase.getInstance().getReference().child("topics").addChildEventListener(new ChildEventListener() {
          @Override
          public void onChildAdded(DataSnapshot dataSnapshot, String s) {
              String topic  = dataSnapshot.getValue(String.class);
              messaging.unsubscribeFromTopic(topic);
}...//rest code
Mirza Asad
  • 606
  • 1
  • 7
  • 18
  • IMO this is the right & easiest solution. just need to save all list of subscribed topics to prefs or wherever, then loop them to unsubscribe at once. – Taufik Nur Rahmanda Jun 10 '22 at 08:58
2

Keep a private list of subscribed topics in Preferences.

It's not that hard. Here's what I do:

public class PushMessagingSubscription {

  private static SharedPreferences topics;

  public static void init(ApplicationSingleton applicationSingleton) {
    topics = applicationSingleton.getSharedPreferences("pushMessagingSubscription", 0);
  }

  public static void subscribeTopic(String topic) {
    if (topics.contains(topic)) return; // Don't re-subscribe
    topics.edit().putBoolean(topic, true).apply();

    // Go on and subscribe ...
  }


  public static void unsubscribeAllTopics() {
    for (String topic : topics.getAll().keySet()) {
      FirebaseMessaging.getInstance().unsubscribeFromTopic(topic);
    }
    topics.edit().clear().apply();
    // FirebaseInstanceId.getInstance().deleteInstanceId();
  }
}
Jacob Nordfalk
  • 3,533
  • 1
  • 21
  • 21
2

For Java users:

If you want to do it topic wise, refer others answers and If you want to stop recieving FCM push notification, do below:

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            FirebaseInstanceId.getInstance().deleteInstanceId();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

I have placed deleteInstanceId() in a separate thread to stop java.io.IOException: MAIN_THREAD W/System.err and wrapped with try / catch to handle IOException.

Blasanka
  • 21,001
  • 12
  • 102
  • 104
1
Firebase.messaging.deleteToken()

Is actual answer for 2021.

Valentin Yuryev
  • 764
  • 7
  • 18
0

We can use the unsubcribeAllTopics in server side as below.

Example,

interface GetTopics {
  rel: {topics: {[key: string]: any}}
}

/**
 * Unsubcribe all topics except one topic
 * 
 * Example response of `https://iid.googleapis.com/iid/info/${fcmToken}?details=true`
 * {
    "applicationVersion": "...",
    "application": "...",
    "scope": "*",
    "authorizedEntity": "...",
    "rel": {
        "topics": {
            "TOPIC_KEY_STRING": { // topic key
                "addDate": "2020-12-23"
            }
        }
    },
    "appSigner": "...",
    "platform": "ANDROID"
}
 */
export const unsubcribeAllTopics = async (
  fcmTokens: string | string[],
  exceptTopic?: string,
) => {
  const headers = {
    'Content-Type': 'application/json',
    Authorization: `key=${process.env.FCM_SERVER_KEY}`,
  }

  const url = `https://iid.googleapis.com/iid/info/${fcmTokens}?details=true`

  try {
    const response = await fetch(url, {method: 'GET', headers: headers})
    const result: GetTopics = await response.json()
    const keys = Object.keys(result.rel.topics)

    keys.forEach(key => {
      key !== exceptTopic &&
        messaging()
          .unsubscribeFromTopic(fcmTokens, key)
          .catch(error => {
            console.error('error', {data: error})
          })
    })
  } catch (error) {
    console.error('error', {data: error})
  }
}

https://gist.github.com/JeffGuKang/62c280356b5632ccbb6cf146e2bc4b9d

Jeff Gu Kang
  • 4,749
  • 2
  • 36
  • 44
0

if you are using flutter if you want to unsubscribe from all topics use-

  final FirebaseMessaging _fcm = FirebaseMessaging();
  _fcm.deleteInstanceID().then((value){
                print('deleted all'+value.toString());
              });
Rahul Repala
  • 99
  • 1
  • 6
-2
try {
    FirebaseInstallations.getInstance().delete()
} catch (e: IOException) {

}
Amir Dora.
  • 2,831
  • 4
  • 40
  • 61