0

I'm trying to create a handler that will produce a popup when a message is received from SignalR which is running in a service. I've got this working in a non-service, but it won't work in a service.

This code works from a non-service:

client.OnMessageReceived += 
(sender2, message) => 
RunOnUiThread(() =>
showMessage(message));

Where client is the SignalR client and showMessage is the method called when a message is received from client. No problem.

Now I want to run the client in/as a service and I need to wire up a handler to do basically the same thing. I've tried several methods I found on StackOverflow and other sites, but all the stuff out there is java, not c# for Xamarin for Visual Studio (2017) and does not translate well. I'm at a loss as to how to proceed.

* Update * This is my forground service code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Acr.UserDialogs;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Views;
using Android.Widget;
using ChatClient.Shared;
using Java.Lang;

namespace OML_Android
{
    public class SignalRService : Service
    {
        public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
        public const string ACTION_MAIN_ACTIVITY = "OML_Android.action.MainActivity";
        public const string SERVICE_STARTED_KEY = "has_service_been_started";

        bool isStarted;
        Handler handler;
        Action runnable;

        // This information will eventually be pulled from the intent, this is just for testing
        public string firstname = "";
        public string lastname = "";
        public string username = "";
        public string name = "";

        private Client mInstance;

        public override IBinder OnBind(Intent intent)
        {
            return null;
            // throw new NotImplementedException();
        }

        public override void OnCreate()
        {
            base.OnCreate();
            mInstance = Client.Getinstance(name, username, firstname, lastname);
            mInstance.Connect();
        }

        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
        {
            return StartCommandResult.Sticky;
        }
        public override void OnDestroy()
        {
            // Not sure what to do here yet, got to get the service working first
        }
        void RegisterForegroundService()
        {
            var notification = new NotificationCompat.Builder(this)
                .SetContentTitle(Resources.GetString(Resource.String.app_name))
                .SetContentText(Resources.GetString(Resource.String.notification_text))
                .SetSmallIcon(Resource.Drawable.alert_box)
                .SetContentIntent(BuildIntentToShowMainActivity())
                .SetOngoing(true)
                //.AddAction(BuildRestartTimerAction())
                //.AddAction(BuildStopServiceAction())
                .Build();


            // Enlist this instance of the service as a foreground service
            StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
        }

        PendingIntent BuildIntentToShowMainActivity()
        {
            var notificationIntent = new Intent(this, typeof(MainActivity));
            notificationIntent.SetAction(ACTION_MAIN_ACTIVITY);
            notificationIntent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTask);
            notificationIntent.PutExtra(SERVICE_STARTED_KEY, true);

            var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, PendingIntentFlags.UpdateCurrent);
            return pendingIntent;
        }

        public async void showMessage(string message)
        {

            var result = await UserDialogs.Instance.ConfirmAsync(new ConfirmConfig
            {
                Message = "Text Message from Company: " + System.Environment.NewLine + message,
                OkText = "Ok",

            });
            if (result)
            {
                // do something
                var x = message;
            }
        }
    }
}

This service sets the client to run as a foreground service (I assume), the client code is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client;

namespace ChatClient.Shared
{
    public sealed class Client
    {
        //public string username;
        private string username = "";
        private string _platform = "";
        private readonly HubConnection _connection;
        private readonly IHubProxy _proxy;

        public string _Username
        {
            get { return username; }
            set { username = value; }
        }

        public string _Platform
        {
            get { return _platform; }
            set { _platform = value; }
        }

        public event EventHandler<string> OnMessageReceived;
        public static Client instance = null;

        public Client(string name, string username, string firstname, string lastname, string company, string department, string section)
        {
            _Username = username;
            _Platform = name;
            _platform = _Platform;

            Dictionary<string, string> queryString = new Dictionary<string, string>();
            queryString.Add("username", username);
            queryString.Add("firstname", firstname);
            queryString.Add("lastname", lastname);
            queryString.Add("company", company);
            queryString.Add("department", department);
            queryString.Add("section", section);

            _connection = new HubConnection("https://www.example.com/SignalRhub",queryString );

            _proxy = _connection.CreateHubProxy("chathub");
        }

        public static Client Getinstance(string name, string username, string firstname, string lastname)
        {
            // create the instance only if the instance is null
            if (instance == null)
            {
                // The username and user's name are set before instantiation
                instance = new Client(name, username, firstname, lastname,"","","");
            }
            // Otherwise return the already existing instance
            return instance;
        }
        public async Task Connect()
        {

            await _connection.Start(); //.ContinueWith._connection.server.registerMeAs("");
            _proxy.On("broadcastMessage", (string platform, string message) =>
            {
                if (OnMessageReceived != null)
                    OnMessageReceived(this, string.Format("{0}: {1}", platform, message));
            });

            // Send("Connected");
        }

