I tried to implement Firebase push notification in Java

Thing you want to do

regulation

Acknowledgments

To be honest, I didn't have much reference material on iOS, so Implemented it on my own, but for Android this article was very helpful. However, I needed some additional knowledge about what I wanted to implement this time, so this article includes that as well.

Implementation points

Initialization in activity

First of all, the main activity. This is ** just one line, topic addition ** for FCM initialization. ** If you don't even need it, you don't need anything **, but then you will not be able to send notifications separately between this and the iOS version app.

MainActivity.java


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FirebaseMessaging.getInstance().subscribeToTopic("TOPICNAME");

        // other initializing...
    }

Get device token

Next, get the generated / updated device token. This is essential if you want different notifications for different devices [^ 1], but the exact time you can do that is FirebaseInstanceIdService # onTokenRefresh. You can override this and send the device token to your own server at that time.

But this time, I just need to be able to send notifications to all users at once, and I'm not really sure if I still need the FirebaseInstanceIdService service. If you don't need it, it will be ** again "you don't have to do anything" **. However, I was a little uneasy, so I decided to run only the service, so I set it to start the superclass directly as a service without creating a subclass. Is this okay?

AndroidManifest.xml


    <service android:name="com.google.firebase.iid.FirebaseInstanceIdService">
      <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
      </intent-filter>
    </service>

Correspondence of reception in the foreground

Even in FCM, GCM article I wrote in my previous job (Chapter of "tag that can automatically overwrite and update Android notifications") -2.html) did not change the refreshing specification ** "If you receive standard notifications on Android / only in the background, you don't need to write any code" **. But this time, you also have to handle the notification in the foreground. To do this, you can override FirebaseMessagingService # onMessageReceived and do so. In that process, you can generate the same content as the notification displayed in the background and register it as a pending intent.

I mean, this is @ kirimin's article It's almost the same. It's just a grateful rainstorm (dead language). However, only the generation of the intent when the notification displayed by this code is tapped is slightly changed. Basically, this intent is in-process, oh, my hand slipped on COM Brain (and for some reason when typing this) Furthermore, since it is used only within the own application, not the professional wrestling brain where the finger slips and hits with impro wrestling), I wonder if the character string constant should be defined in MainActivity and used. That is MainActivity.ARG_FRAGMENT and MainActivity.PUSH_NOTIFICATION_ACTION.

FirMessagingService.java


public class FirMessagingService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Map<String, String> data = remoteMessage.getData();
        if (data == null) {
            return;
        }
        String fragment = data.get(MainActivity.ARG_FRAGMENT);
        if (fragment == null) {
            return;
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext());
        builder.setSmallIcon(R.drawable.notification);
        builder.setContentTitle(remoteMessage.getNotification().getTitle());
        builder.setContentText(remoteMessage.getNotification().getBody());
        builder.setDefaults(Notification.DEFAULT_SOUND
                | Notification.DEFAULT_VIBRATE
                | Notification.DEFAULT_LIGHTS);
        builder.setAutoCancel(true);

        //Create Pending Intent
        Intent intent = new Intent(this, MainActivity.class);
        intent.setAction(MainActivity.PUSH_NOTIFICATION_ACTION);
        intent.putExtra(MainActivity.ARG_FRAGMENT, fragment);
        PendingIntent contentIntent = PendingIntent.getActivity(
                getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(contentIntent);

        //Notification display
        NotificationManagerCompat manager = NotificationManagerCompat.from(getApplicationContext());
        manager.notify(0, builder.build());
    }
}

Of course, don't forget to register this subclass as a service.

AndroidManifest.xml


    <service android:name=".FirMessagingService">
      <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
      </intent-filter>
    </service>

Processing when notification is tapped

Regarding this, the regulation "launch Mode is Single Task" works extremely effectively, and there are only two patterns that can occur when tapping a notification. (Note that this regulation has nothing to do with FCM, it is a common method to always start only one own application on Android, and it is very common [^ 2])

Activity Called when tapped
AppCompatActivitymethod of
How to get an intent
During startup onNewIntent onNewIntentArguments of
Not started onCreate getIntentMethod

And in either case, you can receive an intent packed with exactly the same information, so you can handle it with a common method.

MainActivity.java


    public static final String PUSH_NOTIFICATION_ACTION = "FCM";
    public static final String ARG_FRAGMENT = "fragment";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FirebaseMessaging.getInstance().subscribeToTopic("TOPICNAME");
        int fragmentId = onIntent(getIntent());
        if (fragmentId == 0) {
            //Get the ID value of the most recently displayed Fragment
            fragmentId = PreferenceManager.getDefaultSharedPreferences(this).getInt(PreferenceKey.RECENT_FRAGMENT_POSITION, 0);
        }

        // other initializing

        changeFragment(fragmentId, true);
    }

    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        changeFragment(onIntent(intent), false);
    }

    //Get the ID of the Fragment you want to display from the notification Intent
    //In this implementation, for notifications{"fragment":"info"}If the KV is included, I am trying to display the info screen
    //If you use reflection, you don't need conditional branching,(^^ゞ
    private int onIntent(Intent intent) {
        if (intent != null && "info".equals(intent.getStringExtra(ARG_FRAGMENT))) {
            return R.id.info;
        } else {
            return 0;
        }
    }

    //Unique method to switch to Fragment specified by fragmentId if it exists
    //If isFirst is true, display the default Fragment if fragmentId is 0
    //If false, do nothing
    private void changeFragment(int fragmentId, boolean isFirst) {
        //Let's do our best
    }

