I am learning Xamarin and would like to implement a way for the App to make an HttpClient request in the background about every 20 minutes or so and send a push notification to the user depending on the data it got from the request response. I'm fairly confident this is possible, but from searching on the internet I just haven't seen a way to do this in Xamarin.Forms.
CodePudding user response:
This needs to be done platform-specific. Scheduling the request with android would be a bit simpler, since this could be achieved with a background service running a periodic task and a broadcast receiver. You can read more about it here https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/services/ and https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/broadcast-receivers.
In shared project:
public class BackgroundJobManager
{
private int _currentId;
private readonly List<BackgroundJob> _backgroundJobs = new List<BackgroundJob>();
public IReadOnlyList<BackgroundJob> BackgroundJobs => _backgroundJobs;
private int NextId() => Interlocked.Increment(ref _currentId);
public int AddJob(Action action, TimeSpan delay, TimeSpan interval, string description)
{
var nextJobId = NextId();
MessagingCenter.Subscribe<IAlarmReceiver>(this, nextJobId.ToString(), (sender) => action());
_backgroundJobs.Add(new BackgroundJob() { DueTime = delay, Interval = interval, ID = nextJobId });
return nextJobId;
}
public void RemoveJob(int jobId)
{
MessagingCenter.Unsubscribe<IAlarmReceiver>(this, jobId.ToString());
_backgroundJobs.RemoveWhere(job => job.ID == jobId);
}
}
INotificationManager
public interface INotificationManager
{
event EventHandler<NotificationEventArgs> NotificationReceived;
void Initialize();
void SendNotification(string title, string message);
void ReceiveNotification(string title, string message);
}
NotificationEventArgs
public class NotificationEventArgs : System.EventArgs
{
public string Title { get; set; }
public string Message { get; set; }
}
IAlarmReceiver
public interface IAlarmReceiver
{
}
In your class where you make the request:
private void SetUpBackgroundRequest()
{
Action startBackGroundJob = async () => await MakeYourRequestHere();
_backgroundJobManager.AddJob(startBackGroundJob, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(20), "Making request");
}
Android In your MainActivity.cs add the following:
private Intent _serviceIntent;
protected override void OnPause()
{
base.OnPause();
_serviceIntent = new Intent(this, typeof(PeriodicService));
StartService(_serviceIntent);
}
protected override void OnResume()
{
base.OnResume();
if (_serviceIntent != null)
StopService(_serviceIntent);
}
Periodic Service
[Service]
class PeriodicService : Service
{
private static AlarmHandler alarm = new AlarmHandler();
private readonly BackgroundJobManager _backgroundJobManager = new YourSharedProject.BackgroundJobManager();
public override StartCommandResult OnStartCommand(Intent intent,
StartCommandFlags flags, int startId)
{
foreach(var job in _backgroundJobManager.BackgroundJobs)
{
alarm.SetAlarm(job.DueTime, job.Interval, job.ID);
}
return StartCommandResult.Sticky;
}
public override bool StopService(Intent name)
{
foreach (var job in _backgroundJobManager.BackgroundJobs)
{
alarm.UnsetAlarm(job.ID);
}
return base.StopService(name);
}
public override void OnDestroy()
{
base.OnDestroy();
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
AndroidNotificationManager
public class AndroidNotificationManager : YourSharedProject.NotificationManager
{
private const string ChannelId = "default";
private const string ChannelName = "Default";
private const string ChannelDescription = "The default channel for notifications.";
public const string TitleKey = "title";
public const string MessageKey = "message";;
private bool channelInitialized = false;
private int messageId = 0;
private int pendingIntentId = 0;
private NotificationManager manager;
public static AndroidNotificationManager Instance { get; private set; }
public AndroidNotificationManager() => Initialize();
public override void Initialize()
{
if (Instance == null)
{
CreateNotificationChannel();
Instance = this;
}
}
public override void SendNotification(string title, string message)
{
if (!channelInitialized)
{
CreateNotificationChannel();
}
Show(title, message);
}
public void Show(string title, string message)
{
Interlocked.Increment(ref messageId);
Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
intent.PutExtra(TitleKey, title);
intent.PutExtra(MessageKey, message);
PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, pendingIntentId , intent, PendingIntentFlags.UpdateCurrent);
NotificationCompat.BigTextStyle textStyle = new NotificationCompat.BigTextStyle();
NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, ChannelId)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetStyle(textStyle)
.SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.launchIcon))
.SetSmallIcon(Resource.Drawable.launchIcon)
.SetDefaults((int)NotificationDefaults.All)
.SetAutoCancel(true);
Notification notification = builder.Build();
manager.Notify(messageId, notification);
}
private void CreateNotificationChannel()
{
manager = (NotificationManager)AndroidApp.Context.GetSystemService(Context.NotificationService);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelNameJava = new Java.Lang.String(ChannelName);
var channel = new NotificationChannel(ChannelId, channelNameJava, NotificationImportance.Default)
{
Description = ChannelDescription
};
manager.CreateNotificationChannel(channel);
}
channelInitialized = true;
}
}
AlarmReceiver
[BroadcastReceiver]
class AlarmReciver : BroadcastReceiver, IAlarmReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var id = intent.GetStringExtra("jobId");
MessagingCenter.Send<IAlarmReceiver>(this, id);
}
}
[BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]
class AlarmHandler : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent?.Extras != null)
{
string title = intent.GetStringExtra(View.AndroidNotificationManager.TitleKey);
string message = intent.GetStringExtra(View.AndroidNotificationManager.MessageKey);
View.AndroidNotificationManager manager = View.AndroidNotificationManager.Instance ?? new View.AndroidNotificationManager();
manager.Show(title, message, assignmentId);
}
}
public void SetAlarm(TimeSpan dueTime, TimeSpan interval, int jobId)
{
var alarmIntent = new Intent(Application.Context, typeof(AlarmReciver));
alarmIntent.PutExtra("jobId", jobId.ToString());
var pending = PendingIntent.GetBroadcast(Application.Context,
jobId, alarmIntent, PendingIntentFlags.UpdateCurrent);
var alarmManager = Application.Context.GetSystemService(Context.AlarmService)
.JavaCast<AlarmManager>();
alarmManager.SetRepeating(AlarmType.RtcWakeup, (long)dueTime.TotalMilliseconds,
(long)interval.TotalMilliseconds, pending);
}
public void UnsetAlarm(int jobId)
{
var alarmIntent = new Intent(Application.Context, typeof(AlarmReciver));
var pending = PendingIntent.GetBroadcast(Application.Context,
jobId, alarmIntent, PendingIntentFlags.UpdateCurrent);
var alarmManager = Application.Context.GetSystemService(Context.AlarmService)
.JavaCast<AlarmManager>();
alarmManager.Cancel(pending);
}
}
For iOS it's a bit more complex. IOS offers Background Fetch for refreshing non-critcal content. It is not possible to choose an interval, since the UIApplication.BackgroundFetchIntervalMinimum is based on a lot of things such as app usage, battery life, etc. You would still want to use UIApplication.BackgroundFetchIntervalMinimum to fetch content as often as possible. Another way to do this would be with Remote Notifications, using Apple Push Notification service (APNs). Info and examples on both here https://docs.microsoft.com/en-us/xamarin/ios/app-fundamentals/backgrounding/ios-backgrounding-techniques/updating-an-application-in-the-background.
Another option for both platforms is Azure as described here https://docs.microsoft.com/en-us/azure/developer/mobile-apps/notification-hubs-backend-service-xamarin-forms