Writing UI during Flutter development is really comfortable, especially with its HotReload. But App interface is obviously not enough, you can feel the ability of these Appp push after using some domestic apps. However, because of Flutter’s cross-platform nature, platform-specific code has to pass through a channel to be invoked. Fortunately, some manufacturers integrate push channels for all platforms. In this case, they only need to integrate their SDK to fulfill the push requirements of Flutter.

In the beginning, I used Aurora Push. Although the integration was quick and easy, the free version did not support vendor channel, and after integration, I found that the App needed to apply for a lot of permissions, which were completely unnecessary for a simple tool App. To be honest, I don’t see how these permissions have anything to do with pushing. The only understanding is to facilitate the aurora to collect personal information. And the aurora is collecting information that you can’t see yet, so you have to be open to what you understand. So I decided to change to Mob push.

I have to make a joke that the documentation on Mob’s official website is a bit messy. There is very little information about Flutter, so you need to refer to the Android configuration documentation to figure it out. The process here is nothing more than

  • Configure Android dependencies and introduce MobSDK
  • AppKey and AppSecret are configured
  • Configuring Vendor Push
  • Introduce Flutter plugin
  • Complete the push code

After the simple steps above, Mobpush access is complete. I have to say it looks pretty simple. Send a push message in the background of MobPush and it will be received on the App. If App is online, go through MOB’s TCP channel; if App is offline, go through the manufacturer channel. Is it comfortable?

The custom Action

If you look carefully, you’ll see that Mobpush provides two kinds of messages. One is push notification and the other is custom notification. The first is the common push, which appears in drop-down notifications; The other is App custom push, such as Zhihu’s in-app push, which only appears in the App.

To do this, you need to create your own constraint fields and place the data in extrasMap.

Click push to launch the App

A common scenario is that when you click on an App’s push notification, such as Zhihu, you can see relevant answers instead of going to the home page.

This is something that MobPush’s Flutter plugin cannot do, that is, it cannot receive data pushed by offline vendors inside a Flutter. To that end, I asked Mob’s customer service and was told that you need to handle the data passed through the vendor channel yourself.

Well, Flutter is really hard to build on its own. You have to write android native code yourself. So, I took a look at MobPush’s Android sample code.

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		// The setIntent method needs to be called, otherwise all the data obtained from getIntent will be tried last time
		setIntent(intent);
		dealPushResponse(intent);
	}

	private void dealPushResponse(Intent intent) {
		if(intent ! =null) {
			// Get the data from the manufacturer's home page click
			JSONArray jsonArray = MobPushUtils.parseMainPluginPushIntent(intent);
			System.out.println("parseMainPluginPushIntent:" + jsonArray);

			new PlayloadDelegate().playload(this, intent.getExtras()); }}Copy the code

As you can see, the click data is retrieved from the Extras carried with the Intent. Take a closer look at the Key comment in PayloadDelegate

/** * A push message carries additional data handling classes * >. Additional data can be accessed via the Intent's getExtras in onCreate() or onNewIntent() on the notification's landing page. The additional data of the landing page can be roughly divided into two situations: 1>MobPush's own channel and the channels of xiaomi, Meizu and Huawei: * 1> default landing page, for MobPush channel and xiaomi, Meizu and Huawei, fixed key is needed to obtain data * 2> Configuration scheme specified page, for MobPush channel and Xiaomi, Meizu and Huawei, * 

* 2> The Intent getExtras is used to retrieve additional data from the specified page. Both the default landing page and the configuration scheme specified page open the default startup page (OPPO system control open the default startup page). In the default startup page * additional data are obtained in the fixed key of [pluginExtra]. If the scheme specified page is configured, it does not take effect for the OPPO channel. [mobPUSH_link_k] = [mobPUSH_link_v] = [mobPUSH_link_v] = [mobPUSH_link_v] Both the default landing page and the configuration scheme specified page open the default launch page (Google service controls open the default launch page), and the additional field is completely exposed in the Bundle from the Intent's getExtras. You need to use [mobPUSH_link_k] in the code to get the scheme processing jump. If you also carry scheme additional data, you need to use [mobPUSH_link_v] to get the data */

public void playload(Context context, Bundle bundle) { if (bundle == null) { return; } try { Set<String> keySet = bundle.keySet(); if (keySet == null || keySet.isEmpty()) { return; } HashMap<String, Object> map = new HashMap<String, Object>(); for (String key : keySet) { System.out.println("MobPush playload bundle------------->" + key); System.out.println("MobPush playload bundle------------->" + bundle.get(key)); if (key.equals(MOB_PUSH_OPPO_EXTRA_DATA)) { map = parseOPPOPlayload(bundle); } else if (key.equals(MOB_PUSH_NORMAL_PLAYLOAD_KEY)) { map = parseNormalPlayload(bundle); } else { Object object = bundle.get(key); System.out.println(">>>>>>key: " + key + ", object: "+ object); map.put(key, String.valueOf(object)); }}if(map ! =null&&! map.isEmpty()) { realPerform(context, map); }}catch (Throwable throwable) { Log.e("MobPush", throwable.getMessage()); }}Copy the code

It can be seen that the payload processes data from different vendors. The payload determines whether there are fields of key vendors and processes them separately. Save the data in the map for action processing.

