Building an Android app — From scratch to pro! [2]

Basu
14 min readJun 19, 2020

We will play this song today from Surfaces.

In the last article, we learnt to create activities (the pages), adding buttons and other views. Remember, the key to doing good Android programming is research and self-initiative. You must do good research on it and practise a lot.

This article will complete the following modules:

  • Notification and extra features for your app [Part 2]
  • Building server-side with REST APIs [Part 2]
  • Packaging and publishing the app [Part 2]

Let’s get started.

Notification in Android: In the last article, I mentioned about Notification class in the suggested reading material. The Notification class manages all task related to sending and managing notification.

Definition: A notification is a message that Android displays outside your app’s UI to provide the user with reminders, communication from other people, or other timely information from your app. Users can tap the notification to open your app or take an action directly from the notification.

Everything has changed with Android 5.0 (Lollipop).

When some uncle in your family group on Whatsapp sends out a joke, the Whatsapp sends you a popup notification like this:

Not only this, but the notification can also now appear on the lock screen too. Let’s see the notification anatomy:

  1. Small icon: This is required and set with setSmallIcon().
  2. App name: This is provided by the system.
  3. Timestamp: This is provided by the system but you can override with setWhen() or hide it with setShowWhen(false).
  4. Large icon: This is optional (usually used only for contact photos; do not use it for your app icon) and set with setLargeIcon().
  5. Title: This is optional and set with setContentTitle().
  6. Text: This is optional and set with setContentText().

Notification Channel: Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel or it will not appear. By categorizing notifications into channels, users can disable specific notification channels for your app (instead of disabling all your notifications), and users can control the visual and auditory options for each channel — all from the Android system settings.

In this tutorial, we are following two steps to send a notification.

  1. Receiving notification from our server using Firebase Cloud Messaging (FCM)
  2. Displaying notification on the user’s mobile using Android’s notification class

Firebase Cloud Messaging: Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages at no cost. Using FCM, you can notify a client app that new email or other data is available to sync. You can send notification messages to drive user re-engagement and retention. For use cases such as instant messaging, a message can transfer a payload of up to 4KB to a client app.

How does it work?

An FCM implementation includes two main components for sending and receiving:

  1. A trusted environment such as Cloud Functions for Firebase or an app server on which to build, target, and send messages.
  2. An iOS, Android, or web (JavaScript) client app that receives messages via the corresponding platform-specific transport service.

You can send messages via the Firebase Admin SDK or the FCM server protocols.

Steps:

Register your app with Firebase
After you have a Firebase project, you can add your Android app to it.

Visit Understand Firebase Projects to learn more about best practices and considerations for adding apps to a Firebase project, including how to handle multiple build variants.

  1. Go to the Firebase console.
  2. In the centre of the project overview page, click the Android icon (plat_android) to launch the setup workflow. If you’ve already added an app to your Firebase project, click Add app to display the platform options.
  3. Enter your app’s package name in the Android package name field.

Make sure to enter the package name that your app is actually using. The package name value is case-sensitive, and it cannot be changed for this Firebase Android app after it’s registered with your Firebase project.

(Optional): Enter other app information: App nickname and Debug signing certificate SHA-1.

Click Register app.

Add a Firebase configuration file
Add the Firebase Android configuration file to your app:

  1. Click Download google-services.json to obtain your Firebase Android config file (google-services.json).
  2. Move your config file into the module (app-level) directory of your app.
  3. To enable Firebase products in your app, add the google-services plugin to your Gradle files.

In your root-level (project-level) Gradle file (build.gradle), add rules to include the Google Services Gradle plugin. Check that you have Google’s Maven repository, as well.

buildscript {

repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository

}

dependencies {
// ...

// Add the following line:
classpath 'com.google.gms:google-services:4.3.3' // Google Services plugin

}
}

allprojects {
// ...

repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository

// ...
}
}

In your module (app-level) Gradle file (usually app/build.gradle), apply the Google Services Gradle plugin:

apply plugin: 'com.android.application'
// Add the following line:
apply plugin: 'com.google.gms.google-services' // Google Services plugin


android {
// ...
}

Add Firebase SDKs to your app
To your module (app-level) Gradle file (usually app/build.gradle), add the dependencies for the Firebase products that you want to use in your app.

You can add any of the supported Firebase products to your Android app.

dependencies {
// ...

// Add the SDK for Firebase Cloud Messaging
implementation 'com.google.firebase:firebase-messaging:20.2.0'

// Getting a "Could not find" error? Make sure that you've added
// Google's Maven repository to your root-level build.gradle file
}

Sync your app to ensure that all dependencies have the necessary versions.

If you added Analytics, run your app to send verification to Firebase that you’ve successfully integrated Firebase. Otherwise, you can skip the verification step.

Your device logs will display the Firebase verification that initialization is complete. If you ran your app on an emulator that has network access, the Firebase console notifies you that your app connection is complete.

