Search code examples
iosfirebaserubymotionfirebase-realtime-database

How can I track the current number of viewers of an item?


I have an iPhone app, where I want to show how many people are currently viewing an item as such:

Currently viewing

I'm doing that by running this transaction when people enter a view (Rubymotion code below, but functions exactly like the Firebase iOS SDK):

listing_reference[:listings][self.id][:viewing_amount].transaction do |data|
  data.value = data.value.to_i + 1
  FTransactionResult.successWithValue(data)
end

And when they exit the view:

listing_reference[:listings][self.id][:viewing_amount].transaction do |data|
  data.value = data.value.to_i + -
  FTransactionResult.successWithValue(data)
end

It works fine most of the time, but sometimes things go wrong. The app crashes, people loose connectivity or similar things.

I've been looking at "onDisconnect" to solve this - https://firebase.google.com/docs/reference/ios/firebasedatabase/interface_f_i_r_database_reference#method-detail - but from what I can see, there's no "inDisconnectRunTransaction".

How can I make sure that the viewing amount on the listing gets decremented no matter what?


Solution

  • A Firebase Database transaction runs as a compare-and-set operation: given the current value of a node, your code specifies the new value. This requires at least one round-trip between the client and server, which means that it is inherently unsuitable for onDisconnect() operations.

    The onDisconnect() handler is instead a simple set() operation: you specify when you attach the handler, what write operation you want to happen when the servers detects that the client has disconnected (either cleanly or as in your problem case involuntarily).

    The solution is (as is often the case with NoSQL databases) to use a data model that deals with the situation gracefully. In your case it seems most natural to not store the count of viewers, but instead the uid of each viewer:

    itemViewers
      $itemId
        uid_1: true
        uid_2: true
        uid_3: true
    

    Now you can get the number of viewers with a simple value listener:

    ref.child('itemViewers').child(itemId).on('value', function(snapshot) {
      console.log(snapshot.numChildren());
    });
    

    And use the following onDisconnect() to clean up:

    ref.child('itemViewers').child(itemId).child(authData.uid).remove();
    

    Both code snippets are in JavaScript syntax, because I only noticed you're using Swift after typing them.