Hypertrack is preventing FCM notification in Flutter

旧巷老猫 提交于 2020-12-15 07:53:09

问题


When I use hypertrack_plugin along with firebase_messaging in Flutter, I am not able to receive any message in Android. However, it works fine in iOS.

Version of hypertrack_plugin: 0.1.3 Version of firebase_messaging: 7.0.3


回答1:


UPDATE

This issue has been fixed in the plugin update which is in the version hypertrack_plugin: 0.1.4

Problem

You are facing this issue because there is multiple service class that extends from FirebaseMessagingService. Because of this, messages are received in one class with high priority and not the other.

Solution

Method 1

Add the following to your AndroidManifest.xml file

<service android:name="io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService">
      <intent-filter android:priority="100">
            <action android:name="com.google.firebase.MESSAGING_EVENT"/>
      </intent-filter>
</service>

How it works?

The priority set for FlutterFirebaseMessagingService in its manifest file is zero (default) but HyperTrackMessagingService in its manifest file is declared with a priority of 5 (version 4.8.0 now). The above solution simply overrides the priority and lets incoming messages come to FlutterFirebaseMessagingService instead of HyperTrackMessagingService.

Limitation:

Although HyperTrack will work fine, HyperTrack uses FCM for device-server communication for optimization which won't function without FCM. However, you might not notice this.

Method 2

Forward incoming message in HyperTrackMessagingService to FlutterFirebaseMessagingService in the plugin hypertrack_plugin using reflection.

Steps:

  1. You will need to download hypertrack_plugin source code and use it as a local dependency. How?
  2. Create a new class inside the library sdk-flutter/android/src/main/kotlin/com/hypertrack/sdk/flutter/MyFirebaseMessagingService.java
package com.hypertrack.sdk.flutter;

import android.annotation.SuppressLint;
import android.util.Log;

import com.google.firebase.messaging.RemoteMessage;
import com.hypertrack.sdk.HyperTrackMessagingService;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@SuppressLint("LongLogTag")
public class MyFirebaseMessagingService extends HyperTrackMessagingService {

    private static final String TAG = "MyFirebaseMessagingService";

    private Class<?> serviceClass;
    private Object serviceObject;

    public MyFirebaseMessagingService() {
        try {
            serviceClass = Class.forName("io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService");
            serviceObject = serviceClass.newInstance();
            injectContext();

            Log.d(TAG, "io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService is found");
        } catch (Throwable t) {
            Log.w(TAG, "Can't find the class io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService", t);
        }
    }

    @Override
    public void onNewToken(final String s) {
        super.onNewToken(s);
        injectToken(s);
    }

    @Override
    public void onMessageReceived(final RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        injectMessage(remoteMessage);
    }

    public void injectToken(String newToken) {
        if (serviceClass != null) {
            try {
                Method sendTokenRefresh = serviceClass.getMethod("onNewToken", String.class);
                sendTokenRefresh.invoke(serviceObject, newToken);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                Log.w(TAG, "Can't inject token due to error ", e);
            }
        }
    }

    public void injectMessage(RemoteMessage remoteMessage) {
        if (serviceClass != null) {
            try {
                Method sendTokenRefresh = serviceClass.getMethod("onMessageReceived", RemoteMessage.class);
                sendTokenRefresh.invoke(serviceObject, remoteMessage);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                Log.w(TAG, "Can't inject message due to error ", e);
            }
        }
    }

    private void injectContext() {
        if (serviceObject != null) {
            if (setField(serviceObject, "mBase", this)) {
                Log.d(TAG, "context is set to io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService");
            }
        }
    }

    private boolean setField(Object targetObject, String fieldName, Object fieldValue) {
        Field field;
        try {
            field = targetObject.getClass().getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            field = null;
        }
        Class<?> superClass = targetObject.getClass().getSuperclass();
        while (field == null && superClass != null) {
            try {
                field = superClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                superClass = superClass.getSuperclass();
            }
        }
        if (field == null) {
            return false;
        }
        field.setAccessible(true);
        try {
            field.set(targetObject, fieldValue);
            return true;
        } catch (IllegalAccessException e) {
            return false;
        }
    }
}
  1. Edit manifest file sdk-flutter/android/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.hypertrack.sdk.flutter">

    <application>
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false" >
            <intent-filter android:priority="100" >
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>
</manifest>