Edit your app manifest
Add the following to your app’s manifest:

  • A service that extends FirebaseMessagingService. This is required if you want to do any message handling beyond receiving notifications on apps in the background. To receive notifications in foregrounded apps, to receive data payload, to send upstream messages, and so on, you must extend this service.
<service
android:name=".java.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

(Optional) From Android 8.0 (API level 26) and higher, notification channels are supported and recommended. FCM provides a default notification channel with basic settings. If you prefer to create and use your own default channel, set default_notification_channel_id to the ID of your notification channel object as shown; FCM will use this value whenever incoming messages do not explicitly set a notification channel. To learn more, see Manage notification channels.

<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />

Access the device registration token
On the initial startup of your app, the FCM SDK generates a registration token for the client app instance. If you want to target single devices or create device groups, you’ll need to access this token by extending FirebaseMessagingService and overriding onNewToken.

This section describes how to retrieve the token and how to monitor changes to the token. Because the token could be rotated after initial startup, you are strongly recommended to retrieve the latest updated registration token.

The registration token may change when:

  • The app deletes Instance ID
  • The app is restored on a new device
  • The user uninstalls/reinstall the app
  • The user clears app data.

Retrieve the current registration token

Remember, if you ever need to retrieve user’s registration token, just use this code.

FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}

// Get new Instance ID token
String token = task.getResult().getToken();

// Log and toast
String msg = getString(R.string.msg_token_fmt, token);
Log.d(TAG, msg);
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});

Now, create a java file. Right-click your package name and click new -> Java file. Name it anything you want. I used MyFirebaseMessagingService.java.

Extend this class to “FirebaseMessagingService”.

Remember if you ever use an external class in a Java file, you need to import its class too. If you try to use the class without importing, Android will highlight the class name. You can hover the mouse over the link and press ALT+ENTER.

package com.google.firebase.quickstart.fcm.java;import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.firebase.quickstart.fcm.R;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
/**
* NOTE: There can only be one service in each app that receives FCM messages. If multiple
* are declared in the Manifest then the first one will be chosen.
*/
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// [START_EXCLUDE]
// There are two types of messages data messages and notification messages. Data messages
// are handled
// here in onMessageReceived whether the app is in the foreground or background. Data
// messages are the type
// traditionally used with GCM. Notification messages are only received here in
// onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated
// notification is displayed.
// When the user taps on the notification they are returned to the app. Messages
// containing both notification
// and data payloads are treated as notification messages. The Firebase console always
// sends notification
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
// [END_EXCLUDE]
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
scheduleJob();
}
else {
// Handle message within 10 seconds
handleNow();
}
}//Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
//Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.;
}
//[END receive_message]
// [START on_new_token]/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
@Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token);
}
//[END on_new_token]
/**
* Schedule async work using WorkManager.
*/
private void scheduleJob() {
// [START dispatch_job]
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)
.build();
WorkManager.getInstance().beginWith(work).enqueue();
// [END dispatch_job];
}
/**
* Handle time allotted to BroadcastReceivers.
*/
private void handleNow() {
Log.d(TAG, "Short lived task is done.");
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM InstanceID token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private void sendRegistrationToServer(String token) {
// TODO: Implement this method to send token to your app server.
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = getString(R.string.default_notification_channel_id);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle(getString(R.string.fcm_message))
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}

Here, the onMessageReceived() function is triggered whenever you will send a notification to this user’s device from your server.

@Override
public void onMessageReceived(RemoteMessage remoteMessage)

Here, onNewToken() is called whenever the token of your device is changed.

@Override    
public void onNewToken(String token)

Here, ScheduleJob() is a function to schedule job-based on the condition for any particular time. Read more about it here.

private void scheduleJob()

Now try to figure out the logic here in the sendNotification() function which basically sends the notification to the device.

private void sendNotification(String messageBody) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = getString(R.string.default_notification_channel_id);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle(getString(R.string.fcm_message))
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
Here's a good article on Intent and Pending Intent.

Next steps
After the client app is set up, you are ready to start sending downstream messages with the Notifications composer. This functionality is demonstrated in the quickstart sample, which you can download, run, and review.

To add other, more advanced behaviour to your app, you can send various values over the notification and based on certain data in notification data, choose an activity to open or task to do. You can perform many tasks then. For details, see the guides for sending messages from an app server:

  1. Send topic messages
  2. Send to device groups
  3. Send upstream messages

Keep in mind that, to take advantage of these features, you’ll need a server implementation and the server procotols (HTTP or XMPP), or implementation of the Admin SDK.

Now, you are all set with Notification. Visit here, to go to the control panel for sending a new notification to the user and test it. If you haven’t already signup and created a project on Firebase, you will be asked to do so.

Click, ‘Send Your first Message’ and enter title and notification text along with optional data. Choose targets, time and send it. BOOM!

Now, let’s move to server-side code. This won’t be a detailed explanation but it’s good for a head start. I will also add several links for more tutorial.

I have converter this part into another story so that you can directly access that part anytime. Find it here:

Creating REST API using Slim Framework — Getting started with server-side scripting

Once you are done with it.

Open Firebase.php:

<?phpclass Firebase {public function send($registration_ids, $message) {
$fields = array(
'registration_ids' => $registration_ids,
'data' => $message,
);
return $this->sendPushNotification($fields);
}

/*
* This function will make the actuall curl request to firebase server
* and then the message is sent
*/
private function sendPushNotification($fields) {

//importing the constant files
require_once 'Constants.php';

//firebase server url to send the curl request
$url = 'https://fcm.googleapis.com/fcm/send';

//building headers for the request
$headers = array(
'Authorization: key=' . FIREBASE_API_KEY,
'Content-Type: application/json'
);
//Initializing curl to open a connection
$ch = curl_init();

//Setting the curl url
curl_setopt($ch, CURLOPT_URL, $url);

//setting the method as post
curl_setopt($ch, CURLOPT_POST, true);
//adding headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

//disabling ssl support
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

//adding the fields in json format
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));

//finally executing the curl request
$result = curl_exec($ch);
if ($result === FALSE) {
die('Curl failed: ' . curl_error($ch));
}

//Now close the connection
curl_close($ch);

//and return the result
return $result;
}
}
Try to figure out each line yourself. Here, we are sending data to Google server (Firebase) to send notification to our users.

