background

Flutter relies on Skia’s deep customization and gives us a lot of support for rendering. Can achieve cross-platform application layer rendering consistency. However, in the actual application development, in addition to the basic UI display, there are more functional logic needs to use the underlying capabilities of the system, such as: push, take photos and other functions. Because flutter only takes over the application rendering layer, the underlying capabilities of these systems cannot be supported within the FLUTTER framework. On the other hand, flutter has been around for a relatively short time, and some of the more mature solutions have not yet been implemented in Flutter, so the ability to interact with native flutter is crucial.

Application development

Based on the reflection of the background part, we have determined that interaction with native flutter can solve the requirement scenarios for invoking native capabilities. Can the underlying capabilities + application layer rendering solve all scenarios of an App? Before coming to the conclusion, we first decompose capability and rendering into four dimensions according to the four-quadrant analysis method, and analyze what is needed to build a relatively complete App

Flutter and native interaction can only handle application layer rendering, application layer capabilities and low-level capabilities. For scenes that involve low-level rendering, such as browsers, cameras, maps, and native custom views, flutter can only handle application layer rendering, application layer capabilities, and low-level capabilities. It’s not realistic for us to reinvent the Flutter. In this case, the need for a scenario with a mixed view is obvious. We can reserve a blank area in the Widget tree of flutter in advance and embed a native view matching the blank area in the flutter’s artboard (i.e. FlutterView and FlutterViewController). You can get the visuals you want. However, this approach is extremely inelegant because the embedded native view is not in the flutter rendering hierarchy and requires a lot of adaptation work on both the flutter side and the native side to achieve a normal user interaction experience. To address these issues, let’s take a look at what support Flutter offers developers.

Related support

  • Method Channel

Flutter provides a lightweight solution to the problem of invoking the underlying capabilities of native systems and reusing the associated code base. This is the Method Channel mechanism at the logical layer. Based on the method channel, we can expose the capabilities of native code to Dart in the form of interfaces that allow the Dart code to interact with native code as if it were calling a normal Dart API.

  • Platform View

Flutter provides the concept of a Platform View. It provides a way for developers to embed native systems (Android and iOS) views into Flutter and add them to the render tree of Flutter to achieve an interactive experience consistent with Flutter. This way, through the platform view, we can wrap a native control as a Flutter control and embed it in a Flutter page just like a normal Widget.

Let’s explore in detail the two SOLUTIONS to FLUTTER, how they are implemented, and whether the solutions described above can solve our actual development problems.

Method Channel Method Channel

Flutter is a cross-platform framework that provides a standardized solution that shields developers from operating system differences. However, Flutter is not an operating system, so it also requires direct access to the underlying native code of the system in certain scenarios (such as push, Bluetooth, or camera hardware calls). To this end, Flutter provides a flexible and lightweight mechanism to communicate between Dart and native code. This mechanism is the messaging mechanism for method calls, and the method channel is the channel for passing communication messages. A typical method call process is similar to a network call. The client of flutter sends a method call request to the native code host as a server through the method channel. After listening to the message of the method call, the native code host calls the platform-related API to process the request initiated by flutter. Finally, the processed result is sent back to flutter through the method channel. The call process is as follows:

As can be seen from the figure above, method invocation requests are processed and responded through FlutterView in Android and FlutterViewController in iOS. FlutterView and FlutterViewController provide a drawing board for Flutter application, enabling Flutter built on Skia to achieve the visual effect required by the whole application by drawing. Therefore, they are not only the container for a FLUTTER application, but also the entry point for a flutter application and the most appropriate place to register a method call request. Next, I’ll use an example to demonstrate how to use method channels to interact with native code.

Example method channel usage

In practical business, prompting users to jump to the App market (iOS App Store, Android App market) to score is a frequent requirement. Considering that flutter does not provide such an interface and the jump mode of Flutter is different on Android and iOS. So we need to implement this functionality on Android and iOS, respectively, and expose the Dart interface. Let’s first look at how Flutter, as a client, implements a method call request.

How does FLUTTER implement a method call request?

First, we need to determine a unique string identifier and a named channel; On this channel, Flutter initiates a method call request by specifying the method name “fcreDirect :// personal_friends_Takeover”. As you can see, this is exactly how we normally call a Dart object. Because the method invocation process is asynchronous, we need to use non-blocking (or register callbacks) to wait for the native code to respond. The following shows some of the code for flutter, a simple sharing function, and native interaction

