HMS-Core / hms-flutter-plugin

This repo contains all of Flutter HMS plugins.
https://developer.huawei.com/consumer/en/doc/overview/HMS-Core-Plugin?ha_source=hms1
Apache License 2.0
285 stars 141 forks source link

backgroundMessageCallback not working on huawei push plugin #50

Closed anas-iqbal closed 3 years ago

anas-iqbal commented 3 years ago

Hi, I have implemented HMS push kit and everything is working fine except background message handler, Push.onMessageReceivedStream is working fine but when I send notification when app is on background I get error in backgroundMessageCallback, I get the following error

Unhandled Exception: MissingPluginException(No implementation found for method localNotification on channel com.huawei.flutter.push/method)

I am posting my code below, please let me know if any additional code required, thanks :)

version on pubsec : huawei_push: ^5.0.2+301

My Application class

import com.huawei.hms.flutter.push.PushPlugin
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

class MainApplication : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {

    override fun onCreate() {
        super.onCreate()
        PushPlugin.setPluginRegistrant(this)

    }

    override fun registerWith(registry: PluginRegistry?) {
        registry?.registrarFor("com.huawei.hms.flutter.push.PushPlugin");
    }
}

My HMS helper function ( which I call on main.dart)

 static void backgroundMessageCallback(RemoteMessage remoteMessage) async {
    String data = remoteMessage.data;
    Push.localNotification({
      HMSLocalNotificationAttr.TITLE: '[Headless] DataMessage Received',
      HMSLocalNotificationAttr.MESSAGE: 'Hello My Name'
    });
    print("onRemoteMessageReceived" + " Data: " + data);
  }

  static initNotification() async {
    TokenEventChannel.receiveBroadcastStream()
        .listen(_onTokenEvent, onError: _onTokenError);
    Push.onNotificationOpenedApp.listen(_onNotificationOpenedApp);
    var initialNotification = await Push.getInitialNotification();
    Push.onMessageReceivedStream
        .listen(_onMessageReceived, onError: _onMessageReceiveError);
    bool backgroundMessageHandler =
        await Push.registerBackgroundMessageHandler(backgroundMessageCallback); // returns true
    await Push.getToken("");
    await Push.turnOnPush();
  }
