Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
#54 Support for Push Notifications - Android impl done!
Browse files Browse the repository at this point in the history
  • Loading branch information
EddyVerbruggen committed Jun 26, 2016
1 parent 191c6d3 commit b4bee77
Show file tree
Hide file tree
Showing 39 changed files with 777 additions and 19 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
platforms/android/libraryproject/
41 changes: 39 additions & 2 deletions docs/MESSAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ Version 3.3.0 of this plugin added FCM support (which is the successor of GCM).
Although using push messages in your Firebase app is really easy setting it up is not. Traditionally, especially for iOS.

### Android
Work in progress.
Uncomment `firebase-messaging` in [include.gradle](../platforms/android/include.gradle)

### iOS
Uncomment `Firebase/Messaging` in [Podfile](../platforms/ios/Podfile)

#### Receiving remote notifications in the background
Open `app/App_Resources/iOS/Info.plist` and add this to the bottom:
Expand Down Expand Up @@ -43,6 +44,7 @@ Any pending notifications (while your app was not in the foreground) will trigge
console.log("Body: " + message.body);
// if your server passed a custom property called 'foo', then do this:
console.log("Value of 'foo': " + message.foo);
}
});
```

Expand All @@ -52,6 +54,41 @@ You don't _have_ to provide the handler during `init` - you can also do it throu
firebase.addOnMessageReceivedCallback(
function(message) {
// ..
});
}
);
```

### Pushing to individual devices
If you want to send push messages to individual devices, either from your own backend or the FCM console, you need the push token.

Similarly to the message callback you can either wire this through `init` or as a separate function:

```js
firebase.init({
onPushTokenReceivedCallback: function(token) {
console.log("Firebase push token: " + token);
}
});
```

.. or:

```js
firebase.addOnPushTokenReceivedCallback(
function(token) {
// ..
}
);
```

## Testing
Using the Firebase Console gives you most flexibility, but you can quickly and easily test from the command line as well:

```
curl -X POST --header "Authorization: key=SERVER_KEY" --Header "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d "{\"notification\":{\"title\": \"My title\", \"text\": \"My text\", \"sound\": \"default\"}, \"priority\": \"High\", \"to\": \"DEVICE_TOKEN\"}"
```

* SERVER_KEY: see below
* DEVICE_TOKEN: the on you got in `addOnPushTokenReceivedCallback` or `init`'s `onPushTokenReceivedCallback`

<img src="images/push-server-key.png" width="459px" height="220px" alt="Push server key"/>
Binary file added docs/images/push-server-key.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions firebase-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,10 @@ firebase.QueryRangeType = {
};

firebase.instance = null;

firebase.firebaseRemoteConfig = null;

firebase.authStateListeners = [];

firebase._receivedNotificationCallback = null;

firebase._pendingNotifications = [];

