├── .gitignore ├── .travis.yml ├── README.md ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── test │ │ ├── MainActivity.java │ │ ├── MainApp.java │ │ ├── MyCustomReceiver.java │ │ └── ShowPopUp.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-ldpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_main.xml │ └── popupdialog.xml │ ├── menu │ └── activity_main.xml │ ├── values-v11 │ └── styles.xml │ ├── values-v14 │ └── styles.xml │ └── values │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | proguard-project.txt 25 | 26 | # Intellij project files 27 | *.iml 28 | *.ipr 29 | *.iws 30 | .idea/ 31 | 32 | *.pydevproject 33 | .project 34 | .metadata 35 | .gradle 36 | build/** 37 | bin/** 38 | tmp/** 39 | tmp/**/* 40 | *.tmp 41 | *.bak 42 | *.swp 43 | *~.nib 44 | local.properties 45 | .classpath 46 | .settings/ 47 | .loadpath 48 | 49 | # External tool builders 50 | .externalToolBuilders/ 51 | 52 | # Locally stored "Eclipse launch configurations" 53 | *.launch 54 | 55 | # CDT-specific 56 | .cproject 57 | 58 | # PDT-specific 59 | .buildpath 60 | 61 | # Android Studio project files 62 | *.iml 63 | .gradle 64 | .idea 65 | build 66 | import-summary.txt 67 | 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | sudo: false 5 | android: 6 | components: 7 | - tools 8 | - platform-tools 9 | - build-tools-27.0.3 10 | - android-27 11 | licenses: 12 | - android-sdk-license-.+ 13 | before_install: 14 | - yes | sdkmanager "platforms;android-27" 15 | script: 16 | - "./gradlew build check --daemon" 17 | after_failure: "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results.xml" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parse Push Notification Sample Code 2 | 3 | This sample code demonstrates a working example of push notifications with Parse! This source code was originally created by Vishal Kapoor as part of our Jan 2015 Android bootcamp. The code has been updated significantly by Roger Hu on March 3rd, 2016 to reflect Parse Server. 4 | 5 | See our [configuring parse server push notifications guide](https://guides.codepath.com/android/Configuring-a-Parse-Server#enabling-push-notifications) for more details. Make sure to add a Parse Cloud function (`cloud/main.js`) similar to the one defined [here](https://github.com/codepath/parse-server-example/blob/master/cloud/main.js). 6 | 7 | ### Setup 8 | 9 | 1. Configure your Firebase app in the [console](https://console.firebase.google.com) and register a new Android app. 10 | 11 | 2. Make sure to record the Android package name used and download the `google-services.json` file. Add it to your `app/` dir (see [these docs](https://developers.google.com/android/guides/google-services-plugin#adding_the_json_file) more information.) 12 | 13 | 3. Verify the `applicationId` in your AndroidManifest.xml matches the package name defined for your Firebase app. 14 | 15 | 4. Update your `PARSE_CLOUD_SERVER_URL` in `MainApp.java`. 16 | 17 | 5. Update your `PARSE_APP_ID` in `MainApp.java`. 18 | 19 | Make sure you have your Parse cloud server configured with the `pushChannelTest` function: 20 | 21 | ```javascript 22 | // Depends on this function: https://github.com/codepath/parse-server-example/blob/master/cloud/main.js 23 | Parse.Cloud.define('pushChannelTest', function(request, response) { 24 | 25 | // request has 2 parameters: params passed by the client and the authorized user 26 | var params = request.params; 27 | var user = request.user; 28 | 29 | // extract out the channel to send 30 | var action = params.action; 31 | var message = params.message; 32 | var customData = params.customData; 33 | 34 | // use to custom tweak whatever payload you wish to send 35 | var pushQuery = new Parse.Query(Parse.Installation); 36 | pushQuery.equalTo("deviceType", "android"); 37 | 38 | var payload = {"data": { 39 | "alert": message, 40 | "action": action, 41 | "customdata": customData} 42 | }; 43 | 44 | // Note that useMasterKey is necessary for Push notifications to succeed. 45 | 46 | Parse.Push.send({ 47 | where: pushQuery, // for sending to a specific channel data: payload, 48 | }, { success: function() { 49 | console.log("#### PUSH OK"); 50 | }, error: function(error) { 51 | console.log("#### PUSH ERROR" + error.message); 52 | }, useMasterKey: true}); 53 | 54 | response.success('success'); 55 | }); 56 | 57 | ``` 58 | 59 | You can verify that the Parse Cloud function works by using this curl command (make sure the application ID matches): 60 | 61 | ```bash 62 | curl -X POST -H "X-Parse-Application-Id: myAppId" -H "Content-Type: application/json" -d '{"data": {"alert": "My message"}}' https://yourappname.herokuapp.com/parse/functions/pushChannelTest 63 | ``` 64 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion '27.0.3' 6 | 7 | defaultConfig { 8 | applicationId "com.codepath.ParsePushNotificationExample" // this has to match in Firebase 9 | minSdkVersion 16 10 | targetSdkVersion 27 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | ext { 22 | parseVersion = "1.17.1" 23 | } 24 | dependencies { 25 | implementation 'com.android.support:support-v4:27.1.1' 26 | implementation "com.parse:parse-android:$parseVersion" 27 | implementation "com.parse:parse-fcm-android:$parseVersion" 28 | implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' 29 | implementation 'com.facebook.stetho:stetho:1.5.0' 30 | } 31 | 32 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | --> 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/test/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | 4 | import com.parse.ParseCloud; 5 | 6 | import android.app.Activity; 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.IntentFilter; 11 | import android.os.Bundle; 12 | import android.support.v4.content.LocalBroadcastManager; 13 | import android.view.Menu; 14 | import android.view.View; 15 | import android.view.View.OnClickListener; 16 | import android.widget.Button; 17 | import android.widget.Toast; 18 | 19 | import java.util.HashMap; 20 | 21 | public class MainActivity extends Activity implements OnClickListener { 22 | 23 | private Button push; 24 | 25 | private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 26 | 27 | @Override 28 | public void onReceive(Context context, Intent intent) { 29 | Toast.makeText(getApplicationContext(), "onReceive invoked!", Toast.LENGTH_LONG).show(); 30 | } 31 | }; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_main); 37 | 38 | push = (Button)findViewById(R.id.senPushB); 39 | push.setOnClickListener(this); 40 | } 41 | 42 | @Override 43 | public void onPause() { 44 | super.onPause(); 45 | 46 | LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver); 47 | } 48 | 49 | @Override 50 | public void onResume() { 51 | super.onResume(); 52 | 53 | LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, new IntentFilter(MyCustomReceiver.intentAction)); 54 | } 55 | 56 | @Override 57 | public boolean onCreateOptionsMenu(Menu menu) { 58 | // Inflate the menu; this adds items to the action bar if it is present. 59 | getMenuInflater().inflate(R.menu.activity_main, menu); 60 | return true; 61 | } 62 | 63 | @Override 64 | public void onClick(View v) { 65 | 66 | HashMap payload = new HashMap<>(); 67 | payload.put("customData", "My message"); 68 | ParseCloud.callFunctionInBackground("pushChannelTest", payload); 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/test/MainApp.java: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | import android.app.Application; 4 | 5 | import com.parse.Parse; 6 | 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.logging.HttpLoggingInterceptor; 9 | 10 | public class MainApp extends Application { 11 | 12 | private static final String PARSE_APP_ID = "myAppId"; 13 | private static final String PARSE_CLOUD_SERVER_URL = "http://myherokuapp.herokuapp.com/parse/"; 14 | 15 | // Make sure to update gcm_sender_id in strings.xml!! 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | 21 | // FCM token will be automatically registered by ParseFirebaseJobService 22 | // Look for ParseFCM log messages to confirm 23 | Parse.setLogLevel(Parse.LOG_LEVEL_DEBUG); 24 | 25 | // Use for monitoring Parse OkHttp traffic 26 | // Can be Level.BASIC, Level.HEADERS, or Level.BODY 27 | // See http://square.github.io/okhttp/3.x/logging-interceptor/ to see the options. 28 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 29 | HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); 30 | httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 31 | builder.networkInterceptors().add(httpLoggingInterceptor); 32 | 33 | Parse.initialize(new Parse.Configuration.Builder(this) 34 | .applicationId(PARSE_APP_ID) 35 | .clientKey(null) // no client key needed in Parse open source 36 | .server(PARSE_CLOUD_SERVER_URL) // do not forget the URL needs to end with a trailing slash 37 | .clientBuilder(builder) 38 | .build()); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/test/MyCustomReceiver.java: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import android.app.NotificationManager; 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.support.v4.app.NotificationCompat; 11 | import android.support.v4.content.LocalBroadcastManager; 12 | import android.util.Log; 13 | 14 | import java.util.Iterator; 15 | 16 | public class MyCustomReceiver extends BroadcastReceiver { 17 | private static final String TAG = "MyCustomReceiver"; 18 | public static final String intentAction = "com.parse.push.intent.RECEIVE"; 19 | 20 | @Override 21 | public void onReceive(Context context, Intent intent) { 22 | if (intent == null) { 23 | Log.d(TAG, "Receiver intent null"); 24 | } else { 25 | // Parse push message and handle accordingly 26 | processPush(context, intent); 27 | } 28 | } 29 | 30 | private void processPush(Context context, Intent intent) { 31 | String action = intent.getAction(); 32 | Log.d(TAG, "got action " + action); 33 | if (action.equals(intentAction)) { 34 | String channel = intent.getExtras().getString("com.parse.Channel"); 35 | try { 36 | JSONObject json = new JSONObject(intent.getExtras().getString("com.parse.Data")); 37 | Log.d(TAG, "got action " + action + " on channel " + channel + " with:"); 38 | // Iterate the parse keys if needed 39 | Iterator itr = json.keys(); 40 | while (itr.hasNext()) { 41 | String key = (String) itr.next(); 42 | String value = json.getString(key); 43 | Log.d(TAG, "..." + key + " => " + value); 44 | // Extract custom push data 45 | if (key.equals("customdata")) { 46 | // create a local notification 47 | createNotification(context, value); 48 | } else if (key.equals("launch")) { 49 | // Handle push notification by invoking activity directly 50 | launchSomeActivity(context, value); 51 | } else if (key.equals("broadcast")) { 52 | // OR trigger a broadcast to activity 53 | triggerBroadcastToActivity(context, value); 54 | } 55 | } 56 | } catch (JSONException ex) { 57 | Log.d(TAG, "JSON failed!"); 58 | } 59 | } 60 | } 61 | 62 | public static final int NOTIFICATION_ID = 45; 63 | // Create a local dashboard notification to tell user about the event 64 | // See: http://guides.codepath.com/android/Notifications 65 | private void createNotification(Context context, String datavalue) { 66 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context).setSmallIcon( 67 | R.drawable.ic_launcher).setContentTitle("Notification: " + datavalue).setContentText("Pushed!"); 68 | NotificationManager mNotificationManager = (NotificationManager) context 69 | .getSystemService(Context.NOTIFICATION_SERVICE); 70 | mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); 71 | } 72 | 73 | // Handle push notification by invoking activity directly 74 | // See: http://guides.codepath.com/android/Using-Intents-to-Create-Flows 75 | private void launchSomeActivity(Context context, String datavalue) { 76 | Intent pupInt = new Intent(context, ShowPopUp.class); 77 | pupInt.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 78 | pupInt.putExtra("data", datavalue); 79 | context.getApplicationContext().startActivity(pupInt); 80 | } 81 | 82 | // Handle push notification by sending a local broadcast 83 | // to which the activity subscribes to 84 | // See: http://guides.codepath.com/android/Starting-Background-Services#communicating-with-a-broadcastreceiver 85 | private void triggerBroadcastToActivity(Context context, String datavalue) { 86 | Intent intent = new Intent(intentAction); 87 | intent.putExtra("data", datavalue); 88 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/test/ShowPopUp.java: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.view.View.OnClickListener; 7 | import android.widget.Button; 8 | 9 | public class ShowPopUp extends Activity implements OnClickListener { 10 | 11 | Button ok; 12 | Button cancel; 13 | 14 | boolean click = true; 15 | 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setTitle(getIntent().getStringExtra("customdata")); 20 | setContentView(R.layout.popupdialog); 21 | ok = (Button)findViewById(R.id.popOkB); 22 | ok.setOnClickListener(this); 23 | cancel = (Button)findViewById(R.id.popCancelB); 24 | cancel.setOnClickListener(this); 25 | } 26 | 27 | @Override 28 | public void onClick(View arg0) { 29 | // TODO Auto-generated method stub 30 | finish(); 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/ParsePushNotificationExample/b2983db8ea400691b56435d63d5792f91ebe7215/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/ParsePushNotificationExample/b2983db8ea400691b56435d63d5792f91ebe7215/app/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/ParsePushNotificationExample/b2983db8ea400691b56435d63d5792f91ebe7215/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/ParsePushNotificationExample/b2983db8ea400691b56435d63d5792f91ebe7215/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 |