Pusharoo (version 2)

Setup

The following 4 steps are required to make Pusharoo work in your project

1. Project setup

Add Maven repository

https://mvn.tickaroo.com

to your project and

com.tickaroo:pusharoo2:2.1.18
com.tickaroo:pusharoo2-data:2.1.18

to the dependencies of your app.

2. Firebase setup

// root build.gralde
buildscript {
  dependencies {
    ...
    classpath 'com.google.gms:google-services:4.2.0' // PlayServices
  }
}
// app module build.gradle
// at the very end of the file
apply plugin: 'com.google.gms.google-services'

Also, you have to add google-service.json to your app module. If you create a new project from scratch: Android Studio offers a UI wizard to do all the steps for you. Check in Android Studio (menu bar) Tools -> Firebase If you have have a GCM powered module please see this migration guide to generate the google-services.json file properly.

3. Setup backend authentication token

You have to provide a string xml resource file with R.string.pusharoo_backend_auth_token like this:

<!-- i.e. in strings.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="pusharoo_backend_auth_token">MySubscribeTokenFromThePushServiceAdminPanel</string>
</resources>

4. Register Service to receive push notifications

In your app’s AndroidManifest.xml you have to register a service that extends from PusharooFirebaseMessagingService

<!-- AndroidManifest.xml of your app -->
<service
    android:name=".MyPushMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
class MyPushMessagingService extends PusharooFirebaseMessagingService {

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {

    // TODO(developer): Handle FCM messages here i.e. build a notification
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ

    // Check if message contains a data payload.
    Map<String, String> data = remoteMessage.getData();
    if (data.size() > 0) {
      String headline =  data.get("headline");
      String textContent =  data.get("message");
      String dataPayload =  data.get("data");
      String gcmDataPayload =  data.get("gcmData"); // Optional

      ... // show Android notification etc. (must be done within 10 seconds)

    }
  }

}

Please note that handling a message must be done in less then 10 seconds. More information can be found here. If handling an incomming push notification takes longer than a few seconds (like downloading an image could take more then 10 seconds on slow internet connections) you better start a new background service to do the long running task (i.e. IntentService).

API and Usage

This chapter describes how to use Pusharoo and best practices when working with Pusharoo. First of all, Pusharoo is a singleton instance. Use Pusharoo pusharoo = Pusharoo.with(context) to get the singleton instance.

Pusharoo internally uses RxJava2 and only works with FCM (Firebase Cloud Messaging).

Initialization and Dependency Injection

Pusharoo is a singleton instance and must be initialized at least once with Pushraoo.with(context) which also returns the Pusharoo singleton instance. Any further call of Pusharoo.with(context) has not effect and will return the same Pusharoo singleton instance.

Therefore, best practice is to initialize Pusharoo once in your dependency injection framework (i.e. Dagger) like this:

// i.e. in your Dagger Module
@Provides
@Singleton
public Pusharoo providePusharoo(Context context) {
   return Pusharoo.with(context); // uses context.getApplicationContext() internally
}

If you need a Pusharoo instance to subscribe / cancel subscriptions inject (@Inject) the Pusharoo instance where you need it and call pusharoo.updateSubscriptions(...) instead of Pusharoo.with(context).updateSubscriptions(...). You can also store it in a global static variable if you don’t use dependency injection.

Subscription and Channel

Pusharoo works “channel based”. A Channel is basically just a string. A (app) user can subscribe / unsubscribe to channels. You can build a subchannel hierarchy by giving channels well formed names. Subchannels works with prefix matching. For instance game.123 and game.123.goal where game.123.goal can be seen as subchannel of game.123 since it has the prefix game.123. Please note that the dot (.) is just a character and has no deeper meaning internally.

Also a subscription has an expiresAt field which is a unix timestamp in seconds (not milliseconds). This field helps the backend to delete “expired” subscriptions, i.e. a subscription for a football game: Since the game takes place at a given date, we can set the expiresAt timestamp to date + 1 year since we know that the game must take place within a year (if rescheduled).

Subscribe to a channel

Subscription s1 = Subscription.subscribe("myChannel", expiresAtUnixTimestampInSeconds); // Unix Timestamp in seconds (not milliseconds)
Subscription s2 = Subscription.subscribe("anotherChannel"); // never expires
List<Subscription> subscriptions = Arrays.asList(s1, s2);

