SaschaDittmann / Xamarin.NotificationHub

Apache License 2.0
10 stars 12 forks source link

XAMARIN Android - Azure Mobile Service Push Notification Problem #9

Closed jx37 closed 3 years ago

jx37 commented 9 years ago

Hi, I’m developing an app that receive push notification from Azure Mobile Service in Xamarin-Android. When I start the application I register my device (Nexus 4) to my Azure Notification Hub. Push Notification still received only when app is running or is in background, but not when the app is closed or I delete it from task manager (N.B I don’t force to quit it from settings). Now I have downloaded the example from Github at [1] and I customize that with my Google Project Sender ID, my Notification Hub ID and my Azure Mobile Service Connection String, but neither this working when the app is totally closed. Here [2] the zip containing the test solution.

Hope you can help me.

[1] https://github.com/SaschaDittmann/Xamarin.NotificationHub/tree/master/samples [2] http://1drv.ms/1xPlgL5

Best Regards.

SaschaDittmann commented 9 years ago

Going to have a look...

dbeaupre commented 9 years ago

Hi,

I have the same problem on my side. The notifications does'nt works while the application is closed. Did you found any solution about that ? Thanks

jx37 commented 9 years ago

Hi, I solved the problem . To work properly Android applications must include a special class called "BroadcastReceiver" and an "IntentService" class. Notifications are working when the app is totally closed only if this IntentService class is specified on the BroadcastReceiver class. Below the correct implementation:

[BroadcastReceiver(Permission = Constants.PERMISSION_GCM_INTENTS)] [IntentFilter(new[] { PushNotificationService.PushNotificationReceived}, Priority = (int)IntentFilterPriority.LowPriority)] [IntentFilter(new[] { Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new[] { "packagename" })] [IntentFilter(new[] { Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new[] { "packagename" })] [IntentFilter(new[] { Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new[] { "packagename" })] public class PushGcmBroadcastReceiver : GcmBroadcastReceiverBase {

region Fields

    //The SENDER_ID is your Google API Console App Project Number
    public static string[] SenderIds = { "SENDER_ID" };
    public static string[] Tags = {"android"};
    const string ReceiveAction = "com.google.android.c2dm.intent.RECEIVE";
    const string RegistrationAction = "com.google.android.c2dm.intent.REGISTRATION";
    public static string RegistrationId { get; set; }
    #endregion

    #region Logic
    public override void OnReceive(Context context, Intent intent)
    {           
        var keys = intent.Extras;

        if (intent.Action == ReceiveAction)
        {
            //Get values of toast parameters
            var title = keys.ContainsKey("title") ? keys.GetString("title") : string.Empty;
            var message = keys.ContainsKey("message") ? keys.GetString("message") : string.Empty;
            var param = keys.ContainsKey("id") ? keys.GetString("id") : string.Empty;

            CreateNotification(context, title, message, param);
        }
        else if (intent.Action == RegistrationAction)
        {
            var registrationId = keys.ContainsKey("registration_id") ? keys.GetString("registration_id") : string.Empty;

            if(!string.IsNullOrEmpty(registrationId))
                RegisterHub(registrationId);
            else
                UnRegisterHub();
        }
    }

    private void CreateNotification(Context context, string title, string message, string param)
    {
        var notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);

        //Create an intent to show ui
        var uiIntent = new Intent(context, typeof(MainView));
        uiIntent.PutExtra("ID", param);

        const int pendingIntentId = 0;
        var pendingIntent = PendingIntent.GetActivity(context, pendingIntentId, uiIntent, PendingIntentFlags.OneShot);

        // Instantiate the notification builder and enable sound:
        var builder = new Notification.Builder(context)
            .SetAutoCancel(true)
            .SetContentIntent(pendingIntent)
            .SetContentTitle(title)
            .SetContentText(message)
            .SetDefaults(NotificationDefaults.Sound)
            .SetSmallIcon(Resource.Drawable.ic_launcher);

        var notification = builder.Build();

        // Publish the notification:
        const int notificationId = 0;
        if (notificationManager != null)
            notificationManager.Notify(notificationId, notification);
    }
    #endregion

    #region Register/Unregister Hub
    private static void RegisterHub(string registrationId)
    {
        //Receive registration Id for sending GCM Push Notifications to            
        try
        {
            var notificationHub = PushNotificationService.Hub;

            if (notificationHub == null)
                return;

            RegistrationId = registrationId;

            //notificationHub.UnregisterAllAsync(registrationId);
            notificationHub.UnregisterNativeAsync();

            notificationHub.RegisterNativeAsync(registrationId, Tags);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("An exception has occurred in RegisterHub: {0} - StackTrace: {1}", ex.Message,
                ex.StackTrace);
        }

    }

    private static void UnRegisterHub()
    {
        try
        {
            var notificationHub = PushNotificationService.Hub;
            if (notificationHub != null)
                //notificationHub.UnregisterAllAsync(_registrationId);
                notificationHub.UnregisterNativeAsync();
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception occurred in UnRegisterHub: {0} - StackTrace: {1}", ex.Message, ex.StackTrace);
        }
    }
    #endregion
}

This is necessary because the IntentService class has the task to stay awake the app so that it listens for a possible push notification. In the IntentService class you must provide the method for NotificationHub registration and the method which makes the app waking up (void OnHandleIntent(Intent intent)) as follow:

[Service] public class PushNotificationService : GcmServiceBase {

region Fields

    public static NotificationHub Hub;
    private static Context _context;
    #endregion

region MobileServiceClient

    // Use this constructor instead after publishing to the cloud
    public static MobileServiceClient MobileService = new MobileServiceClient(Configuration.ApiBaseUri, Configuration.MobileServiceAppKey);
    #endregion
    #region ctor
    public PushNotificationService()
        : base(PushGcmBroadcastReceiver.SenderIds)
    {
    }
    #endregion

    #region GcmServiceBase implementation
    public static void Initialize(Context context)
    {
        _context = context;
        Hub = new NotificationHub("NotificationHubName", "AzureConnectionString");
    }

    public static void Register(Context context)
    {
        try
        {
            GcmClient.CheckDevice(context);
            GcmClient.CheckManifest(context);

            var registrationId = GcmClient.GetRegistrationId(context);
            if (string.IsNullOrEmpty(registrationId))
            {
                // Makes this easier to call from our Activity
                GcmClient.Register(context, PushGcmBroadcastReceiver.SenderIds);                    
            }

            registrationId = GcmClient.GetRegistrationId(context);
            if (!string.IsNullOrEmpty(registrationId))
                RegisterHub(registrationId);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception occurred in Register: {0} - StackTrace: {1}", ex.Message, ex.StackTrace);
        }
    }

    protected override void OnRegistered(Context context, string registrationId)
    {
        try
        {
           RegisterHub(registrationId);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception occurred in OnRegistered: {0} - StackTrace: {1}", ex.Message, ex.StackTrace);
        }
    }

    private static void RegisterHub(string registrationId)
    {
        //Receive registration Id for sending GCM Push Notifications to
        if (Hub == null)
            return;

        Hub.UnregisterAllAsync(registrationId);

        Hub.RegisterNativeAsync(registrationId);
    }

    protected override void OnUnRegistered(Context context, string registrationId)
    {
        try
        {
            if (Hub != null)                    
                Hub.UnregisterAllAsync(registrationId);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception occurred in OnUnRegistered: {0} - StackTrace: {1}", ex.Message, ex.StackTrace);
        }
    }

    protected override void OnMessage(Context context, Intent intent)
    {
        Debug.WriteLine("--------- Received Notification ---------");

        //Push Notification arrived - print out the keys/values
        if (intent == null || intent.Extras == null)
            return;

        var keys = intent.Extras;

        //Get values of toast parameters
        var title = keys.ContainsKey("title") ? keys.GetString("title") : string.Empty;
        var message = keys.ContainsKey("message") ? keys.GetString("message") : string.Empty;
        var param = keys.ContainsKey("id") ? keys.GetString("id") : string.Empty;

        //Show push notification
        CreateNotification(title, message, param);
    }

    protected override bool OnRecoverableError(Context context, string errorId)
    {
        //Some recoverable error happened  
        return base.OnRecoverableError(context, errorId);
    }

    protected override void OnError(Context context, string errorId)
    {
        //Some more serious error happened           
    }
    #endregion

    public void CreateNotification(string title, string desc, string param)
    {
        //Create notification
        var notificationManager = GetSystemService(NotificationService) as NotificationManager;

        //Create an intent to show ui
        var uiIntent = new Intent(this, typeof(MainView));
        uiIntent.PutExtra("newsID", param);

        const int pendingIntentId = 0;
        var pendingIntent = PendingIntent.GetActivity(this, pendingIntentId, uiIntent, PendingIntentFlags.OneShot);

        // Instantiate the notification builder and enable sound:
        var builder = new Notification.Builder(this)
            .SetAutoCancel(true)
            .SetContentIntent(pendingIntent)
            .SetContentTitle(title)
            .SetContentText(desc)
            .SetDefaults(NotificationDefaults.Sound)
            .SetSmallIcon(Resource.Drawable.ic_launcher);

        var notification = builder.Build();

        // Publish the notification:
        const int notificationId = 0;
        if (notificationManager != null)
            notificationManager.Notify(notificationId, notification);
    }

//This method is responsible to wake up application when is totally closed and to handle the received notification calling the broadcast receiver protected override void OnHandleIntent(Intent intent) { //base.OnHandleIntent(intent);

        var pushIntent = new Intent(PushNotificationReceived);

        SendOrderedBroadcast(pushIntent, null);
    } 
dbeaupre commented 9 years ago

Thanks a lot, ill give it a try and ill get back to you if i have some issues.

dbeaupre commented 9 years ago

jx37, i tried to make this setup works but i still have some problems. I'm not sure to see what is "PushNotificationReceived" used in OnHandleIntent and IntentFilter. Where and how is it defined ? Thanks a lot for your help

jx37 commented 9 years ago

Hi dbeaupre , "PushNotificationReceived" is the Intent action name which is called when a push notification is received. If you take a look the same name is specified in action declaration before BroadcastReceiver class.

[IntentFilter(new[] { PushNotificationService.PushNotificationReceived}, Priority = (int)IntentFilterPriority.LowPriority)]

Anyway "PushNotificationReceived" is a costant string and you can define at the begin of the PushNotificationService class, like this:

public const string PushNotificationReceived = "PushNotificationReceived";

Hope this help.

dbeaupre commented 9 years ago

Hi, tanks a lot for your help. When the app is running or in background I can receive notifications but when the app is closed i dont. Do i have to start the service somewhere ? Now i'm calling the Register and initialize sub on OnCreate of my main activity, is this ok ? Thanks

moonClimber commented 9 years ago

Hi jx37, I'm involved in the same problem. I've tried to start from your initial code and apply your solution but I get a lot of errors. For instance, I cannot inherit from GcmBroadcastReceiverBase because this class just doesn't exist (instead, exists the generic one GcmBroadcastReceiverBase).

Anyway, could you please share all your working code (or, better, un updated solution to download), just for reference? Thanks

moonClimber commented 9 years ago

I've solved the problem. You can find my solution here: https://github.com/moonClimber/AzureHubNotification_XamarinAndroid