Open Push.php:

<?phpclass Push {
//notification title
private $title;
//notification message
private $message;
//notification image url
private $image;
//notification type
private $type;
//initializing values in this constructor
function __construct($title, $message, $image, $type) {
$this->title = $title;
$this->message = $message;
$this->image = $image;
$this->type = $type;
}

//getting the push notification
public function getPush() {
$res = array();
$res['data']['title'] = $this->title;
$res['data']['message'] = $this->message;
$res['data']['image'] = $this->image;
$res['data']['type'] = $this->type;
return $res;
}

}

This a raw class that holds the data we are sending in a class so that we finally send it as JSON to our user.

Open index.php and add the following code:

$app->post('/sendNotification/:title/:message+/:image/:type', function ($title, $message, $image, $type) {
$db = new DbOperation();
$push = null;
$push = new Push(
$title, $message, $image, $type
);
$mPushNotification = $push->getPush();
$devicetoken = $db->getAllTokens();
$firebase = new Firebase();
echo $firebase->send($devicetoken, $mPushNotification);
});
Now, whenever you use xyz.com/sendNotification/title/message+/image/type
the server will send these 4 data to Google server(Firebase) which in turn will send notification to your users along with these datas.
The '+' in your message field will make this field accept '/' character also. What it means is when you use xyz.com/sendNotification/title/message/image/type
url to send data, you can not have '/' character in your message field. But if you use + in message you can send data like this: xyz.com/sendNotification/This is title/My message with a / and data/image.png/myType
See the '/' character in message field: 'My message with a / and data'. If you had used '/' character without +, Slim would throw an error, since '/' means next variable is coming and Slim will be receiving extra variable. The best use for having + is when you are sending URL since URL contains '/' character.
Remember, + in any variable means it will be sent as an array. So if you have http://www.google.com in your message field, it will be stored as an array.

You are all set now. You can use this URL to send a notification to your users.

www.xyz.com/sendNotification/My Title/ My Message/Image Url/ Any Type to choose message category in user for different activities.

If you need more help on this, feel free to contact me. I’ve also listed several reading materials at the end of the article.

Packaging and publishing the app:

Once you are ready to publish, go to Play Store Developer Console. Signup using your email and pay the required fees.

Once logged in, create a new application. Choose language and title and press next.

This will be your settings page for that page. You can change various settings here and add various information.

If you see on the left-hand side, you will see various options with a tick (grey colour) next to it. Those sections are necessary to complete before you can publish your app.

All these options are easy to complete and won’t take much time. You will need a few images to upload on your app store and a cover photo.

Once you have filled in all the details, go to App Release. In this section, you will upload your apk for users to download.

Now, open your Android studio. Click Build-> Signed Bundle/ Apk.

Choose APK. Click next. Now create a new key by clicking “Create New”. Choose your path and fill in your details. Remember your Key alias and password. Click OK. Select Remember passwords. Click next. Choose release. Click finish. The Android Studio will build the apk and show the complete task message at the bottom right. You will also get an option to locate the generated apk.

Click it and upload the file in your Play Store Developer Console.

VOILA!!!

Your app is published.

Suggested reading:

  1. Firebase Cloud Messaging server-side implementation
  2. Complete Firebase Tutorial
  3. Android notification tutorial 1
  4. Android notification tutorial 2
  5. Guide to FCM and Android setup
  6. Publishing your app

Thank you for taking out the time to read this article. Please feel free to connect with me anytime if you need any help in Android development.

Cheers!

--

--