        public async Task<List<string>> ConnectedUsers()
        {

            List<string> Users = await _proxy.Invoke<List<string>>("getConnectedUsers");

            return Users;
        }

        public async Task<List<string>> ConnectedSectionUsers(string company, string department, string section, string username)
        {

            List<string> Users = await _proxy.Invoke<List<string>>("getConnectedSectionUsers",company, department, section, username);

            return Users;
        }

        public Task Send(string message)
        {
            return _proxy.Invoke("Send", _platform, message);
        }

        public Task SendSectionMessage(string company, string department, string section, string name, string message)
        {
            return _proxy.Invoke("messageSection", company,  department,  section,  name,  message);
        }

        public Task SendCompanyMessage(string company, string department, string section, string name, string message)
        {
            return _proxy.Invoke("messageCompany", company, name, message);
        }

    }
}

This is the code I plan to use to start the service (not working yet), I will be adding information to the intent via intent.PutExtras, namely Firstname, lastname, username, name, company, department and section. For now I just set them to null string in the service for testing purposes.:

    public static void StartForegroundServiceComapt<SignalRService>(this Context context, Bundle args = null) where SignalRService : Service
    {
        var intent = new Intent(context, typeof(SignalRService));
        if (args != null)
        {
            intent.PutExtras(args);
        }

        if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
        {
            context.StartForegroundService(intent);
        }
        else
        {
            context.StartService(intent);
        }
    }

This all works as expected, I still need to do some work to clean it up but it is working. I am able to connect to the server hub and send messages to the correct groups of people. Now I need to get this to run as a foreground service.

Prescott Chartier
  • 1,519
  • 3
  • 17
  • 34
  • Maybe I'm misunderstanding what you mean by service and client, but a service should never pop up a message box. – iakobski Jun 01 '19 at 14:40
  • Well, I need to wire up a handler to display the message from the client. I've got an idea Im working on. Once I start the client as a service, I'll wire up the instance to a handler. That may work. I'll post the result here. – Prescott Chartier Jun 02 '19 at 15:37
  • You could show a notification from the service that opens an activity showing the message when the notification is clicked by the user. Can you be more specific about the problems you had translating Java to C#? – Trevor Balcom Jun 03 '19 at 19:33
  • @Trevor Balcom there are just too many functional differences between java code and c# code to sucessfully translate and my knowledge of Signalr is just plain overwhelmed with the errors generated by a conversion. Some classes that exist in java do not exist in c# or the parameters are very different. – Prescott Chartier Jun 04 '19 at 14:36
  • @Trevor Balcom this was the code I was trying to translate. See the answer to the question. https://stackoverflow.com/questions/44542179/how-to-use-signalr-in-android-service – Prescott Chartier Jun 04 '19 at 17:11
  • @PrescottChartier It looks like it can all be translated to Xamarin.Android with some effort. Android Oreo has added a ton of [background restrictions](https://developer.android.com/about/versions/oreo/background). I think you'll have many struggles trying to keep this SignalR connection alive on modern versions of Android even running as a service. You could have more luck with a foreground service, which will give you a notification you could update to show the user new info. Have you considered using Firebase Cloud Messaging to push messages to the phone from a server application? – Trevor Balcom Jun 04 '19 at 17:31
  • @Trevor Balcom A foreground service is exactly what I'm trying to do. – Prescott Chartier Jun 04 '19 at 18:04
  • @PrescottChartier if you want to show what you've done so far, and/or what you're having trouble with then I'm sure we can help. You've already got a popup in the form of the required foreground service notification. – Trevor Balcom Jun 04 '19 at 18:57
  • @Trevor Balcom I added my code to the question – Prescott Chartier Jun 04 '19 at 19:25

1 Answers1

0

Is your app creating a notification channel? You should be passing the notification channel ID to the NotificationCompat.Builder constructor.

It doesn't look like you're calling the RegisterForegroundService method to promote the service to a foreground service. You'll want to call RegisterForegroundService early in the OnCreate override. Modern versions of Android require a foreground service to show a notification within a few seconds or an exception will be thrown.

You may want to add the android.permission.FOREGROUND_SERVICE permission to your Android Manifest because it's required on Android P and later.

I don't think ACR.UserDialogs will work if your app has no current top activity. The service outlives the activity so it's very possible to run into this scenario. You can simply have the service update the existing foreground notification to show the user a new message is available.

Trevor Balcom
  • 3,766
  • 2
  • 32
  • 51
  • All that is there, check `void RegisterForegroundService()`. I anticipate problems with `showMessage` and will deal with that once I get to that point, but everything else you suggest is there. – Prescott Chartier Jun 04 '19 at 22:04