3

I've seen many different types of solutions which may have worked in the past, but nothing solid that has worked for myself. And it's a minefield of what people say works, what doesn't work, what has changed, etc. But I'm trying to find not only a solution but hopefully an understanding - because right now I am seriously confused.


What I can do right now - My Xamarin Forms app (Android) can receive push notification if the app is in the Foreground/Background, I can also intercept these notifications when the user taps them so I can tell my app what to do.

What I am trying to do - Essentially the above but in the state of where the app has been completely stopped.


I have my Firebase Messaging setup which is wired to Azure Notification Hub - unfortunately, I won't be moving away from Azure (just in case anyone suggests to drop it). Most of what I have currently is information I've managed to stitch together from various Microsoft Documentation (here I don't use AppCenter - just used this to cross-reference any useful code, here, here, and here ), other StackOverflow questions (such as here, here, and here - far too many more to link) and the Xamarin forum - so again, apologies if there is any obsolete code being used (please let me know - I have tried my best to use up-to-date methods, etc).

The type of push notifications that I am sending are Data Messages which I read up on here, I'm using custom data in my notifications therefore it is my understanding this is the correct type of push I want to send, as shown below.

{
    "data": {
        "title": "Title Test",
        "body": "Push notification body test",
        "area": "SelectedPage"
    }
}

Below is the current code I have setup in my project to handle push notifications thus far.

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.pushtesting" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
    <application android:label="Push Testing">

        <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
        <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>

    </application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

MainActivity.cs

I have LaunchMode = LaunchMode.SingleTop, would my understanding be correct, for this is to ensure the current Activity is still used instead of creating a new one - for instance, when the user would tap a notification - implementing it plus additional code (below) seems to suggest this is true.

protected override void OnNewIntent(Intent intent) {
    base.OnNewIntent(intent);

    String area = String.Empty;
    String extraInfo = String.Empty;

    if (intent.Extras != null) {
        foreach (String key in intent.Extras.KeySet()) {
            String value = intent.Extras.GetString(key);
            if (key == "Area" && !String.IsNullOrEmpty(value)) {
                area = value;
            } else if (key == "ExtraInfo" && !String.IsNullOrEmpty(value)) {
                extraInfo = value;
            }
        }
    }
    NavigationExtension.HandlePushNotificationNavigation(area, extraInfo);
}

Using OnNewIntent to intercept the push notification when the user interacts with it.

MyFirebaseMessaging.cs

using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Support.V4.App;
using Android.Util;
using Firebase.Messaging;
using PushTesting.Models;
using WindowsAzure.Messaging;

namespace PushTesting.Droid.Services {

    [Service]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    public class OcsFirebaseMessaging : FirebaseMessagingService {

        private const String NotificationChannelId = "1152";
        private const String NotificationChannelName = "Push Notifications";
        private const String NotificationChannelDescription = "Receive notifications";
        private NotificationManager notificationManager;

        public override void OnNewToken(String token) => SendTokenToAzure(token);

        /// <summary>
        /// Sends the token to Azure for registration against the device
        /// </summary>
        private void SendTokenToAzure(String token) {
            try {
                NotificationHub hub = new NotificationHub(Constants.AzureConstants.NotificationHub, Constants.AzureConstants.ListenConnectionString, Android.App.Application.Context);

                Task.Run(() => hub.Register(token, new String[] { }));
            } catch (Exception ex) {
                Log.Error("ERROR", $"Error registering device: {ex.Message}");
            }
        }

        /// <summary>
        /// When the app receives a notification, this method is called
        /// </summary>
        public override void OnMessageReceived(RemoteMessage remoteMessage) {
            Boolean hasTitle = remoteMessage.Data.TryGetValue("title", out String title);
            Boolean hasBody = remoteMessage.Data.TryGetValue("body", out String body);
            Boolean hasArea = remoteMessage.Data.TryGetValue("area", out String area);
            Boolean hasExtraInfo = remoteMessage.Data.TryGetValue("extraInfo", out String extraInfo);

            PushNotificationModel push = new PushNotificationModel {
                Title = hasTitle ? title : String.Empty,
                Body = hasBody ? body : String.Empty,
                Area = hasArea ? area : String.Empty,
                ExtraInfo = hasExtraInfo ? extraInfo : String.Empty
            };

            SendNotification(push);
        }

        /// <summary>
        /// Handles the notification to ensure the Notification manager is updated to alert the user
        /// </summary>
        private void SendNotification(PushNotificationModel push) {
            // Create relevant non-repeatable Id to allow multiple notifications to be displayed in the Notification Manager
            Int32 notificationId = Int32.Parse(DateTime.Now.ToString("MMddHHmmsss"));

            Intent intent = new Intent(this, typeof(MainActivity));
            intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
            intent.PutExtra("Area", push.Area);
            intent.PutExtra("ExtraInfo", push.ExtraInfo);

            PendingIntent pendingIntent = PendingIntent.GetActivity(this, notificationId, intent, PendingIntentFlags.UpdateCurrent);
            notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);

            // Creates Notification Channel for Android devices running Oreo (8.0.0) or later
            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) {
                NotificationChannel notificationChannel = new NotificationChannel(NotificationChannelId, NotificationChannelName, NotificationImportance.High) {
                    Description = NotificationChannelDescription
                };

                notificationManager.CreateNotificationChannel(notificationChannel);
            }

            // Builds notification for Notification Manager
            Notification notification = new NotificationCompat.Builder(this, NotificationChannelId)
            .SetSmallIcon(Resource.Drawable.ic_launcher)
            .SetContentTitle(push.Title)
            .SetContentText(push.Body)
            .SetContentIntent(pendingIntent)
            .SetAutoCancel(true)
            .SetShowWhen(false)
            .Build();

            notificationManager.Notify(notificationId, notification);
        }
    }
}