Then, set the intent filter properly so that MainActivity can receive the intent of the notification. The reason will be explained later, but the action name " FCM " must be the same string as ** MainActivity.PUSH_NOTIFICATION_ACTION **.

AndroidManifest.xml


    <activity android:label="@string/app_name" android:name=".MainActivity"
        android:taskAffinity="com.wsf_lp.oritsubushi.map" android:launchMode="singleTask">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <action android:name="FCM" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
    </activity>

The most important story: Understanding the meaning of various string literals

Actually, if you do not do this properly not only on the application side but also on your own server side, you will stumble somewhere in the series of movements of "sending and receiving notifications and switching the display".

So, I've summarized the list of string literals that came out in the series of codes so far and their meanings.

String Appearance location meaning Where applicable
"FCM" Manifest
Main activity
Intent action name FCM notification payloadclick_actionThe value of the
onMessageReceivedIntent to create with
onNewIntentIntent passed to
onCreateAt the innergetIntentIntent obtained by calling
"fragment" FirebaseMessagingService
Main activity
User data key name Key name in FCM data payload
onMessageReceivedKey name in remote data that can be obtained with
"info" FirebaseMessagingService
Main activity
User data value Of the FCM data payload"fragment"The value of the
onMessageReceivedで取得できるリモートデータ内The value of the

It's simple to summarize, but "FCM payload" appears in all the items in the table. In fact, ** understanding the payload on your own server that passes the data to send to FCM ** is the most important thing. In other words, this also means that ** when linking the operation of the application with push notifications, the omission of "easily enter the notification content from the notification screen of the Firebase console and click (dead language)" is not allowed **. (Of course, considering the icon problem described later, the current console is useless from the beginning).

