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: ifchanelNames = "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: ifchanelNamePrefixes = "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(...)
andpusharoo.reset()
must run on a background thread (not main UI Thread). Use RxJava’s.observeOn(...)
operator.