Then finally my Firebase class where I use OnNewToken() to register to Azure with, OnMessageReceived() is overridden so I can handle a push notification being received and then SendNotification() which is being used to build and show the notification.

Any and all help will be highly appreciated!

Added Sample Project to GitHub

MattVon
  • 481
  • 1
  • 8
  • 25
  • Interesting problem. Does your iOS app receive push notification from apple service when app is terminated or it happens only when running your code on Android? I have tried to use Azure notification hub, but I wasn't very successful and switches finally to firebase. Are you pushing your notifications directly from Azure or web service? What I would try is to send notification to specific device do you can detect if device is subscribed to notification channel correctly or not. – Adlorem Jan 04 '20 at 10:48
  • I've had a quick look at my iOS app and the problem is not on that, works perfectly fine even after the app has been killed for a few minutes. So currently in my development environment I am using Postman and communicating to the Azure Notification API. So I'll send a notification from Postman > Azure > Firebase > Mobile App. Using the API I can see all devices connected, I've cleared them all out and tried one instance, which registers successfully. The Android app gets the notification so long as the app is running, but as soon as it's killed after a minute or so, no more notifications. – MattVon Jan 06 '20 at 09:11
  • Could you kindly check if you have permission in your AndroidManifest – Adlorem Jan 06 '20 at 12:19
  • My Android Manifest is apart of my original post, I can confirm that permission is there. – MattVon Jan 06 '20 at 12:20
  • Can you show in your MainActivity how you initialize firebase? I assume google-services.json is present and compiled as GoogleServicesJson – Adlorem Jan 06 '20 at 12:37
  • I have no additional code in MainActivity to initialize Firebase. I have the recommended Nuget packages installed which are Xamarin.GooglePlayServices.Base and Xamarin.Firebase.Messaging. Then in MyFirebaseMessagingService.cs I have the following `public class MyFirebaseMessagingService : FirebaseMessagingService`. Which from my understanding initialises Firebase for me? As on a new installed OnNewToken() gets called as expected. And yes, I have the json file and I have set the build properties. – MattVon Jan 06 '20 at 12:54
  • What makes this more frustrating now is I've created a sample project focusing on resolving this issue, and the sample project can receive push notifications when the apps state is dead. So at least I know it is plausible the way I'm doing it, just for some reason it doesn't want to work on my main project. – MattVon Jan 08 '20 at 15:25
  • Hitting the same issue and pretty worried by this thread, very recent: https://forums.xamarin.com/discussion/174983/push-notification-not-working-after-xamarin-forms-upgrade – Frank Jan 09 '20 at 11:52
  • @MattVon gut feel is pointing me at the manifest....could you pls share the manifest to the new project? or some suggestions on what the main diffs are between your working test proj and main? – Sentinel Jan 09 '20 at 14:10
  • @Sentinel, the Mainfest code in my original post is all there is - it's also identical on both projects. I've tried to make my sample project identical to my main project (without adding all business logic not related to Push Notifications). I've even started adding unrelated Push Notification Nuget Packages, thinking maybe something else is causing my issue. My sample was also using the latest XF build (4.4 I think?) and downgraded that to 4.1 (yet my sample still worked). One thing I have noticed - though doesn't help me, if I send a test notification from Firebase, everything works. – MattVon Jan 09 '20 at 14:13
  • I am also not seeing any fixes from changing the manifest. – Frank Jan 09 '20 at 14:52
  • 1
    Found an active case on GitHub for this exact issue - no solutions yet. Though I am still miffed by how my sample project works. https://github.com/xamarin/GooglePlayServicesComponents/issues/273 – MattVon Jan 09 '20 at 15:33
  • @MattVon, hi, this is my issue on GitHub and its discussion seems very dead. Hovewer, Microsoft/Xamarin support team answers sometimes. Without any success though... The latest answer that I receve so far was 2 days ago: https://gist.githubusercontent.com/Alexander-Alekseev/fba574730e25af68048c6f31c9b1899b/raw/269415d1999f4ca54adca8eba885d5085c90c6b2/20200108_119103125000278 You mentioned that your test project works fine, could you please share it with GitHub for example? – Alexander Alekseev Jan 10 '20 at 06:25
  • 2
    @AlexanderAlekseev, please find my sample here: https://github.com/MattVon/Xamarin-Push-Notification-Sample – MattVon Jan 10 '20 at 12:00
  • 1
    @MattVon, thank you. I compared your test project with my project, found several small differences, but still no luck :( – Alexander Alekseev Jan 14 '20 at 08:15
  • @MattVon Also, thanks a lot, I am looking into this right now too as it's a showstopper for me. – Frank Jan 14 '20 at 10:22
  • Tested against an earlier app version and push notifications work. It's these latest Xamarin updates failing for some reason, or at least the latest version of my app. Trying to isolate if some evolution of my app introduced some complexity no longer handled by Xamarin-latest and not evident in MattVon's minimal demo app. – Frank Jan 15 '20 at 10:53
  • If you have any luck with your investigations let me know, it sounds like we have the same issue. The only thing that springs to my mind for my original project, is I updated from 3.x (I think 3.6) to 4.0. I've yet to test this theory but I wonder if updating from 3.x to 4.x has any impact. – MattVon Jan 15 '20 at 11:03
  • I have news too. I just took my test device and saw that all yesterday's push notifications came at night (please note that I did several changes to fit @MattVon's test app). Not sure how "let's check FCM-server if I have new notifications"-mechanism works on device/app side, but seems like Xamarin service (that we register in our apps) doesn't work properly/fast enough. – Alexander Alekseev Jan 15 '20 at 11:07
  • 1
    Yes I think it is almost certainly the Xamarin Forms upgrade causing the issue - an earlier version was before 4. and that worked. I just found something (actually a number of things related): if I enable Developer options in the Android settings, set the default debug app to my app, then specify that it should 'wait for debugger', then when I send a test notification the app tries to launch. I get the 'waiting for debugger' message. Visual Studio does not allow a connect to that session though. But it does mean the app is trying to start. NB: running in debug mode and closing 'force stops' – Frank Jan 15 '20 at 11:22
  • Other thing to rule out: 32bit version vs 64 bit makes no different. Just tried that. – Frank Jan 15 '20 at 11:22
  • Other docs say a common Android error is spawning threads in a BroadcastReceiver - going to look into async patterns being used in the notification handler and stuff like that. – Frank Jan 15 '20 at 11:23
  • We might want to migrate to a gitter channel or something - or does this SO have a useful chat platform? – Frank Jan 15 '20 at 11:24
  • Usually SO would have suggested moving it to a chat by now to prevent this.. So I'm not entirely sure. – MattVon Jan 15 '20 at 11:29
  • I think I ran into a gitter.im channel for Xamarin forms. I think if we all move there it might kick up more of a stink and get MS to look into it. I also added various messages on like MS doc pages and so on. One moment, I will look for the gitter link.... – Frank Jan 15 '20 at 11:32
  • So, there's a Xamarin.Forms gitter but I think we might get more help on the Android one https://gitter.im/xamarin/xamarin-android# ? – Frank Jan 15 '20 at 11:36
  • So I connected to and got device logs (Visual Studio - View -> Other Windows -> Device Log) while the notification was being received when app stopped. Just analyzing now. – Frank Jan 15 '20 at 12:20
  • Seeing similar error messages to this: https://developercommunity.visualstudio.com/content/problem/808284/stbintentservice-send-error-crash.html – Frank Jan 15 '20 at 12:25
  • Also this error and exception that is crashing the app: "Could not activate JNI Handle 0x7fdbfee170 (key_handle 0x7d963d4) of Java type 'crc64925d64ef8a16450b/FirebaseService' as managed type 'CMSApp.Droid.FirebaseService'" Web search suggests that this answer here is what is going on: https://stackoverflow.com/questions/10593022/monodroid-error-when-calling-constructor-of-custom-view-twodscrollview/10603714#10603714 Looks like the 'main' projects are doing things in the wrong order somehow and/or Firebase is racey, something like that. – Frank Jan 15 '20 at 12:45
  • @AlexanderAlekseev I suspect in your case you have a service of some kind in your app that wakes at night or under certain conditions, or a receiver listening for some event attached to night conditions, bringing the app into the foreground and then therefore receiving the messages. I'd guess this is a red herring. – Frank Jan 15 '20 at 12:51
  • @Frank, hmmm... Nope, nothing like that. I've meant FirebaseMessagingService. Actually, here is the only one service in my app. And, of course, I don't need to notify user after N hours, I want user to be notified immediately (that's the purpose of push notifications, right?). In my previous post I meant that push notifications that I've sent from my server came to the device/app after several hours. And, to be honest, they are still coming... One message per 10-30 minutes. That's strange. Seems like they are duplicated. – Alexander Alekseev Jan 15 '20 at 13:07
  • 1
    None of that receiver stuff seems to be necessary anymore in the manifest. I think that is legacy config. The Service and IntentFilter attribute on the FirebaseMessaging class is enough (the Xamarin tools inject the right stuff in the android manifest). Still no luck though. – Frank Jan 15 '20 at 16:15
  • Just to clarify, which lines are you referring to? – MattVon Jan 15 '20 at 16:20
  • @MattVon The two Receiver elements in the application xml element. AFAICT that's no longer required. I removed it anyway and see no change in behavior. EG https://firebase.google.com/docs/cloud-messaging/android/client and various other info to the same effect – Frank Jan 15 '20 at 18:31
  • Interesting... I know the MS documents are dated, but they say otherwise. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/azure-services/azure-notification-hub#configure-android-manifest (Yay, documentation contradiction). Of course, I'd go with the Firebase url you provided over MS, just creates great confusion. – MattVon Jan 15 '20 at 19:28
  • @MattVon Yep this is strange. Anyway push works without those elements, and causes the same launch-attempt-then-crash situation when the app is closed. The error in the logs is definitely that push is received and then it crashes because a .net class is not defined at that point yet, or something like that. – Frank Jan 15 '20 at 20:40
  • @MattVon Check the GitHub issue....I just looked again at your above code and can't see a parallel with my specific solution/case. It looks like then the manifest might need changing as I can't see anything in C# analogous to my issue. You might want to check the references to make sure nothing is trying to access unloaded libraries when the service is invoked in response to a push. – Frank Jan 16 '20 at 10:22

1 Answers1

1

There were a number of issues for me (more details here):

  • Unfamiliarity with the Android app architecture and how that relates to the FirebaseMessagingService lifecycle: There is the app, services and receivers. When the 'app' (from a user perspective) is closed, by swiping from recents, the services/receivers are available to react to 'intents' (the incoming notification message). When the app is 'force stopped', no ui, services or receivers run.
  • Visual Studio force stops apps after the debug session ends. To diagnose a 'stopped' app you need to run the app on device and look at Device Logs.
  • The FirebaseMessagingService lifecycle is such that when the app is in the stopped state the ctor no longer has access to properties or methods of the shared app (I had to work around this by removing abstractions and making code platform specific - in particular a DependencyService that could not be used as it was not available).
  • MS docs are out of date. Firebase manifest entries no longer require the Receiver elements for example.
  • You cannot attach debugger to a 'stopped' app in Visual Studio.
  • Error messages on device are cryptic.

In the end the solution was to View->Other Windows -> Device Logs and run the app a second time to avoid the Force Stopped state, to discover the ctor lifecycle issue, which I had to work around by moving out code that touched the shared app library from FirebaseMessagingService.

Frank
  • 903
  • 7
  • 14
  • Apologies for the silence, I was working on other defects. Very good info provided, I already knew about the stopped state because of VS when debugging - nasty trick that. Hopefully I can jump back on this in my own project and see if I can get any further. Have you had any success/more info helping you get anywhere? – MattVon Jan 27 '20 at 12:00
  • Literally started debugging this and taking the approach for checking the logs, turns out I was also get the `FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: You MUST call Xamarin.Forms.Init(); prior to using it.` also. Once I removed the specific code causing this, it was working again. – MattVon Jan 27 '20 at 12:22
  • @MattVon Excellent :-) It was a bit of an epic hacking session getting that all sussed out. I think if VS allowed the debugger to stay attached to the app when in the swipe-closed state, we could have avoided all this. Anyway, very glad I could have helped. – Frank Jan 27 '20 at 14:34