7

I'm currently trying to migrate my app to firebase and I'm looking for the Firebase equivalent of Parse Installations and Channels.

What I've found is that we are supposed to use topics however in my app "subscribing" and "unsubscribing" to topics is common yet there is no way (that I have found) to see what topics a user is subscribed to. Any ideas?

I've looked through the Firebase documentation but I'm new to Firebase so maybe someone with more experience would know: https://firebase.google.com/docs/cloud-messaging/android/topic-messaging#managing_topic_subscriptions_from_the_server

Thanks in advance for any help!

Kevin R
  • 83
  • 2
  • 9

3 Answers3

1

FCM topic subscriptions are based on an application's Instance ID, so when you subscribe or unsubscribe to or from a topic the IID is used.

You can use the Instance ID API to get information about a particular IID, this information includes the topics that the IID is currently subscribed to. See the reference

Arthur Thompson
  • 9,087
  • 4
  • 29
  • 33
  • So if I wanted there to be a local record of what topics a user was subscribed to I would have to do it all manually? – Kevin R Aug 16 '16 at 05:38
  • Yes, you would have to maintain those records yourself on the client side. – Arthur Thompson Aug 16 '16 at 22:22
  • I see, that's unfortunate. Anyway, thanks for your help! I will mark you as the answer. – Kevin R Aug 17 '16 at 02:18
  • I understand that it would be convenient to know what topics a client was subscribed to on the client side, but given that a client could be subscribed or unsubscribed on the server side the client may not always know the truth about the client's subscription status. So taking action based on what the client knows about subscriptions may result in unexpected behavior. So the safe thing (for now) would be to manage the subscription state yourself. Your feedback is appreciated we will keep it in mind for future releases. – Arthur Thompson Aug 17 '16 at 13:10
  • Consider using the Firebase real-time database as a way to keep the client and server in sync wrt client subscriptions. – Arthur Thompson Aug 17 '16 at 13:15
1

You have to make a request to the Instance ID API. I created an extension

https://gist.github.com/eduardo22i/0ee8960c36659b17fbc538964e929cac

extension Messaging {

static private let accessToken = "" //Server Web Key

struct Topic : Decodable {
    var name : String?
    var addDate : String?
}

struct Rel : Decodable {
    var topics = [Topic]()

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let relContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .rel)
        let topicsContainer = try relContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .topics )

        for key in topicsContainer.allKeys {
            var topic = Topic()
            topic.name = key.stringValue

            let topicContainer = try? topicsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: key)
            topic.addDate = try! topicContainer?.decode(String.self, forKey: .addDate)

            self.topics.append(topic)
        }
    }

    struct CodingKeys : CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }

        static let rel = CodingKeys(stringValue: "rel")!
        static let topics = CodingKeys(stringValue: "topics")!
        static let addDate = CodingKeys(stringValue: "addDate")!
    }
}

func loadTopics(block : @escaping (_ topics: [Messaging.Topic]?, _ error: Error?) -> Void ) {
    if let token = InstanceID.instanceID().token() {
        let url = URL(string: "https://iid.googleapis.com/iid/info/\(token)?details=true")!
        var request = URLRequest(url: url)
        request.addValue("key=\(Messaging.accessToken)", forHTTPHeaderField: "Authorization")
        let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
        DataManager.shared.make(request: urlRequest, block: { (data, error) in
            if let data = data {
                let decoder = JSONDecoder()
                let rel = try? decoder.decode(Rel.self, from: data)
                block(rel?.topics, error)
            } else {
                block(nil, error)
            }
        }

        dataTask.resume()
    }
}
}

// Usage

Messaging.messaging().loadTopics { (topics, error) in
    topics?.forEach({ (topic) in
        print(topic.name)
    })
}
Eduardo Irias
  • 1,043
  • 11
  • 12
  • 1
    Nice man! I got it working, only 2 small notes: 1) This line should be removed: "DataManager.shared.make(request: urlRequest, block: { (data, error) in" 2) For other people using the code, make sure to fill out the "accesstoken" on top of his code. Which is the API key for your project and be found under: (gear-next-to-project-name) > Project Settings > Cloud Messaging -> Server Key is the API key. – Vasco Dec 18 '19 at 12:15
  • Unfortunately the code is not working anymore... could anyone share an update? @Vasco – Nico S. Dec 22 '19 at 10:38
1

Here is the update from @Eduardo's, requested by @Nico

2 things:

  • You need to import Firebase, since you'll need the InstanceID and the messaging module.
  • Make sure you fill out the API key (accessToken), which can be found under: (gear-next-to-project-name) > Project Settings > Cloud Messaging -> Server Key is the API key.

This is the updated code:

import Firebase


// Grab susbcribed channels
// Source 1: https://stackoverflow.com/questions/38948720/is-there-anyway-to-access-the-firebase-topics-a-user-is-subscribed-to
// Source 2: https://developers.google.com/instance-id/reference/server#get_information_about_app_instances
extension Messaging {

// Needs to be grabbed from: https://stackoverflow.com/questions/37337512/where-can-i-find-the-api-key-for-firebase-cloud-messaging
static private let accessToken = ""

struct Topic : Decodable {
    var name : String?
    var addDate : String?
}

struct Rel : Decodable {
    var topics = [Topic]()

    init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let relContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .rel)
        let topicsContainer = try relContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .topics )

        for key in topicsContainer.allKeys {
            var topic = Topic()
            topic.name = key.stringValue

            let topicContainer = try? topicsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: key)
            topic.addDate = try! topicContainer?.decode(String.self, forKey: .addDate)

            self.topics.append(topic)
        }
    }

    struct CodingKeys : CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }

        static let rel = CodingKeys(stringValue: "rel")!
        static let topics = CodingKeys(stringValue: "topics")!
        static let addDate = CodingKeys(stringValue: "addDate")!
    }
}

func loadTopics(block : @escaping (_ topics: [Messaging.Topic]?, _ error: Error?) -> Void ) {
    InstanceID.instanceID().instanceID { (result, error) in
        if let err = error {
            block(nil, err)
        } else if let result = result {
            let url = URL(string: "https://iid.googleapis.com/iid/info/\(result.token)?details=true")!
            var request = URLRequest(url: url)
            request.addValue("key=\(Messaging.accessToken)", forHTTPHeaderField: "Authorization")
            let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
                if let data = data {
                    let decoder = JSONDecoder()
                    let rel = try? decoder.decode(Rel.self, from: data)
                    block(rel?.topics, error)
                } else {
                    block(nil, error)
                }
            }
            dataTask.resume()
        }
    }
}
}

Usage is like this:

Messaging.messaging().loadTopics { (topics, error) in
   topics?.forEach({ (topic) in
       print("Subscribed to topic: \(topic.name ?? "No name")")
   })
}
Vasco
  • 837
  • 8
  • 9