The company has APP development needs. Broadly speaking, Android and IOS are also the front end, so the task naturally falls on me. Requirements are still relatively simple, nothing more than display, jump, form and other basic operations, can hold. But the product manager came to me with an AAR, a framework, several code examples and PDFS, and asked me to integrate the SDK of a camera into the project, which was needed for both Android and Apple. I couldn’t cope with it. But there is no way, hard scalp also want to.

Never contact mobile terminal native development, received demand at the end of March, read the relevant documents, and consult native big guy after some problems, began to try on April 1, Baidu, Google, consult big guy, SegmentFault, StackOverflow, see Github source code. Finally, on May 10th, all the functions of this plug-in were completed and applied to the project. Despite the rest time, it lasted almost 30 days.

The fastest way to learn is to have a good teacher, simple problems, their own thinking three days as a teacher.

For the master of native development, this is not difficult, but for the small white, or not easy. In this process, there is a lot of knowledge to master. Fortunately, there are many predecessors who have made a lot of technical summaries. The following is a list of the knowledge used:

Flutter article:

  1. Introduction to the Flutter development plugin
  2. The Flutter is an asynchronous operation

Android articles:

  1. Kotlin basic syntax used
  2. Flutter introduces the third party AAR practice
  3. API.flutter. Dev (Flutter API documentation)

IOS article:

  1. Comprehensive Swift syntax parsing
  2. The iOS Flutter plugin imports the third party Framework

As someone with little experience with Flutter, the easiest part of plug-in development was to write a basic plug-in demo using the most familiar DART language. There are three modes that the plugin uses to trigger a MethodChannel that returns data, an EventChannel that listens to the flow of status, and a BasicMessageChannel. Used to pass strings and semi-structured messages, and we mainly use the first two.

1, MethodChannel

The Native side and the Flutter are called to each other, and the result can be returned after the call. The Native side can actively call or the Flutter can actively call, which belongs to two-way communication. This is the most common method, and Native side calls need to be executed in the main thread.

The flutter end

In the demo/lib/demo. Dart:

import 'dart:async';
import 'package:flutter/services.dart';

class Demo {
  // Declare the channel name
  static const MethodChannel _channel = const MethodChannel('demo');
  // invokeMethod can communicate with native
  static Future<bool> get setup async {
    return await _channel.invokeMethod('setup'); }}Copy the code

It’s that simple on the Flutter side. This is just the native side of the flutter triggering. We won’t discuss active triggering on the Navive side.

The Android end

The android/SRC/main/kotlin/com/example/demo/DemoPlugin kt

import android.Manifest
import android.app.Activity
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.Result
import io.flutter.plugin.common.MethodChannel.MethodCallHandler

class DemoPlugin: FlutterPlugin.MethodCallHandler{
    private lateinit var channel : MethodChannel
    
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // Same name as the flutter end
        channel = MethodChannel(binding.binaryMessenger, "demo")
        channel.setMethodCallHandler(this)}override fun onMethodCall(call: MethodCall, result: Result) {
        when (call.method) {
          "setup" -> {
            println(call.arguments)
            result.success("setup")}else -> {
            result.notImplemented()
          }
        }
    }
}
Copy the code

When communicating with Android, register the MethodChannel in the main class, primarily because the channel name should be the same as the One on the Flutter side.

The iOS side

In the ios/Classes/SwiftDemoPlugin. Swift

import Flutter
import UIKit

public class SwiftHzCameraPlugin: NSObject.FlutterPlugin{
  public static let instance: SwiftDemoPlugin = SwiftDemoPlugin(a)public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "demo", binaryMessenger: registrar.messenger())
    registrar.addMethodCallDelegate(instance, channel: channel)
  }
  
  public func handle(_ call: FlutterMethodCall.result: @escaping FlutterResult) {
    switch (call.method) {
      case "setup":
        print(call.arguments)
        result("setup")
      default:
        result("nothing")}}}Copy the code

It is basically the same as android, but the syntax is different.

Here we basically complete the most basic function of a plug-in, the Flutter actively communicates with the native side and gets the return information. But there’s only one trigger, one callback, so it’s not very flexible. Sometimes we need to listen for some state on the native side, and EventChannel is essential.

2, the EventChannel

EventChannel is mainly used for the communication of event streams. The Native side actively sends data to the Flutter, which is usually used for state monitoring, such as network changes and sensor data.

The flutter end

In the demo/lib/demo. Dart:

import 'dart:async';
import 'package:flutter/services.dart';

class Demo {
  // Still declare the channel name, but in this case it's EventChannel
  static const EventChannel _eventChannel = const EventChannel('Demo_event');
  // Status listening
  static StreamController<String> statusController = StreamController.broadcast();
  
  initEvent() {
    _eventChannel
      .receiveBroadcastStream()
      .listen((event) async {
          statusController.add(event);
      }
    }, onError: (dynamic error) {
        print(error);
    },cancelOnError: true); } Demo() { initEvent(); }}Copy the code

When chanel gets the data, it adds the data to the StreamController, making it easy to call the StreamController from the normal page.

The Android end

Or in the android/SRC/main/kotlin/com/example/demo/DemoPlugin kt

import android.Manifest
import android.app.Activity
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel

class DemoPlugin: FlutterPlugin.MethodCallHandler.ActivityAware{
    private lateinit var activity: Activity
    private var eventSink: EventChannel.EventSink? = null
    
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
      // Same name as the flutter end
      val eventChannel = EventChannel(binding.binaryMessenger, "Demo_event")
      // Initialize StreamHandler
      eventChannel.setStreamHandler(
        object : EventChannel.StreamHandler {
          override fun onListen(arguments: Any? , events:EventChannel.EventSink?). {
            eventSink = events
          }
          override fun onCancel(arguments: Any?). {
            eventSink = null}}}Copy the code

When kotlin needs to actively send a message to the Flutter end, use the following method to communicate directly

eventSink? .success("Communication successful")
Copy the code

The iOS side

In the ios/Classes/SwiftDemoPlugin. Swift

import Flutter
import UIKit

public class SwiftHzCameraPlugin: NSObject.FlutterPlugin.HZCameraSocketDelegate{
  public static let instance: SwiftDemoPlugin = SwiftDemoPlugin(a)var eventSink:FlutterEventSink?
  
  // Data stream listener
  class SwiftStreamHandler: NSObject.FlutterStreamHandler {
    func onListen(withArguments arguments: Any?.eventSink events: @escaping FlutterEventSink) -> FlutterError? {
      instance.eventSink = events
      return nil
    }

    func onCancel(withArguments arguments: Any?) -> FlutterError? {
       instance.eventSink = nil
       return nil}}public static func register(with registrar: FlutterPluginRegistrar) {
    let eventChannel = FlutterEventChannel(name: "Demo_event", binaryMessenger: registrar.messenger())
    eventChannel.setStreamHandler(SwiftStreamHandler()}}Copy the code

The following methods are also used to communicate with the android terminal

self.eventSink!("Communication successful")
Copy the code

conclusion

The few lines of code that flutter communicates with the native can be understood abstractly as registration-triggering. The first step is to register with the environment and then trigger with the corresponding event. When we need native data, we call the native event and get the required data through the invokeMethod() corresponding method, and then use result() to return the data. When the native state needs to be monitored, such as wifi state, power, etc., onListen is used to register eventSink into the environment. When the state changes, the event can be triggered actively.

Of course, after understanding these features, plug-in development is just getting started, and learning how to operate on the native side is the most important.

← To Be Continued…