Search code examples
c#xamarin.formsxamarin.androidalarmmanager

Schedule Alarm manager to run a method every Thursday


For example i want to have an alarm that will fire every Thursday at 12PM How would i do this?

I have something implemented but isnt working properly,with the code that i have, today is wednesday 15, if change the date of the phone to 16 thrusday, the app doesnt do anything, if i change the date of the phone for the next wednesday 22 the phone sends a notification, but only should send on thursdays.

Here is my code:

MainActivity:

protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;
        base.OnCreate(bundle);
        AsNumAssemblyHelper.HoldAssembly();
        global::Xamarin.Forms.Forms.Init(this, bundle);
        ImageCircleRenderer.Init();

        Intent alarmIntent = new Intent(this, typeof(AlarmReceiver));
        PendingIntent pending = PendingIntent.GetBroadcast(this, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
        AlarmManager alarmManager = GetSystemService(AlarmService).JavaCast<AlarmManager>();

        //AlarmType.RtcWakeup – it will fire up the pending intent at a specified time, waking up the device
        alarmManager.SetRepeating(AlarmType.RtcWakeup,BootReceiver.FirstReminder(), BootReceiver.reminderInterval, pending);
        PendingIntent pendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent, 0);

        LoadApplication(new App());

    }

BootReceiver:

[BroadcastReceiver]
[IntentFilter(new[] { Intent.ActionBootCompleted })]
public class BootReceiver : BroadcastReceiver
{

    //the interval currently every one minute
    //to set it to dayly change the value to 24 * 60 * 60 * 1000
    public static long reminderInterval = AlarmManager.IntervalDay * 7;
    //public static long reminderInterval = 30 * 1000;

    public static long FirstReminder()
    {
        Java.Util.Calendar calendar = Java.Util.Calendar.Instance;
        calendar.Set(Java.Util.CalendarField.DayOfWeek, Calendar.Thursday);
        return calendar.TimeInMillis;

    }

    public override void OnReceive(Context context, Intent intent)
    {
        Console.WriteLine("BootReceiver: OnReceive");
        var alarmIntent = new Intent(context, typeof(AlarmReceiver));
        var pending = PendingIntent.GetBroadcast(context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
        AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);

        alarmManager.SetRepeating(AlarmType.RtcWakeup, FirstReminder(), reminderInterval, pending);
        PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 0, alarmIntent, 0);
    }
}

AlarmReceiver:

[BroadcastReceiver]
public class AlarmReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        try
        {
                var title = "Something";
                var message = "Something";

                Intent backIntent = new Intent(context, typeof(MainActivity));
                backIntent.SetFlags(ActivityFlags.NewTask);


                var builder =
                    new Notification.Builder(context)
                        .SetContentTitle(title)
                        .SetContentText(message)
                        .SetAutoCancel(true)
                        .SetSmallIcon(Resource.Drawable.icon)
                        .SetDefaults(NotificationDefaults.All);
                var notification = builder.Build();
                var manager = NotificationManager.FromContext(context);
                manager.Notify(1331, notification);
            }

        }
        catch (Exception)
        {

        }
    }
}

Solution

  • For api levels below 19 you should use AlarmManager.setRepeating() and your alarms will trigger exactly at specified time. But as the document said, when your device api levels 19 and above this will no longer work.

    Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

    Also while using alarmManager.SetExact() method :

    The alarm will be delivered as nearly as possible to the requested trigger time.

    So if you would like to achieve exact repeating alarm, as @Dus said here is two suggestions :

    • Accept the time delays(but maybe consider using JobSchedular which is more recommended and will save you battery).

    Or :

    • Use SetExactAndAllowWhileIdle which might cause you battery issues(use this carefully, too many alarms will be bad for your battery). This method isn't repeating, so you have to declare the next job to be run at the service which the pendingIntent opens.

    Convert Dus's code to C# :

    AlarmManager alarmManager = (AlarmManager)this.GetSystemService(Context.AlarmService);
    var ALARM_TYPE = AlarmType.RtcWakeup;
    if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M)
    {
        alarmManager.SetExactAndAllowWhileIdle(ALARM_TYPE, calendar.TimeInMillis, pendingIntent);
    }
    else if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop)
    {
        alarmManager.SetExact(ALARM_TYPE, calendar.TimeInMillis, pendingIntent);
    }
    else if
    {
        alarmManager.Set(ALARM_TYPE, calendar.TimeInMillis, pendingIntent);
    }
    

    Update :

    The idea behind doze is to attempt to prevent draining the battery. Repeated alarms drain battery, so the builtin way to repeat alarms by passing an extra parameter was removed in android 6. So it requires you to manually reschedule the alarm.

    You could reschedule the alarm immediately when it fires, before doing anything else that could go wrong and prevent the alarm from being rescheduled.

    Update 2 :

    A simple demo about use SetExactAndAllowWhileIdle method to implement a repeating alarm, hope this can help you.

    The first time set an alarm :

    var intent = new Intent(this, typeof(RepeatingAlarm));
    var source = PendingIntent.GetBroadcast(this, 0, intent, 0);
    
    // Schedule the alarm!
    var am = (AlarmManager)GetSystemService(AlarmService);
    
    //After 15s, use the RepeatingAlarm to show a toast
    am.SetExactAndAllowWhileIdle(AlarmType.ElapsedRealtimeWakeup, SystemClock.ElapsedRealtime() + 15 * 1000, source);
    

    In the RepeatingAlarm :

    [BroadcastReceiver]
    public class RepeatingAlarm : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            //Every time the `RepeatingAlarm` is fired, set the next alarm
            var intentForRepeat = new Intent(context, typeof(RepeatingAlarm));
            var source = PendingIntent.GetBroadcast(context, 0, intent, 0);
            var am = (AlarmManager)Android.App.Application.Context.GetSystemService(Context.AlarmService);
            am.SetExactAndAllowWhileIdle(AlarmType.ElapsedRealtimeWakeup, SystemClock.ElapsedRealtime() + 15 * 1000, source);
    
            Toast.MakeText(context, "repeating_received and after 15s another alarm will be fired", ToastLength.Short).Show();
        }
    }