How it works?

MyFirebaseMessagingService extended from HyperTrackMessagingService, is declared in the AndroidManifest.xml file with higher priority (definitely higher than HyperTrackMessagingService). This will allow messages directly to come to the new class. This will also remove the limitation in method 1. Now we forward the message also to FlutterFirebaseMessagingService using reflection.

Limitation:

No limitation, but you need to manually update the hypertrack_plugin when the host library is updated. It's good to have this update in the plugin itself which is not present now (Nov 13)

Method 3

Now we will not touch any code in the library but write our own code. We will propose a solution in a safe way. You don't have to add hypertrack_plugin as local dependencies.

  1. Create class <project_root>/android/app/src/main/<your_package_name>/MyFirebaseMessagingService.java
package com.example.myapp;

import android.annotation.SuppressLint;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@SuppressLint("LongLogTag")
public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMessagingService";

    // put all the firebase messaging service classes used in your project here
    private String[] fcmClasses = new String[]{
            "io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService",
            "com.hypertrack.sdk.HyperTrackMessagingService"};

    @Override
    public void onNewToken(@NotNull String token) {
        Log.d(TAG, "onNewToken()");
        super.onNewToken(token);
        injectToken(token);
    }

    @Override
    public void onMessageReceived(@NotNull RemoteMessage remoteMessage) {
        Log.d(TAG, "onMessageReceived()");
        super.onMessageReceived(remoteMessage);
        injectMessage(remoteMessage);
    }

    public void injectToken(String newToken) {
        Log.d(TAG, "injectToken()");

        for (String fcmClass : fcmClasses) {
            try {
                Class<?> serviceClass = Class.forName(fcmClass);
                Object serviceObject = serviceClass.newInstance();
                injectContext(serviceClass, serviceObject);

                Method sendTokenRefresh = serviceClass.getMethod("onNewToken", String.class);
                sendTokenRefresh.invoke(serviceObject, newToken);

            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | InstantiationException e) {
                Log.w(TAG, "Can't inject token due to error ", e);
            }
        }
    }

    public void injectMessage(RemoteMessage remoteMessage) {
        Log.d(TAG, "injectMessage()");

        for (String fcmClass : fcmClasses) {
            try {
                Class<?> serviceClass = Class.forName(fcmClass);
                Object serviceObject = serviceClass.newInstance();
                injectContext(serviceClass, serviceObject);

                Method sendTokenRefresh = serviceClass.getMethod("onMessageReceived", RemoteMessage.class);
                sendTokenRefresh.invoke(serviceObject, remoteMessage);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | InstantiationException e) {
                Log.w(TAG, "Can't inject token due to error ", e);
            }
        }
    }

    private void injectContext(Class<?> serviceClass, Object serviceObject) {
        Log.d(TAG, "injectContext()");
        if (serviceClass != null) {
            if (setField(serviceObject, "mBase", this)) {
                Log.d(TAG, "context is set to " + serviceClass.getName());
            }
        }
    }

    private boolean setField(Object targetObject, String fieldName, Object fieldValue) {
        Log.d(TAG, "setField()");

        Field field;
        try {
            field = targetObject.getClass().getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            field = null;
        }
        Class<?> superClass = targetObject.getClass().getSuperclass();
        while (field == null && superClass != null) {
            try {
                field = superClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                superClass = superClass.getSuperclass();
            }
        }
        if (field == null) {
            return false;
        }
        field.setAccessible(true);
        try {
            field.set(targetObject, fieldValue);
            return true;
        } catch (IllegalAccessException e) {
            return false;
        }
    }
}
  1. Declare it in <project_root>/couriers/android/app/src/main/AndroidManifest.xml

Important: Set priority higher than 5 (one declared in HyperTrack SDK)

     <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter android:priority="100">
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

How does it work?

We have created MyFirebaseMessagingService explicitly and extended from FirebaseMessagingService. We also have set a priority as high as 100 to make sure we are receiving incoming messages only here and not in any other firebase messaging service such as HyperTrack. Now we have the flexibility to forward this message wherever we need, using reflection.

Limitation:

None



来源:https://stackoverflow.com/questions/64804556/hypertrack-is-preventing-fcm-notification-in-flutter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!