MaikuB / flutter_local_notifications

A Flutter plugin for displaying local notifications on Android, iOS, macOS and Linux
2.43k stars 1.38k forks source link

How can flutter_local_notifications wake up the screen and make notifications when the screen is off #2234

Open laterdayi opened 7 months ago

laterdayi commented 7 months ago

How can flutter_local_notifications wake up the screen and make notifications when the screen is off

amervelic commented 7 months ago

Do you mean 'to wake up' the device when a notification arrives?

yashpalzala commented 6 months ago

yes, in latest OS the screen does not necessarily wakeup when the notification arrives. And this could cause to miss important notifications. 'Wake up' means if the screen is locked we can "wake up" somehow for few seconds so user knows some important notification has come

amervelic commented 6 months ago

Firebase Messaging background handler

I am using firebase_messaging and a background handler (separate isolates/thread) (@pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message)) where messages arrive even when the application is turned off. I have created a small plugin for the project that uses deprecated code but works on API 33 and 34 and older versions. This is all the code I use in the plugin for "wake up"

(Android Kotlin)

private fun wakeUp() {
    val pm = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
    val isScreenOn = if (Build.VERSION.SDK_INT >= 20) pm.isInteractive else pm.isScreenOn // check if screen is on
    if (!isScreenOn) {
        val wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "myApp:notificationLock")
        wl.acquire(3000) //set your time in milliseconds
        Log.i("Wakeup","Wakeup !")
    }
}
yashpalzala commented 6 months ago

yes, even i have created the plugin, but are you able to call this native code from background isolate (i.e. firebaseMessagingBackgroundHandler())? As far as i know methodchannel is dependent on flutter engine and since you are on separate isolate you cannot use methodchannel the way we normally use.

I'm using this to invoke native code : try { const platform = MethodChannel('com.example.app/native'); await platform.invokeMethod('wakeupscreen'); } on PlatformException catch (e) { print("Failed to invoke the method: '${e.message}'."); }

but it show unimplemented error Unhandled Exception: MissingPluginException(No implementation found for method wakeupscreen on com.example.app/native) if i call nativemethod on fcmbackgroundmessagehandler

amervelic commented 6 months ago

simple plugin code

package com.amervelic.wake_device

import android.content.Context
import android.os.Build
import android.os.PowerManager
import android.util.Log
import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.StandardMethodCodec

/** WakeDevicePlugin */
class WakeDevicePlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel
    private lateinit var applicationContext: Context

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        applicationContext = flutterPluginBinding.applicationContext
        val taskQueue =
            flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
        channel = MethodChannel(
            flutterPluginBinding.binaryMessenger,
            "wake_device",
            StandardMethodCodec.INSTANCE,
            taskQueue
        )
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        if (call.method == "getPlatformVersion") {
            result.success("Android ${android.os.Build.VERSION.RELEASE}")
        }

        else if (call.method == "wakeDevice") {
            wakeUp()
            result.success(null)
        }

        else {
            result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }

    private fun wakeUp() {
        val pm = applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
        val isScreenOn = if (Build.VERSION.SDK_INT >= 20) pm.isInteractive else pm.isScreenOn // check if screen is on
        if (!isScreenOn) {
            val wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "myApp:notificationLock")
            wl.acquire(3000) //set your time in milliseconds
            Log.i("Wakeup","Wakeup !")
        }
    }
}
yashpalzala commented 6 months ago

Nope see i get the same error when calling it inside a firebaseMessagingBackgroundHandler() callback function:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method wakeDevice on channel wake_device)

But if I call it outside the firebaseMessagingBackgroundHandler() then it works well.

@amervelic can you show your implementation on flutter side?

amervelic commented 6 months ago

github repo

yashpalzala commented 6 months ago

Nope, the example in the repo does not call the method inside a background isolate. The example you have uploaded call the native method inside main UI isolate and not background isolate. Try the same example inside a background isolate it won't work.

amervelic commented 6 months ago

I use it in the application and it is called without any issues.

// Entry point for handling background messages received by Firebase Messaging.
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  // Ensure Flutter notifications are set up before showing a notification.
  await NotificationService.setupFlutterNotifications();
  // Show a notification using the received message.
  NotificationService.handleFlutterNotification(message);
  await NotificationService.wakeDevice();
  print('Handling a background message ${message.messageId}');
  await NotificationService.keepSync();
yashpalzala commented 6 months ago

@pragma('vm:entry-point') Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async { const MethodChannel _channel = MethodChannel('wake_device'); //wake up screen try { await _channel.invokeMethod('wakeDevice'); } on PlatformException catch (e) { print("Failed to invoke method: '${e.message}'."); }

here.. this is my code to invoke method, looks almost similar to yours.

yashpalzala commented 6 months ago

Hi @amervelic I can make a sample project on git and you can do the changes that worked for you on that project. Should we do it? That will help both of us understand exactly what's working and what's not.