firebase.addAuthStateListener = function(listener) {
if (firebase.authStateListeners.indexOf(listener) === -1) {
firebase.authStateListeners.push(listener);
Expand Down
101 changes: 95 additions & 6 deletions firebase.android.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
var appModule = require("application");
var firebase = require("./firebase-common");

firebase._launchNotification = null;

(function() {
if (typeof(com.google.firebase.messaging) === "undefined") {
return;
}
appModule.onLaunch = function(intent) {
var extras = intent.getExtras();
if (extras !== null) {
var result = {
foreground: false
};

var iterator = extras.keySet().iterator();
while (iterator.hasNext()) {
var key = iterator.next();
if (key !== "from" && key !== "collapse_key") {
result[key] = extras.get(key);
}
}

// in case this was a cold start we don't have the _receivedNotificationCallback yet
if (firebase._receivedNotificationCallback === null) {
firebase._launchNotification = result;
} else {
// add a little delay just to make sure clients alerting this message will see it as the UI needs to settle
setTimeout(function() {
firebase._receivedNotificationCallback(result);
});
}
}
};

})();

firebase.toHashMap = function(obj) {
var node = new java.util.HashMap();
for (var property in obj) {
Expand Down Expand Up @@ -134,13 +169,12 @@ firebase.init = function (arg) {

// Firebase notifications (FCM)
if (typeof(com.google.firebase.messaging) !== "undefined") {
console.log("--- has messaging!");
// TODO see iOS:
// firebase._addObserver(kFIRInstanceIDTokenRefreshNotification, firebase._onTokenRefreshNotification);
if (arg.onMessageReceivedCallback !== undefined) {
console.log("--- adding messaging callback!");
firebase.addOnMessageReceivedCallback(arg.onMessageReceivedCallback);
}
if (arg.onPushTokenReceivedCallback !== undefined) {
firebase.addOnPushTokenReceivedCallback(arg.onPushTokenReceivedCallback);
}
}

resolve(firebase.instance);
Expand All @@ -160,7 +194,22 @@ firebase.addOnMessageReceivedCallback = function (callback) {
}

firebase._receivedNotificationCallback = callback;
firebase._processPendingNotifications();

org.nativescript.plugins.firebase.FirebasePlugin.setOnNotificationReceivedCallback(
new org.nativescript.plugins.firebase.FirebasePluginListener({
success: function(notification) {
console.log("---------- received notification: " + notification);
callback(JSON.parse(notification));
}
})
);

// if the app was launched from a notification, process it now
if (firebase._launchNotification !== null) {
callback(firebase._launchNotification);
firebase._launchNotification = null;
}

resolve();
} catch (ex) {
console.log("Error in firebase.addOnMessageReceivedCallback: " + ex);
Expand All @@ -169,6 +218,30 @@ firebase.addOnMessageReceivedCallback = function (callback) {
});
};

firebase.addOnPushTokenReceivedCallback = function (callback) {
return new Promise(function (resolve, reject) {
try {
if (typeof(com.google.firebase.messaging) === "undefined") {
reject("Uncomment firebase-messaging in the plugin's include.gradle first");
return;
}

org.nativescript.plugins.firebase.FirebasePlugin.setOnPushTokenReceivedCallback(
new org.nativescript.plugins.firebase.FirebasePluginListener({
success: function(token) {
callback(token);
}
})
);

resolve();
} catch (ex) {
console.log("Error in firebase.addOnPushTokenReceivedCallback: " + ex);
reject(ex);
}
});
};

firebase.getRemoteConfigDefaults = function (properties) {
var defaults = {};
for (var p in properties) {
Expand All @@ -180,6 +253,13 @@ firebase.getRemoteConfigDefaults = function (properties) {
return defaults;
};

firebase._isGooglePlayServicesAvailable = function () {
var context = appModule.android.foregroundActivity;
var playServiceStatusSuccess = com.google.android.gms.common.ConnectionResult.SUCCESS; // 0
var playServicesStatus = com.google.android.gms.common.GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
return playServicesStatus === playServiceStatusSuccess;
};

firebase.getRemoteConfig = function (arg) {
return new Promise(function (resolve, reject) {
try {
Expand All @@ -193,6 +273,11 @@ firebase.getRemoteConfig = function (arg) {
return;
}

if (!firebase._isGooglePlayServicesAvailable()) {
reject("Google Play services is required for this feature, but not available on this device");
return;
}

// Get a Remote Config object instance
firebaseRemoteConfig = com.google.firebase.remoteconfig.FirebaseRemoteConfig.getInstance();

Expand Down Expand Up @@ -236,7 +321,6 @@ firebase.getRemoteConfig = function (arg) {

var onFailureListener = new com.google.android.gms.tasks.OnFailureListener({
onFailure: function (exception) {
console.log("--- onFailureListener: " + exception);
if (exception == "com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException") {
returnMethod(true);
} else {
Expand Down Expand Up @@ -323,6 +407,11 @@ function toLoginResult(user) {
firebase.login = function (arg) {
return new Promise(function (resolve, reject) {
try {
if (!firebase._isGooglePlayServicesAvailable()) {
reject("Google Play services is required for this feature, but not available on this device");
return;
}

var firebaseAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
var onCompleteListener = new com.google.android.gms.tasks.OnCompleteListener({
onComplete: function (task) {
Expand Down
12 changes: 10 additions & 2 deletions firebase.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,20 +203,27 @@ declare module "nativescript-plugin-firebase" {

/**
* The returned object in the callback handler of the addOnMessageReceivedCallback function.
*
* Note that any custom data you send from your server will be available as
* key/value properties on the Message object.
*/
export interface Message {
/**
* Indicated whether or not the notification was received while the app was in the foreground.
*/
foreground: boolean;
/**
* The main text shown in the notificiation.
* Not available on Android when the notification was received in the background.
*/
body: string;
body?: string;
/**
* Optional title, shown above the body in the notification.
* Not available on Android when the notification was received in the background.
*/
title?: string;
/**
* iOS badge number
* iOS badge count, as sent from the server.
*/
badge?: number;
}
Expand All @@ -226,6 +233,7 @@ declare module "nativescript-plugin-firebase" {
export function logout(): Promise<any>;
export function getRemoteConfig(options: GetRemoteConfigOptions): Promise<GetRemoteConfigResult>;
export function addOnMessageReceivedCallback(onMessageReceived: (data: Message) => void): Promise<any>;
export function addOnPushTokenReceivedCallback(onPushTokenReceived: (data: string) => void): Promise<any>;
export function createUser(options: CreateUserOptions): Promise<CreateUserResult>;
export function deleteUser(): Promise<any>;
export function resetPassword(options: ResetPasswordOptions): Promise<any>;
Expand Down
34 changes: 32 additions & 2 deletions firebase.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ var types = require("utils/types");
var frame = require("ui/frame");

firebase._messagingConnected = null;
firebase._pendingNotifications = [];
firebase._receivedPushTokenCallback = null;

firebase._addObserver = function (eventName, callback) {
return NSNotificationCenter.defaultCenter().addObserverForNameObjectQueueUsingBlock(eventName, null, NSOperationQueue.mainQueue(), callback);
Expand Down Expand Up @@ -36,14 +38,15 @@ firebase.addAppDelegateMethods = function(appDelegate) {
var userInfoJSON = firebase.toJsObject(userInfo);

if (application.applicationState === UIApplicationState.UIApplicationStateActive) {
// foreground
if (firebase._receivedNotificationCallback !== null) {
userInfoJSON.foreground = true;
firebase._receivedNotificationCallback(userInfoJSON);
} else {
userInfoJSON.foreground = false;
firebase._pendingNotifications.push(userInfoJSON);
}
} else {
// background
userInfoJSON.foreground = false;
firebase._pendingNotifications.push(userInfoJSON);
}
};
Expand All @@ -68,6 +71,23 @@ firebase.addOnMessageReceivedCallback = function (callback) {
});
};

firebase.addOnPushTokenReceivedCallback = function (callback) {
return new Promise(function (resolve, reject) {
try {
if (typeof(FIRMessaging) === "undefined") {
reject("Enable FIRMessaging in Podfile first");
return;
}

firebase._receivedPushTokenCallback = callback;
resolve();
} catch (ex) {
console.log("Error in firebase.addOnPushTokenReceivedCallback: " + ex);
reject(ex);
}
});
};

firebase._processPendingNotifications = function() {
if (firebase._receivedNotificationCallback !== null) {
for (var p in firebase._pendingNotifications) {
Expand Down Expand Up @@ -252,9 +272,14 @@ firebase.init = function (arg) {
// Firebase notifications (FCM)
if (typeof(FIRMessaging) !== "undefined") {
firebase._addObserver(kFIRInstanceIDTokenRefreshNotification, firebase._onTokenRefreshNotification);

if (arg.onMessageReceivedCallback !== undefined) {
firebase.addOnMessageReceivedCallback(arg.onMessageReceivedCallback);
}

if (arg.onPushTokenReceivedCallback !== undefined) {
firebase.addOnPushTokenReceivedCallback(arg.onPushTokenReceivedCallback);
}
}

resolve(firebase.instance);
Expand All @@ -272,6 +297,11 @@ firebase._onTokenRefreshNotification = function (notification) {
}

console.log("Firebase FCM token received: " + token);

if (firebase._receivedPushTokenCallback) {
firebase._receivedPushTokenCallback(token);
}

FIRMessaging.messaging().connectWithCompletion(function(error) {
if (error !== null) {
// this is not fatal at all but still would like to know how often this happens
Expand Down
15 changes: 15 additions & 0 deletions platforms/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
For more sophisticated handling of Firebase Messaging we need to implement 2 services.
Those services must be configured in `AndroidManifest.xml` and we need to ship 2 additional classes.

To make it as easy as possible for consumers of this plugin we bundle those bith in an `.aar` file.

Steps to update the `.aar` file:

* Clone this repo
* Start Android Studio and pick 'import existing project' > `{this repo}/platforms/android/libraryproject`
* Update `firebase/src/main/AndroidManifest.xml` as needed
* Open the Gradle toolwindow
* Run `firebase > Tasks > build > build`
* The (release) `.aar` will be generated in `firebase/build/outputs/aar`
* Copy that to the `platforms/android` folder, replacing the old `.aar`
* Commit and push the changes as usual
Binary file added platforms/android/firebase-release.aar
Binary file not shown.
4 changes: 2 additions & 2 deletions platforms/android/include.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ repositories {
}

dependencies {
// make sure you have these versions by updating your local Android SDK's (Android Support repo and Google repo)
// make sure you have these versions by updating your local Android SDK's (Android Support repo and Google repo)
compile "com.google.firebase:firebase-database:9.0.2"
compile "com.google.firebase:firebase-auth:9.0.2"

// for reading google-services.json and configuration
compile "com.google.android.gms:play-services:9.0.2"
compile "com.google.android.gms:play-services-base:9.0.2"

// Uncomment if you want to use 'Remote Config'
// compile "com.google.firebase:firebase-config:9.0.2"
Expand Down
8 changes: 8 additions & 0 deletions platforms/android/libraryproject/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
Loading

0 comments on commit b4bee77

Please sign in to comment.