Now that you know how to get the vendor data, the next problem is how to pass the data to flutter. It’s easy to imagine that you could write your own channel to pass data. However, this cost was too high, so my solution was to modify the mobpush_plugin plugin to pass vendor data.

To do this, take a look at how mobpush_plugin passes push data.

    /** * Plugin registration. */
    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "mob.com/mobpush_plugin");
        channel.setMethodCallHandler(new MobpushPlugin());

        MobpushReceiverPlugin.registerWith(registrar);

        createMobPushReceiver();
        MobPush.addPushReceiver(mobPushReceiver);
    }
Copy the code

When registering mobPUSH_plugin, it registers an additional MobpushReceiverPlugin class.

    /** * Plugin registration. */
    public static void registerWith(Registrar registrar) {
        final EventChannel channel = new EventChannel(registrar.messenger(), "mobpush_receiver");
        channel.setStreamHandler(new MobpushReceiverPlugin());
    }
Copy the code

In this class, messages from MobSDK are received and passed to Flutter

    private MobPushReceiver createMobPushReceiver(final EventChannel.EventSink event) {
        mobPushReceiver = new MobPushReceiver() {
            @Override
            public void onCustomMessageReceive(Context context, MobPushCustomMessage mobPushCustomMessage) {
                HashMap<String, Object> map = new HashMap<String, Object>();
                map.put("action".0);
                map.put("result", hashon.fromJson(hashon.fromObject(mobPushCustomMessage)));
                event.success(hashon.fromHashMap(map));
            }

            @Override
            public void onNotifyMessageReceive(Context context, MobPushNotifyMessage mobPushNotifyMessage) {
                HashMap<String, Object> map = new HashMap<String, Object>();
                map.put("action".1);
                map.put("result", hashon.fromJson(hashon.fromObject(mobPushNotifyMessage)));
                event.success(hashon.fromHashMap(map));
            }

            @Override
            public void onNotifyMessageOpenedReceive(Context context, MobPushNotifyMessage mobPushNotifyMessage) {
                HashMap<String, Object> map = new HashMap<String, Object>();
                map.put("action".2);
                map.put("result", hashon.fromJson(hashon.fromObject(mobPushNotifyMessage)));
                event.success(hashon.fromHashMap(map));
            }

            @Override
            public void onTagsCallback(Context context, String[] tags, int operation, int errorCode) {}@Override
            public void onAliasCallback(Context context, String alias, int operation, int errorCode) {}};return mobPushReceiver;
    }

    @Override
    public void onListen(Object o, EventChannel.EventSink eventSink) {
        mobPushReceiver = createMobPushReceiver(eventSink);
        MobPush.addPushReceiver(mobPushReceiver);
    }
Copy the code

This onListen is called when the Flutter is listening. This allows the push data to be transmitted to Flutter, and the plugin nicely distinguishes between different push types. Here we need to focus on is onNotifyMessageOpenedReceive this type.

To enable Flutter to receive startup messages, the onListen method needs to be modified. Start by adding an initial push message to this class.

private static MobPushNotifyMessage initNotifyMessage;  // Initial push message
private static long defaultDelaySpan;					// First send wait interval

 public static void setInitNotifyMessage(MobPushNotifyMessage msg) {
     setInitNotifyMessage(msg, 1500);
 }

 public static void setInitNotifyMessage(MobPushNotifyMessage msg, long delaySpan) {
      initNotifyMessage = msg;
      defaultDelaySpan = delaySpan;
 }
Copy the code

This requires Android to call setInitNotifyMessage at startup to initialize the first push notification.


class MainActivity : FlutterActivity(a){
    private val tag = "MainActivity"

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

        Log.d(tag, "onCreate") val pushExtra = intent.extras ? :return
        handleNotifyOpen(pushExtra)
    }

    private fun handleNotifyOpen(data: Bundle) {
        Log.d(tag, "NotifyOpen: ${data.toString()}")
        val message = MobPushNotifyMessage()
        val pushData = data.getString("pushData") ?: return
        val pushJson = JSONObject(pushData)
        val extraMap = HashMap<String, String>()
        for (key in pushJson.keys()) {
            extraMap[key] = pushJson.getString(key)
        }
        message.extrasMap = extraMap
        MobpushReceiverPlugin.setInitNotifyMessage(message)
    }

    override fun onNewIntent(nIntent: Intent) {
        super.onNewIntent(nIntent)
        intent = nIntent
    }

    override fun onDestroy(a) {
        super.onDestroy()
        MobpushReceiverPlugin.setInitNotifyMessage(null) intent.extras? .clear() } }Copy the code

OnCreate determines whether the intent carries push data, and if so, converts it to MobPushNotifyMessage and passes it to mobPUSH_plugin. This allows Flutter to receive a push notification to start.

One thing to note here is the onNewIntent method. Without this method, the intent may carry data from the previous intent, causing the push to repeat/fail.

  • Modified plugins github.com/dreamer2q/M…

Sharesdk conflicts with mobpush_plugin

  • Github.com/MobClub/Sha…

The integration of push and mobshare third-party authentication will result in appKey is illegal. The solution is to write AppKey and AppSecret to androidmanifest.xml.

 				<meta-data
                android:name="Mob-AppKey"
                android:value="appkey" />

				<meta-data
                android:name="Mob-AppSecret"
                android:value="appsecret" />
Copy the code

summary

It is not enough to just be able to Flutter. The platform-specific code needs to be well understood. Otherwise there’s no telling what’s going on.