Search code examples
androidandroid-notificationsandroid-alertdialogintentserviceandroid-geofence

Android, Display alertDialog instead of notification when app is open


I followed this developer tutorial, and have Geofencing working within my app, as expected.

A notification is sent when a Geofence Transition occurs, from within an IntentService:

@Override
protected void onHandleIntent(Intent intent) {
    GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);

    ...        

    sendNotification(geofenceTransitionDetails);
}

private void sendNotification(String notificationDetails) {
    // Create an explicit content Intent that starts the main Activity.
    Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);

    // Construct a task stack.
    TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

    // Add the main Activity to the task stack as the parent.
    stackBuilder.addParentStack(MainActivity.class);

    // Push the content Intent onto the stack.
    stackBuilder.addNextIntent(notificationIntent);

    // Get a PendingIntent containing the entire back stack.
    PendingIntent notificationPendingIntent =
            stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

    // Get a notification builder that's compatible with platform versions >= 4
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

    // Define the notification settings.
    builder.setSmallIcon(R.mipmap.ic_launcher)
            // In a real app, you may want to use a library like Volley
            // to decode the Bitmap.
            .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                    R.mipmap.ic_launcher))
            .setColor(Color.RED)
            .setContentTitle(notificationDetails)
            .setContentText("Return to app")
            .setContentIntent(notificationPendingIntent);

    // Dismiss notification once the user touches it.
    builder.setAutoCancel(true);

    // Get an instance of the Notification manager
    NotificationManager mNotificationManager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // Issue the notification
    mNotificationManager.notify(0, builder.build());
}

This is cookie-cutter from the tutorial. The intent is set-up in the Main activity:

private PendingIntent getGeofencePendingIntent() {
    // Reuse the PendingIntent if we already have it.
    if (mGeofencePendingIntent != null) {
        return mGeofencePendingIntent;
    }
    Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
    // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
    // addGeofences() and removeGeofences().
    return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

How can I add functionality that suppresses the notifications if the app is open, and instead displays an AlertDialog to the user? Ideally, I'd like to be able to execute different tasks, depending on which view the user is currently in when the Geofence Transition occurs. Can I monitor/intercept the transition from within each view, or somehow globally?

Thanks in advance.


Solution

  • Some of the answers were incomplete, and so here is the complete solution to what I was looking for.

    First off, set up MyApplication class, that implements ActivityLifecycleCallbacks:

    public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks {
    
        private static boolean isActive;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            registerActivityLifecycleCallbacks(this);
        }
    
        public static boolean isActivityVisible(){
            return isActive;
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
            isActive = true;
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
            isActive = false;
        }
    
        ... no other methods need to be used, but there are more that 
        ... must be included for the ActivityLifecycleCallbacks
    }
    

    Be sure to name this in your manifest (only name line was added, rest is default):

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:hardwareAccelerated="true">
    

    What was done above is used to track the lifecycle of your app. You can use this to check if your app is currently in the foreground or not.

    Next is to set up a BroadcastReceiver, wherever you would like code to run (in the event that the app is open when the trigger occurs). In this case, it is in my MainActivity:

    protected BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            ... Do whatever you want here
    
            Toast.makeText(...).show();
        }
    };
    

    Register the receiver in your onCreate of the same activity:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        ...
    
        LocalBroadcastManager.getInstance(this).registerReceiver(mNotificationReceiver, new IntentFilter("some_custom_id"));
    }
    

    And don't forget to unregister it:

    @Override
    protected void onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mNotificationReceiver);
        super.onDestroy();
    }
    

    When a broadcast is received, the code within the receiver is executed.

    Now, to check if the app is in the foreground, and send a broadcast if it is. Inside of the IntentService:

    @Override
    protected void onHandleIntent(Intent intent) {
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        if (geofencingEvent.hasError()) {
            String errorMessage = getErrorString(this,
                    geofencingEvent.getErrorCode());
            return;
        }
    
        int geofenceTransition = geofencingEvent.getGeofenceTransition();
    
        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
    
            ...
    
            if(MyApplication.isActivityVisible()){
                Intent intnt = new Intent("some_custom_id");
                intnt.putExtra("message", geofenceTransitionDetails);
                LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
            }else{
                sendNotification(geofenceTransitionDetails);
            }
    
        } else {
            // Log the error.
        }
    }
    

    The important bit is the last nested if-statement:

    if(MyApplication.isActivityVisible()){
        Intent intnt = new Intent("some_custom_id");
        intnt.putExtra("message", geofenceTransitionDetails);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
    }else{
        sendNotification(geofenceTransitionDetails);
    }
    

    Check if the app is in the foreground using MyApplication.isActivityVisible(), as defined above, and then either send the notification, or send a broadcast. Just make sure that your intent code (i.e. "some_custom_id") matches on your sender and receiver.

    And that's about it. If the app is in the foreground (specifically the MainActivity), I execute some code. If the app is not in the foreground, I send a notification.