7

I build a to-do list app that should show notifications to remind about tasks. In order to be able to schedule the notifications to the exact minute of the deadline, I pass the notifications data from flutter to kotlin, and showing the notification from a Broadcast receiver.

Here I am sending the notifications data to kotlin:

 await platform.invokeMethod('setNextNotification', {
      'tasksNames': tasksNames,
      'notificationsTimeInMillis': notificationsTimeInMillis
    });

This is how I get the data inside FlutterActivity:

private const val CHANNEL = "flutter.native/helper"

class MainActivity : FlutterActivity() {

companion object {
    const val TASKS_NAMES_EXTRA = "tasksNames"
    const val NOTIFICATIONS_TIME_IN_MILLIS_EXTRA = "notificationsTimeInMillis"

}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)

    // Init the AlarmManager.
    val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

    // We got here from the setNotifications() method in flutter...
    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
        if (call.method == "setNextNotification") {

            // Get the time till next notification
            val notificationsTimeInMillis: ArrayList<Long> = call.argument(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
                    ?: ArrayList()

            // Create a pending intent for the notifications
            val pIntent: PendingIntent? = createPendingIntent(call.argument(TASKS_NAMES_EXTRA), call.argument(TIME_LEFT_TEXTS_EXTRA), notificationsTimeInMillis, this)

            // Cancel all alarms
            while (alarmManager.nextAlarmClock != null)
                alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)

            // Set the alarm
            setAlarm(notificationsTimeInMillis[0], pIntent, alarmManager)

        } 
    }
}

private fun setAlarm(notificationTime: Long, pIntent: PendingIntent?, alarmManager: AlarmManager) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // The API is 23 or higher...
        alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(notificationTime, pIntent), pIntent)
    } else { // The API is 19 - 22...
        // We want the alarm to go of on the exact time it scheduled for so we use the setExact method.
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, notificationTime, pIntent)
    }

}

private fun createPendingIntent(tasksNames: ArrayList<String>?, timeTillNotificationsInMillis: ArrayList<Long>?,
                                context: Context): android.app.PendingIntent? {

  
    return try {

        val intent: android.content.Intent = android.content.Intent(context, AlarmManagerHelperWakeful::class.java)
        intent.action = "notification"
     
        intent.putStringArrayListExtra(TASKS_NAMES_EXTRA, tasksNames)
        intent.putStringArrayListExtra(NOTIFICATIONS_TIME_IN_MILLIS_EXTRA, timeTillNotificationsInMillisAsString)
        android.app.PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    } catch (e: java.lang.Exception) {
        null
    }
}

}

And this is how I show the notification on the BroadcastReceiver, and after that sets the next notification:

Class AlarmManagerHelperWakeful : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {

    if (intent != null && intent.action == "notification" && context != null) {
        
       
        val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
                ?: ArrayList()

        val notificationsTimeInMillisAsString: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.NOTIFICATIONS_TIME_IN_MILLIS_EXTRA)
                ?: ArrayList()

        if (tasksLabels.size > 0) {
          
            // Create a notification manager.
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            var builder = NotificationCompat.Builder(context) // The initialization is for api 25 or lower so it is deprecated.


            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // This is API 26 or higher...
                // Create a channel for API 26 or higher;
                val channelId = "channel_01" // The id of the channel.
                if (notificationManager.getNotificationChannel(channelId) == null) {
                    val channel = NotificationChannel(channelId,
                            context.getString(R.string.notification_channel_name),
                            NotificationManager.IMPORTANCE_DEFAULT)
                    notificationManager.createNotificationChannel(channel)

                }
                // Update the builder to a no deprecated one.
                builder = NotificationCompat.Builder(context, channelId)
            }

            // Set the notification details.
            builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
            builder.setContentTitle(tasksLabels[0])
            builder.setContentText(someText)
            builder.priority = NotificationCompat.PRIORITY_DEFAULT

            notificationId = someUniqueId

            // Show the notification.
            notificationManager.notify(notificationId.toInt(), builder.build())

            // Remove this notification from the notifications lists.
            tasksLabels.removeAt(0)
            notificationsTimeInMillisAsString.removeAt(0)

            // There are more notifications...
            if (tasksLabels.size > 0) {

                // Init the AlarmManager.
                val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

                // Cancel all alarms
                while (alarmManager.nextAlarmClock != null)
                    alarmManager.cancel(alarmManager.nextAlarmClock.showIntent)

                // Create a pending intent for the notifications
                val pIntent: PendingIntent? = createPendingIntent(tasksLabels, cnotificationsTimeInMillisAsString, context)

                // Set the alarm
                setAlarm(notificationsTimeInMillisAsString[0].toLong(), pIntent, alarmManager)

            }

        }

    } else {
        if (intent == null) {
            Log.d("Debug", "Checking: intent == null")
        } else if ( intent.action != "notification") {
            Log.d("Debug", "Checking: intent.action != notification")
            val tasksLabels: ArrayList<String> = intent.getStringArrayListExtra(MainActivity.TASKS_NAMES_EXTRA)
                    ?: ArrayList()
            Log.d("Debug", "Checking: tasksNames.size inside else if" + tasksLabels.size)
        }
    }

}
}