`

manifiest

 <
application
        android:name=".MainApplication"

        <!-- Set push kit auto enable to true (for obtaining the token on initialize)-->
        <meta-data
            android:name="push_kit_auto_init_enabled"
            android:value="true" />
        <!-- These receivers are for sending scheduled local notifications -->
        <receiver android:name="com.huawei.hms.flutter.push.receiver.local.HmsLocalNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        <receiver android:name="com.huawei.hms.flutter.push.receiver.local.HmsLocalNotificationScheduledPublisher"
            android:enabled="true"
            android:exported="true">
        </receiver>

        <receiver android:name="com.huawei.hms.flutter.push.receiver.BackgroundMessageBroadcastReceiver"
            android:enabled="true" android:exported="true">
            <intent-filter>
                <action android:name="com.huawei.hms.flutter.push.receiver.BACKGROUND_REMOTE_MESSAGE" />
            </intent-filter>
        </receiver>

        <provider
            android:authorities="${applicationId}.HMSContentProvider"
            android:name="com.huawei.hms.flutter.analytics.AnalyticsContentProvider"/>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

build grade

buildscript {
    ext.kotlin_version = '1.3.72'
    repositories {
        google()
        jcenter()
        maven { url 'https://developer.huawei.com/repo/' }

        flatDir { dirs 'libs' }
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.3'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
        classpath 'com.huawei.agconnect:agcp:1.4.1.300'

    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://developer.huawei.com/repo/' }
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
aktug commented 3 years ago

Hello @anas-iqbal

When the application is in a background or killed state, the onMessageReceivedStream handler will not be called when receiving data messages. Instead, you need to setup a background callback handler via the registerBackgroundMessageHandler method.

To setup a background handler, call the registerBackgroundMessageHandler outside of your application logic as early as possible:

Step 1 Add an Application.java class to your app in the same directory as your MainActivity.java. This is typically found in ./android/app/src/main/java/\/

image

package com.huawei.hms.flutter.push_example;

import com.huawei.hms.flutter.push.PushPlugin;

import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class Application extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
    @Override
    public void onCreate() {
        super.onCreate();
        PushPlugin.setPluginRegistrant(this);
    }

    @Override
    public void registerWith(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry);
    }
}

Step 2 In Application.java, make sure to change package com.huawei.hms.flutter.push_example; to your package's identifier. Your package's identifier should be something like com.domain.application_name

package com.domain.application_name;

Step 3 Set name property of application in AndroidManifest.xml. This is typically found in ./android/app/src/main/

<application
    android:name=".Application"
    android:icon="@mipmap/ic_launcher"
    android:label="HMS Push Demo" ... >
    ....
</application>

Step 4 Define a top-level or static function to handle background data messages

//main.dart
static void backgroundMessageCallback(RemoteMessage remoteMessage) async {
    String data = remoteMessage.data;

    Push.localNotification({
      HMSLocalNotificationAttr.TITLE: '[Headless] DataMessage Received',
      HMSLocalNotificationAttr.MESSAGE: data
    });
}

Step 5 Set backgroundMessageCallback handler when calling registerBackgroundMessageHandler

//main.dart
...
@override
void initState() {
    super.initState();
    ...
    bool backgroundMessageHandler =
      await Push.registerBackgroundMessageHandler(backgroundMessageCallback);
    print("backgroundMessageHandler registered: $backgroundMessageHandler");
    ...
}
...
static void backgroundMessageCallback(RemoteMessage remoteMessage) async {
    String data = remoteMessage.data;

    Push.localNotification({
      HMSLocalNotificationAttr.TITLE: '[Headless] DataMessage Received',
      HMSLocalNotificationAttr.MESSAGE: data
    });
}
aktug commented 3 years ago

You can find a proper working demo app in the example folder 👍👍

anas-iqbal commented 3 years ago

Thank you for responding, if you refer to my code again u will see that I have already implemented registerBackgroundMessageHandler and it does gets called but it gives error on the following line inside backgroundMessageCallback method u can see below

static void backgroundMessageCallback(RemoteMessage remoteMessage) async {
    String data = remoteMessage.data;
    Push.localNotification({
      HMSLocalNotificationAttr.TITLE: '[Headless] DataMessage Received',
      HMSLocalNotificationAttr.MESSAGE: 'Hello My Name'
    }); // THIS LINE GIVES THE ERROR
    print("onRemoteMessageReceived" + " Data: " + data);
  }

Additionally your sample code is written on java, since I am using kotlin so your sample code isn't helping me much and even gives me the following error Type mismatch: inferred type is PluginRegistry but FlutterEngine was expected when I convert this method below

override fun registerWith(registry: PluginRegistry) {
        GeneratedPluginRegistrant.registerWith(registry) // red line on registry and gives the above mismatch error
        // That is why I am using the below line instead
        registry?.registrarFor("com.huawei.hms.flutter.push.PushPlugin");
    } 

As per your answer I also moved the registerBackgroundMessageHandler before any logic right after init but didn't help either. Please advise.

Thanks

anas-iqbal commented 3 years ago

Also sharing the result of my flutter doctor if it helps.

[✓] Flutter (Channel stable, 1.20.3, on Mac OS X 10.15.5 19F101, locale en-PK)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3) [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) [!] Android Studio (version 4.1) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [✓] VS Code (version 1.52.0)

aktug commented 3 years ago

@anas-iqbal thank you for your interest 😊

You should separate Activity and Application files like that;

//MainActivity.java

import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
    }
}
//MainApplication.java

import com.huawei.hms.flutter.push.PushPlugin;

import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class Application extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
    @Override
    public void onCreate() {
        super.onCreate();
        PushPlugin.setPluginRegistrant(this); // <--- Add this line
    }

    @Override
    public void registerWith(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry);
    }
}

and you should remove flutterEmbedding from AndroidManifest.xml file.

<meta-data
            android:name="flutterEmbedding"
            android:value="2" />

The Android embedding v2 includes support for standard Android life-cycle events and the separation of Flutter execution from the Android UI, for using that backgroundMessageHandler we should remove that embedding 👍

anas-iqbal commented 3 years ago

@aktug thanks for getting back to me, I tried removing the below lines from manifest

<meta-data
            android:name="flutterEmbedding"
            android:value="2" />

but after that my project stopped building as my project is build on Android embedding v2 (as it is recommended by Flutter) and most of the plugins I am using requires Android embedding v2 I am getting below errors,

The plugin `plugin name` requires your app to be migrated to the Android embedding v2. Follow the steps on https://flutter.dev/go/android-project-migration

MainActivity.kt: (108, 48): Type mismatch: inferred type is FlutterEngine but PluginRegistry! was expected

I think downgrading my project to v1 won't be an option so is there any work around we can do to get it running on flutterEmbedding v2 projects?

Thank you

obaid-saleem commented 3 years ago

@aktug Some packages in my existing project are dependent on v2 embedding. Removing this line causes those plugins to crash. Additionally, I believe Flutter devs are encouraging to use v2 embedding moving forward. Any possibility we can get a working sample with latest Flutter version and Kotlin with embedding v2 enabled?

Reference: https://stackoverflow.com/questions/59988211/what-is-the-difference-between-flutter-android-embedding-v1-and-v2

aktug commented 3 years ago

Hello @obaid-saleem and @anas-iqbal ,

When we're developing this background feature, there was not any other solution for implementing this feature without downgrading to flutterEmbedding v1, did not figure it out without it.

For now, if anyone wants to use the backgroundMessageHandler feature, should remove that metadata or downgrade to v1. We're chasing the Flutter change history, if a more reliable way to implement this feature with v2, we'll update our plugin and we're open to new ideas as always 👍

Thank you for your interest 👍

anas-iqbal commented 3 years ago

Hello @aktug

Finally got it working with flutterEmbedding v2, after registering the plugin manually in my application class. Below is my updated application class

class MainApplication : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {

    override fun onCreate() {
        super.onCreate()
        PushPlugin.setPluginRegistrant(this)
    }

    override fun registerWith(registry: PluginRegistry?) {
        if (!registry!!.hasPlugin("com.huawei.hms.flutter.push.PushPlugin")) {
            PushPlugin.registerWith(registry?.registrarFor("com.huawei.hms.flutter.push.PushPlugin"))
        }
    }
}

I think we have manually register the v1 plugins in v2 projects in order to use them.

anas-iqbal commented 3 years ago

Hello @aktug

I just ran into another problem, can you please have a look? I am getting data on my background message handler method, but then user clicks on notification and my onNotificationOpenedApp is called I am not getting any data. as you can see in the images below

notification on background method (it's showing the data)

Screenshot 2020-12-21 at 12 09 36 PM

Same notification on notification tap method (data is null)

Screenshot 2020-12-21 at 12 09 06 PM
aktug commented 3 years ago

Case 1: Notification Message This type of message does not trigger onMessageReceived event. image You can access datas and infos from onNotificationOpenedApp image

Case 2: Data Message This type of message triggers onMessageReceived event. image image In the demo project, if any "data message" received, the Push.localNotification method calling with these received data message's title and message infos. As you can see, there is no field for data in that method so we can't set any data to localnotifications. We noted that the feature will be implemented in the next version.

Thank you so much for your collaborations 🤩

anas-iqbal commented 3 years ago

I know its an old and closed issue, but just for an update so if may help anyone, I ended up using flutter local notification plugin along with this plugin for handling data payload. :)

dvarelae858 commented 9 months ago

the backgroundMessageCallback MUST BE a top-level or static function

MarcosZunigaT commented 4 weeks ago

Hi everyone, if you are searching information about backgroundMessageHandler using flutter v2 pls follow this link https://developer.huawei.com/consumer/en/doc/HMS-Plugin-Guides/receiving-data-messages-at-killed-state-0000001058199547?ha_source=hms1. And create the backgrounMessageCallback like this

@pragma('vm:entry-point') void onMessageReceived(RemoteMessage remoteMessage) { String data = remoteMessage.getData ?? '';

if (data.isNotEmpty) {

try {

  final hNotification =  Model.fromJson(data);

  Push.localNotification({
    HMSLocalNotificationAttr.TITLE: hNotification.title,
    HMSLocalNotificationAttr.MESSAGE: hNotification.message,
    HMSLocalNotificationAttr.BIG_PICTURE_URL: hNotification.image,
    HMSLocalNotificationAttr.LARGE_ICON: hNotification.image,
    HMSLocalNotificationAttr.SMALL_ICON: '@drawable/ic_huawei_notification',
  });
} catch (error) {
  debugPrint('error local notification: ${error.toString()}');
}

} }

@pragma('vm:entry-point') is the most important code to wake up in background like FirebaseNotifications this work for me in app gallery deployed. And is not in official documentation

This code work actually in 2024 using flutter huawei_push: ^6.12.0+302. I hope help to anyone :)