1

I develop an Android application, where the user can receive an earthquake information. If user's device has location close to the earthquake location, the notification will have 2 buttons to confirm if they're being safe or evacuated from that happening earthquake. I'm stuck in a process while the user press 1 of the 2 buttons in notification (SAFE or EVACUATED).

Here is my code :

MyFirebaseInstanceService.java

private void showNotification(String title, String body) {
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    String NOTIFICATION_CHANNEL_ID = "com.example.yohan.notifgempafcm";

    Intent amanIntent = setIntent(body, "SAFE");
    Intent evakuasiIntent = setIntent(body, "EVACUATED");

    PendingIntent pendingIntentAman = PendingIntent.getBroadcast(this, 0, amanIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    PendingIntent pendingIntentEvakuasi = PendingIntent.getActivity(this, 1, evakuasiIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "Notification",
                NotificationManager.IMPORTANCE_DEFAULT);
        notificationChannel.enableLights(true);
        notificationChannel.setDescription("Info Gempa");
        notificationChannel.setLightColor(Color.BLUE);
        if (title.contains("WASPADA GEMPA")) {
            notificationChannel.setVibrationPattern(new long[]{0, 1000, 500, 1000});
        }
        notificationManager.createNotificationChannel(notificationChannel);
    }

    NotificationCompat.Action actAman = new NotificationCompat.Action.Builder(android.R.drawable.ic_secure, "AMAN", pendingIntentAman).build();
    NotificationCompat.Action actEvakuasi = new NotificationCompat.Action.Builder(android.R.drawable.ic_partial_secure, "EVAKUASI", pendingIntentEvakuasi).build();

    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
    notificationBuilder.setAutoCancel(true)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(body)
            .setContentInfo("Info");
    if (title.contains("WASPADA GEMPA")) {
        notificationBuilder.addAction(actAman);
        notificationBuilder.addAction(actEvakuasi);
    }
    final Notification notification = notificationBuilder.build();
    notificationManager.notify(11111, notification);
}

public Intent setIntent(String body, String status){
    Intent intent = new Intent(this, NotificationActionReceiver.class);
    intent.putExtra("tanggal", (body.split(" ")[2] + " " + body.split(" ")[3]));
    intent.putExtra("token", token);
    intent.putExtra("status", status);
    return intent;
}

NotificationActionReceiver.java

public class NotificationActionReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String tanggal = intent.getStringExtra("tanggal");
        String token = intent.getStringExtra("token");
        String status = intent.getStringExtra("status");
        konfirmasi(context, tanggal, token, status);
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(11111);
    }

    public void konfirmasi(final Context context, String tanggal, String token, String status){
        HashMap<String, String> data = pushData(tanggal, token, status);
        PostResponseAsyncTask konfirm = new PostResponseAsyncTask(context, data, new AsyncResponse() {
            @Override
            public void processFinish(String s) {
                if (s.equals("UPDATED")){
                    Toast.makeText(context, "Konfirmasi berhasil disimpan", Toast.LENGTH_SHORT).show();
                }
            }
        });
        konfirm.execute(LoginActivity.URL + "notifikasi/changeStatus");
    }

    public HashMap<String, String> pushData(String tanggal, String token, String status){
        HashMap<String, String> data = new HashMap<>();
        data.put("datetime", tanggal);
        data.put("token", token);
        data.put("konfirmasi", status);
        return data;
    }
}

I expect (example) if user press SAFE button in notification, it will safe the confirmation status from the user from NOTIFIED (default) to SAFE in database server, and so on. But it returns an error :

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.yohan.notifgempafcm, PID: 3821
    java.lang.RuntimeException: Unable to start receiver com.example.yohan.notifgempafcm.NotificationActionReceiver: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2621)
        at android.app.ActivityThread.access$1700(ActivityThread.java:153)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1382)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5293)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:569)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:282)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
        at android.app.Dialog.show(Dialog.java:298)
        at com.kosalgeek.genasync12.PostResponseAsyncTask.onPreExecute(PostResponseAsyncTask.java:151)
        at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:591)
        at android.os.AsyncTask.execute(AsyncTask.java:539)
        at com.example.yohan.notifgempafcm.NotificationActionReceiver.konfirmasi(NotificationActionReceiver.java:36)
        at com.example.yohan.notifgempafcm.NotificationActionReceiver.onReceive(NotificationActionReceiver.java:21)
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2614)
        at android.app.ActivityThread.access$1700(ActivityThread.java:153) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1382) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:135) 
        at android.app.ActivityThread.main(ActivityThread.java:5293) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:372) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) 

Any solution?

KENdi
  • 7,576
  • 2
  • 16
  • 31

1 Answers1

0

In this case error prone code is

  Toast.makeText(context, "Konfirmasi berhasil disimpan", Toast.LENGTH_SHORT).show();

BroadcastReceiver are allowed to run for up to 10 seconds . If you access the context after it it will be invalid(This is your case). Also if this is the only stuff in Process at that time then this Process will be killed and it won't care your API is being called or not . So you should not directly do long running operation inside #onReceive().
Documentation

To resolve this with your code you can use goAsync(). This will keep the broadcast active after returning from that function . Before using this please read the documentation of #goAsync().

Here is an example of goAsync().

To do this more elegant way You can start an IntentService which handle the API call in background thread automatically .

ADM
  • 20,406
  • 11
  • 52
  • 83
  • I just change the code in NotificationActionReceiver.java that now extends Activity. It works well, just one more, the confirmation status will saved to database without open an intent. Thanks! – Yohanes Dwi Listio Jun 16 '19 at 12:30