Regarding the FCM payload, It's all in the official documentation, but you can't just do this, [All formats] It seems essential to read (https://firebase.google.com/docs/cloud-messaging/http-server-ref) properly.

FCM payload

With that in mind, here's the JSON I'm sending from my own server to my FCM server.

{
  "to": "Topic name registered in the app",
  "notification": {
    "title": "Notification title",
    "body": "Notification body",
    "click_action": "FCM",
    "icon": "notification"
  },
  "data": {
    "fragment": "info"
  }
}

The most important thing in FCM is the notification, the notification payload. Aside from title and body, click_action is one of the keys. This is exactly the value of the action contained in the Android push notification intent [^ 3]. And with the value of this action, it's the manifest intent filter setting that keeps your app's activity from receiving extra intents. Therefore, * the click_action of the notification payload sent to the FCM server and the action name of the intent filter set in the manifest must match *, and * the intent generated by itself to display the notification even in the foreground. Action names must also be the same *.

On the other hand, data, which is optional in FCM, that is, the data payload, is a normal dictionary. And this is the one that is passed as it is to the RemoteMessage passed by the argument of FirebaseMessagingService # onMessageReceived, and also obtained by the argument of getIntent or ʻonNewIntent called inside the activity's ʻonCreate. The value set as the extra of the intent that can be done. So * those key names must match * and * the same value must be copied as an extra with the same key name into the intent they generate to display the notification even in the foreground. *.

In addition, since it will not be internationalized this time, it is like writing Japanese as it is in title and body, but if internationalization is necessary GCM article written in my previous job (: //www.mate-en.com/techblog/google-cloud-messaging-2.html) should be helpful as it is.

Notification icon

And again, though not directly related to the code. On Android, an icon can be displayed on the notification display. And in older versions of GCM before FCM, GCMListenerService # onMessageReceived is * always called * regardless of whether the app is in the foreground or background * (so GCM ListerService must be a service. I didn't), and I always created the notifications myself (just like the process in ʻonMessageReceived at the foreground in FCM now). Of course, I was free to set the notification icon there. But then the new GCM, which is almost the current FCM, introduced the spec * from which ʻonMessageReceived is not called * in the background *. And as a result, you will not be able to set the icon in the background. To avoid that, the key name ʻicon` was added to the GCM notification payload, which is carried over to the current FCM.

{
  "notification": {
    "icon": "notification"
  }
}

And this is also a metamorphosis, but suppose you set the value notification to ʻiconas above. This value ofnotification was mapped to R.drawable.notificationon the app side. Of course, this symbol is an ID value, and if the icon is an image file, specifically a file calledres / drawable / notification.png (of course, the substance was divided by resolution / SDK version / local etc. (Distributed in folders) is used as an icon. The point is that ** the filename of the PNG image for the icon (excluding the extension) will be the value of the ʻicon key in the notification payload **. In other words, to display the notification icon, you can usually create (many!) Icon images, place them appropriately under res / drawable /, and pass the file name to the FCM server. Of course, the same icon must be set inside ʻonMessageReceived` to display the notification even in the foreground.

This feeling that "the character string of the file name becomes a symbol name in Java code and it becomes an integer value when referenced, but when calling it from the server, specify it with a character string", it goes around and returns to the original .. As expected it is Android (heartburn) [^ 4].

Conclusion

Annotation

[^ 1]: If it is an SNS or chat app, the content to be notified depends on the user. Therefore, on your own server side, attach the device tokens of the user and the terminal (of course, the same user may use multiple devices, so RDB manages it with the n: m table), and notify a specific user. The process of sending only to all devices of that user is required. And it requires a device token to notify only that particular user's device, and Android can change that device token, so each time it changes, it will notify its own server. Must be. [^ 2]: Ah, in the Win32 era, this was done with a named Mutex ... In order to prevent the early Windows version of Eclipse from clicking too many icons and launching multiple times, the workspace would be destroyed. I wrote it with Create Mutex ... (As a result of presbyopia, distant eyes) [^ 3]: By the way, click_action, which is a button that appears when you swipe a notification on iOS (if the app supports it) = is linked to a custom action. It is a key inherited from GCM, but it is amazing that it can support both OSs with this! It is a praise. [^ 4]: Of course, converting symbols to integer values and building would have been necessary on older Androids, which had very poor resources. And even in the present age when that is not the case, there is an advantage that IDE code completion works easily (even in the old days, it was abused as a bad know-how of VC ++ by stuffing everything into a mysterious class). No, but I also feel that a significant portion of the unreasonableness of the Win32 API is due to being dragged too much by Win16 to make the code for OLE controls in-house produced by the company for VB compatible (or rather evil). The root may be VB older than Win16 ??), Android is also dragged by method 64k restrictions and various things (as a result, it is very difficult to make Scala) ... Is it something that has a history? ..

Recommended Posts

I tried to implement Firebase push notification in Java
I tried to implement deep learning in Java
I tried to implement the Euclidean algorithm in Java
I tried to implement polymorphic related in Nogizaka.
I tried to output multiplication table in Java
I tried to create Alexa skill in Java
I tried metaprogramming in Java
I tried to implement TCP / IP + BIO with JAVA
# 2 [Note] I tried to calculate multiplication tables in Java.
I tried to create a Clova skill in Java
I tried to make a login function in Java
I tried to implement Stalin sort with Java Collector
[Java] I tried to implement Yahoo API product search
~ I tried to learn functional programming in Java now ~
I tried to find out what changed in Java 9
I tried to interact with Java
I tried using JWT in Java
I tried to summarize Java learning (1)
Try to implement Yubaba in Java
I tried to summarize Java 8 now
I tried to convert a string to a LocalDate type in Java
I tried using Dapr in Java to facilitate microservice development
I tried to implement a buggy web application in Kotlin
I tried to make a client of RESAS-API in Java
I tried using Elasticsearch API in Java
How to implement date calculation in Java
How to implement Kalman filter in Java
I tried to implement Ajax processing of like function in Rails
Try to implement n-ary addition in Java
I tried to summarize Java lambda expressions
I tried the new era in Java
I tried setting Java beginners to use shortcut keys in eclipse
I tried to implement the Iterator pattern
How to implement coding conventions in Java
I tried to implement ModanShogi with Kinx
I tried to build a Firebase application development environment with Docker in 2020
I tried to make a talk application in Java using AI "A3RT"
I tried to make Basic authentication with Java
I want to send an email in Java.
I tried to organize the session in Rails
java I tried to break a simple block
Send push notifications using Notification Hubs in Java
I wanted to make (a == 1 && a == 2 && a == 3) true in Java
rsync4j --I want to touch rsync in Java.
I tried to build Micra mackerel in 1 hour!
I tried to develop an application in 2 languages
I tried to implement a server using Netty
I tried to break a block with java (1)
I tried Firebase Local Emulator Suite in Flutter
I tried Mastodon's Toot and Streaming API in Java
I tried to implement file upload with Spring MVC
I tried to organize the cases used in programming
I want to do something like "cls" in Java
[Java 11] I tried to execute Java without compiling with javac
I tried using Google Cloud Vision API in Java
[Java] I tried to solve Paiza's B rank problem
I tried to operate SQS using AWS Java SDK
I want to use ES2015 in Java too! → (´ ・ ω ・ `)
I tried using an extended for statement in Java
Summary of how to implement default arguments in Java
I tried passing Java Silver in 2 weeks without knowing Java