0
votes

got an issue where i have this setup fully working so far:

1) created azure notification hub and setup both ios apn and firebase fcm
2) added code to xamarin forms app in ios and android to receive a custom push notification using a template.
3) template version works 100% on ios (killed app, background and foreground) when it comes to receiving notification.
4) on xamarin android got both foreground and back ground working picking up custom push notification and processing it.

Issue: I cannot get xamarin android to show (in the tray) or even associate the custom push notification with my app. This is my issue and I cannot find any way to do this? any help guys is much appreciated.

my custom template:

string templateBodyAPNS = "{"data":{"message":"$(titleParam)","name":"$(bodyParam)","image":"$(imageParam)","url":"$(urlParam)"}}";

note: i've removed the \" in the above code to make it more readable

works fine in all scenarios except I get not a thing when the android app is killed.

2
I gave you an answer for Android native development, I don't know, how this should be done in Xamarin - IgniteCoders
I posted an an answer for Xamarin but with deprecated API. Anyway, it gives you an idea about the trick to avoid this problem. The native answer is better if you can reproduce it in Xamarin. - IgniteCoders

2 Answers

0
votes

You need to use Firebase JobDispatcher. You can find some information here, and here the code. Basically, hold a service to run it when it's possible, even when the app is killed.

It works like this:

This is your FirebaseService declared in manifest:

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

And the implementation:

public class FirebaseCloudMessagingService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        Map<String, String> data = remoteMessage.getData();
        Log.i("PUSH", data.toString());
        if (ApplicationContextProvider.isInBackground()) {
            //***********************
            //This is the most important portion of code
            FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
            Job myJob = dispatcher.newJobBuilder()
                .setService(SyncAppInBackgroundService.class) // the JobService that will be called
                .setTag("my-unique-tag")        // uniquely identifies the job
                .build();

            dispatcher.mustSchedule(myJob);
            //***********************
        } else {
            // This code manage the PushNotification when the user is in the app
            int notificationType = Integer.valueOf(data.get("type"));
            switch (notificationType) {
                case Constants.PushNotification.TYPE_NEW_MESSAGE:
                case Constants.PushNotification.TYPE_CHECK_MESSAGE:
                    if (Chat.TABLE.getByServerId(Long.valueOf(data.get("idChat"))) != null) {
                        new SyncAppHandler(null).execute(SyncAppHandler.SynchronizeTaskType.SynchronizeTaskTypeMessages);
                        break;
                    }
                case Constants.PushNotification.TYPE_NEW_GROUP:
                case Constants.PushNotification.TYPE_EDIT_CHAT_USER:
                    new SyncAppHandler(null).execute(SyncAppHandler.SynchronizeTaskType.SynchronizeTaskTypeChats);
                    break;
            }
        }
    }
}

Now, we need to create a JobService to be scheduled:

public class SyncAppInBackgroundService extends JobService implements SyncAppDelegate {

    private JobParameters params;

    private SyncAppHandler syncAppHandler;
    private SynchronizeTaskType lastTask = SynchronizeTaskType.SynchronizeTaskTypeLogin;

    @Override
    public boolean onStartJob(JobParameters job) {
        // Do some work here
        params = job;
        syncAppHandler = new SyncAppHandler(this);
        syncAppHandler.execute(lastTask);
        return true; // Answers the question: "Is there still work going on?"
    }

    @Override
    public boolean onStopJob(JobParameters job) {
        return true;// Answers the question: "Should this job be retried?"
    }

    @Override
    public void didFinishExecutingTaskWithResult(SynchronizeTaskType type, int result) {
        //handle the sync and if you finished, call:
        jobFinished(params, true);
    }
}

Dont fotget to add it in the manifest:

<service
    android:name=".services.SyncAppInBackgroundService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" />
    </intent-filter>
</service>

All what you need now, is to create your SyncAppHandler. This class is mine, don't extends any android or library class. Just implement there your business code or negotiations to synchronize.


EXTRA:

My method to ask if the app is in background. Returns falseonly if the app is in foreground, all other cases like phone locked, app killed... returns true.

public static boolean isInBackground() {
    RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
    ActivityManager.getMyMemoryState(myProcess);
    if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
        return true;

    KeyguardManager keyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
    return keyguardManager.inKeyguardRestrictedInputMode(); // app is in foreground, but if screen is locked show notification anyway
}
0
votes

This code can help you to implement something like Firebase JobDispacher

This example is with GCM, but the important this here, is the WakefulBroadcastReceiver:

using Android.Support.V4.Content;
using Android.Content;
using Android.App;

[assembly: Permission(Name = "@[email protected]_MESSAGE")]
[assembly: UsesPermission(Name = "@[email protected]_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]

namespace PushNotification.Droid {
    [BroadcastReceiver(Permission = "com.google.android.c2dm.permission.SEND")]
    [IntentFilter(new string[] { "com.google.android.c2dm.intent.RECEIVE" }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { "com.google.android.c2dm.intent.REGISTRATION" }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { "com.google.android.gcm.intent.RETRY" }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Intent.ActionBootCompleted })]
    public class WakefulReceiver : WakefulBroadcastReceiver {
        public override void OnReceive(Context context, Intent intent) {
            StartWakefulService(context, intent);
        }
    }
}

The WakefulBroadcastReceiver can run an IntentService if the app is killed. So, when you receive the PushNotification, you can handle it like this:

using System;
using Android.App;
using Android.Content;
using Android.Support.V4.Content;

namespace PushNotification.Droid {
    [Service(Exported = false), IntentFilter(new[] { "com.google.android.c2dm.intent.RECEIVE" }, Categories = new string[] { "@PACKAGE_NAME@" })]
    public class GCMNotificationIntentService : IntentService {
        public GCMNotificationIntentService() { }

        protected override void OnHandleIntent(Intent intent) {
            try {
                //I try to handle my push notification
                HandlePushNotification(ApplicationContext, intent);
            } finally {
                // But always run my intent with wakeful receiver
                // To ensure that it will be done
                WakefulReceiver.CompleteWakefulIntent(intent);
            }
        }


        private void HandlePushNotification(Context context, Intent intent) {
            Intent notificationReceived = new Intent(PushEventCenter.EVENT_NAMES.PUSH_NOTIFICATION_RECEIVED);
            foreach (String key in intent.Extras.KeySet()) {
                notificationReceived.PutExtra(key, intent.Extras.GetString(key));
            }
            LocalBroadcastManager.GetInstance(this).SendBroadcast(notificationReceived);
        }
    }
}

WakefulBroadcastReceiver is deprecated since API 26, but gives you an idea about, How to ensure that my code will be executed when my app is not in foreground.