MainActivity
classMainActivity
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.
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...
}
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>
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>
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 tappedAppCompatActivity method of |
How to get an intent |
---|---|---|
During startup | onNewIntent |
onNewIntent Arguments of |
Not started | onCreate |
getIntent Method |
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>
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_action The value of theonMessageReceived Intent to create withonNewIntent Intent passed toonCreate At the innergetIntent Intent obtained by calling |
"fragment" | FirebaseMessagingService Main activity |
User data key name | Key name in FCM data payloadonMessageReceived Key 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 theonMessageReceived で取得できるリモートデータ内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.
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.
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 of
notification 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 called
res / 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].
[^ 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