Everything works perfectly fine unless I restart my device. Then the Broadcast receiver gets an intent without any data. For the BoradcastReceiver to get intent with notifications data, I need to invoke the method from the flutter code (the method that sends notifications data to the kotlin code), which means that for now, the user has to enter the app for that. Otherwise, user will not see notifications until entering my app and reinvoking the flutter code.

How can I overcome that issue?

Simple UX Apps
  • 611
  • 2
  • 10
  • 20
  • you can grab more idea refering to this article https://medium.com/flutter/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124 – Jiten Basnet Aug 18 '20 at 07:42
  • @JitenBasnet That's a lot of code to digest. But from what I can understand, I invoke a method in Kotlin from flutter and then receive data from Kotlin to dart using a callback. The question is, whether the callback stays alive after the user restarts the device, or turning off the device kills the callback so I can't send data back to dart when the device is turning on. – Simple UX Apps Aug 23 '20 at 16:13
  • I have collected few sources here as well. https://stackoverflow.com/questions/63228013/how-to-use-flutter-method-channel-in-background-app-minimised-closed – Jiten Basnet Aug 24 '20 at 05:39

2 Answers2

0

You should use Push Notification instead of sending local notification to your broadcast receiver. There're lot of cases that make your app cannot send local notification. ex: user closed app (lot of users alway close app after using), OS closed app or clean memory, Dart method is crashing. Firebase FCM is pretty simple, it's much more simple than you solution that's using broadcast receiver. Also totally free.

https://pub.dev/packages/firebase_messaging

Pushwoosh is good too, it has schedule notification

https://pub.dev/packages/pushwoosh

Using push notification also has other pros that you app will also work on iOS, doesn't need to keep your app running on background which is very bad idea if your app doesn't have any special features that need to run background (music player, geo location, VOIP)

In case you don't want to use Push Notification. Take a look at this lib: https://pub.dev/packages/flutter_local_notifications

Long Phan
  • 857
  • 7
  • 6
  • Hi Long and thank you for your answer. I am using flutter local notifications for IOS, but on android, the delivery of the notifications is inexact which makes it useless for android. I read the firebase messaging doc, but couldn't find how to schedule a notification for a task reminder from within the app. Is there any explanation of how can I do that? – Simple UX Apps Aug 23 '20 at 10:36
0

Right now you are sending data from dart to the native plugin. You might try it the other way around. This example shows how you can get the native android reboot event. Next, you can use this example to fetch the required data. After fetching the data you can set the notification.

You could also try to store the information of the last notification in SharedPreferences, fetch that on boot, and set the notification.

Robin Dijkhof
  • 18,665
  • 11
  • 65
  • 116
  • I know how to listen to the reboot event. I need to know how to invoke a method in flutter when that happens. The example in your second link is not that clear. Where should I put the myUtilsHandler method? How do I initializing the channel object inside java? Is there more code inside the Java code? – Simple UX Apps Aug 24 '20 at 14:15