Completable c = pusharoo.updateSubscriptions(subscriptions);
c.blockingAwait(); //  or c.subscribe( successCallback, errorCallback ) if you prefer async

Unsubscribe (cancel subscription for a channel)

Subscription s1 = Subscription.cancel("myChannel");
Subscription s2 = Subscription.cancel("anotherChannel");
List<Subscription> subscriptions = Arrays.asList(s1, s2);

Completable c = pusharoo.updateSubscriptions(subscriptions);
c.blockingAwait(); //  or c.subscribe( successCallback, errorCallback ) if you prefer async

Get List of subscriptions

There are two methods that can be invoked to check if the user is actively subscribed to a given channel:

  • pusharoo.getSubscriptions(List<String> channelNames): Get a List of Subscription that exactly match the given channel names (concatenated with OR). For example: if chanelNames = "foo", "bar" it will return subscriptions for the channels “foo” or “bar”, i.e. the resulting list could be just “foo” if only subscribed to channel with name “foo” (but not subscribed to “bar”).
  • pusharoo.getSubscriptionsInclSubChannel(List<String> chanelNamePrefixes): Get a List of Subscription by matching the channel name prefixes (concatenated with OR). For example: if chanelNamePrefixes = "foo", "bar" it will return subscribptions for channels "foo", "foo.sub.channel", "bar", "baromether", ... as those have a “foo” or “bar” as prefix.

Both methods return an Observable<List<Subscription>> so you will automatically receive updates if the subscription for a channel has been changed (i.e. cancelled).

Reset all subscriptions

Call resetSubscriptions() to reset all subscriptions. This means that all subscriptions are canceled. Actually it is not exactly the same as cancelling each subscription manually, but rather it is a “hard reset” on the backend.

Completable c = pusharoo.resetSubscriptions();
c.blockingAwait()  //  or c.subscribe( successCallback, errorCallback ) if you prefer async

There is a small chance (very unlikely though) that while executing the “hard reset” a push notification is received. This is not handled by Pusharoo. Handling this scenario properly is the responsibility of an app developer. For example a common strategy is to check if the user is still subscribed to the given channel while receiving a push notification. Since this scenario is very unlikely to happen it is also okay to not handle it at all.

Is Pusharoo pending?

Pending means that Pusharoo still has some work to do before being fully synced (this work is scheduled to run, but hasn’t run or completed yet). In other words: It emits true if either subscriptions are waiting to be synced with the backend or if a reset has been required but the “reset” must be sent to the backend.

Observable<Boolean> pendingObservable = pusharoo.hasPendingSubscriptions();
pendingObservable.blockingFirst()  //  or c.subscribe( nextCallback, errorCallback) if you prefer async. Please note that onComplete() will never be called

Play Service checks

FCM relies on Google Play Services. Therefore, Play services must be installed on the user’s device. Whenever you call pusharoo.updateSubscriptions(...) or pusharoo.reset() Pusharoo will check if play services are installed. If not, a PlayServiceException will be thrown. This class contains an errorCode that is the error code play services internally uses. Then it’s up to you to implement error handling properly, i.e. ask the user to install play services on his device. GoogleApiAvailability class offers some helper methods to display such a dialog. However, this is not part of Pusharoo library, as this belongs to the view layer (from a software architectural point of view), whereas Pusharoo is “business logic” (and not View layer).

Good to know

  • FCM / GCM assign a new push token whenever the app is (re)installed. So there is no need to call Pusharoo.reset() at very first app start.
  • Optimistic Synchronisation: Pusharoo’s subscription synchronisation is designed with “optimistic Synchronisation” in mind which means that one can assume that after calling pusharoo.updateSubscriptions(...) the user is successfully subscribed / unsubscribed for a channel without having to worry if the local (on android device) subscription has already been synced with push backend (executed http request). The Pusharoo takes care of that kind of thoughts. The basic idea is: you can assume the subscription was successful without concerning active internet connection because if there is no active connection Pusharoo automatically syncs once an active internet connection is available. In the meantime (no internet connection) the user will not receive any push notifications because of missing internet connection.
  • Pusharoo works with doze mode etc. because it uses Android’s JobScheduler under the hoods (Lollipop API 21 and above) to schedule sync.
  • pusharoo.updateSubscriptions(...) and pusharoo.reset() must run on a background thread (not main UI Thread). Use RxJava’s .observeOn(...) operator.