/ / declare MethodChannel
const methodChannel = const MethodChannel('flutter_native');
_iOSPushToFriend() async {
   await methodChannel.invokeMethod('redirect://personal_friends_takeover');
   // It is important to note that the method call request may fail. In practice, we need to wrap the statement that initiates the method call with a try-catch.
}
Copy the code

On iOS, method calls are handled and responded to in the entry to the Flutter application, namely the FlutterViewController. We can implement this in the host App:

func flutterMethodChannel(viewController: FlutterViewController) {
    let channelName = "flutter_native"
    let methodChannel = FlutterMethodChannel.init(name: channelName, binaryMessenger: viewController.binaryMessenger)
        
    methodChannel.setMethodCallHandler {(call: FlutterMethodCall, result: @escaping FlutterResult) in
        if (call.method = = "redirect://personal_friends_takeover") {
            print("Interact")}else {
            print("nothing")}}}Copy the code

Note that when it comes to cross-system data interaction, Flutter uses StandardMessageCodec to normalize the data transfer behavior by serializing jSON-like binary information transferred in the channel. When we send or receive data, the data is automatically serialized and deserialized according to the preset rules of the respective systems. Common data type conversions between Android, iOS, and Dart are summarized in the following table to help you understand and remember them. Just keep in mind that basic types like NULL, booleans, integers, strings, arrays, and dictionaries can be intermixed between platforms according to platform-defined rules.

Platform View

If method channels address the reuse of native capabilities logic, platform views address the reuse of native views. Flutter provides a lightweight way to create native (Android and iOS) views that can be inserted into the Widget tree after some simple ENCAPSULATION of the Dart layer interface to mix native and Flutter views. A typical platform view usage process is similar to the method channel:

  • First, the client of Flutter initiates the creation request of the native view by passing a view identifier to the native view wrapper class of Flutter (UIKitView and AndroidView on iOS and Android platforms respectively).
  • The native code side then hands off the creation of the corresponding native view to the PlatformViewFactory.
  • Finally, the view identifier is associated with the platform view factory on the native code side, so that view creation requests initiated by flutter can directly find the corresponding view creation factory.

At this point, you can use the native view just as you would use a Widget. The whole process is shown in the figure below:

How does Flutter implement native view interface calls?

In the following code, we use the native Android view wrapper AndroidView and the iOS view wrapper UIkitView inside the SampleView and pass in a unique identifier to associate with the native view:

class SampleView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Use the Android platform AndroidView, pass in the unique identifier sampleView
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(viewType: 'sampleView');
    } else {
      // Use UIKitView for iOS and pass in the unique identifier sampleView
      return UiKitView(viewType: 'sampleView'); }}}Copy the code

As you can see, the platform view is simple to use on the Flutter side and is not significantly different from a normal Widget. Platform views solve the reuse problem of native rendering capabilities, enabling Flutter to assemble native views into a FLUTTER control through lightweight code encapsulation. Flutter provides two concepts: platform view factory and view identifier. Therefore, Dart layer view creation requests can directly find the corresponding view creation factory through the identifier, thus implementing the fusion reuse of native views and FLUTTER views. For the need to dynamically invoke the native view interface at runtime, we can register method channels in the wrapper class of the native view to achieve precise control over the presentation of the native view.

conclusion

Methods the channel

Method channels address the reuse of native capabilities at the logical layer, enabling FLUTTER to interact with native code through lightweight asynchronous method calls. A typical invocation process starts with a method call request from a flutter. The request arrives at the native code host via a method channel specified by a unique identifier. The native code host implements, responds to, and processes the invocation request by registering the corresponding method, and finally passes the execution result back to the flutter via a message channel.

Platform view

Because Flutter is completely different from native rendering, there is a significant performance overhead associated with converting different render data. Instantiating multiple native controls on an interface at the same time can have a significant impact on performance, so we should avoid using embedded platform views where flutter controls can also be used. In this way, on the one hand, a large amount of adaptive bridge code needs to be written on Android and iOS respectively, which violates the original intention of cross-platform technology and increases subsequent maintenance costs. On the other hand, except for maps, Webviews, cameras, and other special cases involving low-level scenarios, most UI effects that native code can achieve can be implemented with Flutter.