preface

Flutter, the current industry-wide cross-platform solution, opens up a whole new set of design concepts. With its own UI framework, Flutter enables efficient construction of applications on multiple platforms while maintaining the same high performance as native applications.

In the development of Flutter project, the development and reuse of plug-ins can improve development efficiency and reduce the coupling degree of the project. Flutter developers can introduce plug-ins to quickly integrate capabilities for their projects, allowing them to focus on implementing specific business functions. While the Flutter project was being developed, new components needed to be developed when common business logic was split or native capabilities needed to be encapsulated.

In order to reduce the cost of developers developing Android and iOS apps at the same time, improve development efficiency, and lower the threshold of integrating map SDK, Tencent Location Service team also plans to encapsulate a set of map Flutter plug-ins based on the native map SDK capabilities in business practice, enabling Flutter developers to call map SDK interface across platforms. During my internship in 2019, based on the latest Android map SDK 4.2.4 at that time, the author built a set of Android map SDK Flutter plug-in by encapsulating some commonly used basic map operation functions in the map SDK.

Now, the Map SDK has been iterated to version 4.4.0, and I have also updated the map Flutter plugin. This article introduces the construction of the Map Flutter plugin project, the loading of a map instance, and the presentation of a demo. The details of functional encapsulation for basic map operations will be explained in a subsequent article.

Build the Map Flutter plugin project

Map the Flutter plugin project structure

Android /ios directory: native code. This corresponds to the Android/iOS Flutter plugin directory. Lib directory: Dart code. Flutter developers will use the interface implemented by the Flutter plugin here. Example directory: map SDK demo program. Examples of use to verify the usability of the Flutter plugin.

Map the Flutter plugin depends on configuration items

The configuration of the Flutter plugin on Android is similar to the configuration description of the Android map SDK on the official website. You need to configure two files in the Android directory: build.gradle and Androidmanifest.xml. The package name of the Flutter plug-in on Android is com.tencent. Tencentmap, and the configuration of the Androidmanifest.xml file is as follows:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.tencent.tencentmap"> <! -- Tencent Map SDK required permissions (start) --> <! - access to the network to obtain map services - > < USES - permission android: name = "android. Permission. INTERNET" / > <! - check the network availability - > < USES - permission android: name = ". Android. Permission ACCESS_NETWORK_STATE "/ > <! - access to WiFi state - > < USES - permission android: name = "android. Permission. ACCESS_WIFI_STATE" / > <! <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <! --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <! <uses-permission android:name="android.permission.READ_LOGS" /> <! -- Tencent Map SDK required permissions (end) --> <! -- Tencent locates the permissions required by SDK (start) --> <! - is obtained by GPS precise location - > < USES - permission android: name = "android. Permission. ACCESS_FINE_LOCATION" / > <! - through the network to get a rough location - > < USES - permission android: name = "android. Permission. ACCESS_COARSE_LOCATION" / > <! -- Access the network. Need some location information from a web server - - > < USES - permission android: name = "android. Permission. INTERNET" / > <! -- Access WiFi status. Need WiFi positioning information for network - > < USES - permission android: name = "android. Permission. ACCESS_WIFI_STATE" / > <! -- Change the WiFi status. A WiFi scanning, need WiFi information for network positioning - > < USES - permission android: name = "android. Permission. CHANGE_WIFI_STATE" / > <! Access network status and check network availability. Need information on network operators for network positioning - > < USES - permission android: name = ". Android. Permission ACCESS_NETWORK_STATE "/ > <! - the change of the access network, need some information for network positioning - > < USES - permission android: name = "android. Permission. CHANGE_NETWORK_STATE" / > <! --> <uses-permission Android :name="android.permission.READ_PHONE_STATE" /> <! -- Tencent locates the permissions required by SDK (end) --> <application> <! -- If your key check is correct, but the authorization is still not approved, <meta-data android:name="TencentMapSDK" Android :value="Your key"/> </application> </manifest>Copy the code

The Android map SDK version used in this article is 4.4.0. Meanwhile, the implementation language of the Flutter plug-in in this paper is based on Kotlin implementation. Build. Gradle has the following dependencies:

**

Dependencies {implementation 'com. Android. Support: appcompat - v7:27.1.1' implementation 'com. Tencent. The map: tencent - map - vector - SDK: 4.4.0' implementation "org. Jetbrains. Kotlin: kotlin stdlib - jdk7: $kotlin_version" The compile "org. Jetbrains. Kotlin: kotlin - script - the runtime: 1.2.71"}Copy the code

Map Flutter plugin loads map instances

The Flutter plugin acts as a layer of bridge between the upper UI Dart end and the lower Native SDK end. The communication flow between the Flutter end and Native end is shown as follows:

structure1.png

The Flutter can communicate with Native code via the MethodChannel. The client uses MethodChannel to send method calls and parameters to the server, which also receives the relevant data through MethodChannel. Therefore, MethodChannel and EventChannel are two inevitable classes in Flutter plugin development. To explain the functionality of these two classes in more general terms:

MethodChannel is used to pass method calls, such as native method calls on the flutter side or native method calls on the flutter side. MethodChannel is primarily used for method calls.

The purpose of EventChannel is to send messages. When the Native layer wants to notify the flutter layer of some message, the Native layer sends the message, and the flutter receives the message. EventChannel is typically used for data flow messages.

Subsequent articles will cover the use of MethodChannel and EventChannel in the map SDK plug-in in more detail. Without further discussion, this article focuses on the process of loading a map instance using PlatformView. PlatformView is used in a similar way to MethodChannel. The process for loading a map instance is as follows:

(1) Create TencentMapView at Native end

TencentMapView inherits from PlatformView. PlatformView is a common component in Flutter 1.0, which is divided into Android and iOS. On Android it’s called the AndroidView component, on iOS it’s called the UIKitView component. Therefore, use PlatformView to build and load the map instance in Native SDK and maintain the life cycle of the map instance in PlatformView. The TencentMapView also adds registration logic for MethodChannel and EventChannel, which are used for the interface of the map to interact with each other. These two parts will be explained in more detail in future articles. Android TencentMapView implementation is as follows:

**

class TencentMapView(context: Context, private val id: Int, private val activityState: AtomicInteger, tencentMapOptions: TencentMapOptions) : PlatformView, load Application. ActivityLifecycleCallbacks {/ / build map instance private val mapView = mapView (context, tencentMapOptions) private val registrarActivityHashCode: Int = TencentmapPlugin. The registrar. The activity (). The hashCode () / / maintenance fun map instance lifecycle setup () {the when (activityState. The get ()) {STOPPED - > { mapView.onStop() } RESUMED -> { mapView.onResume() } CREATED -> { mapView.onStart() } DESTROYED -> { Mapview.ondestroy ()}} // The flutter end calls the map native SDK's MethodChannel val mapChannel = MethodChannel(registrar.messenger(), "$mapChannelName$id") mapChannel.setMethodCallHandler { methodCall, result -> MAP_METHOD_HANDLER[methodCall.method] ? .with(mapView.map) ? .onMethodCall(methodCall, result) ? : Result.notimplemented ()} // The native SDK notifies the flutter layer of EventChannel val mapEventChannel = EventChannel(registrar.messenger(), "$mapChannelName$id") } }Copy the code

(2) Register the newly written TencentMapView instance TencentMapView in tencentmapplugin. kt:

**

@JvmStatic fun registerWith(registrar: Pluginregistry.registrar){// Register the TencentMapView instance with the plug-in registrar.platformViewRegistry().registerViewFactory("com.tencentmap/map", tencentMapView) }Copy the code

AndroidView is embedded into the TencentMapView with the Dart code on the Flutter side:

**

class TencentMapView extends StatelessWidget{ const TencentMapView({ this.onTencentMapViewCreated, }); final MapCreatedCallback onTencentMapViewCreated; @override Widget build(BuildContext context) { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'com.tencentmap/map', onPlatformViewCreated: _onViewCreated, creationParams: { }, creationParamsCodec: const StandardMessageCodec(), ); }}}Copy the code

The important thing to note here is that the string values in the viewTypes registered on both the Android and Flutter sides must be consistent for unique identification. In this article, the identity string is ‘com.tencentmap/map’, which establishes the association between the AndroidView on the Flutter end and the TencentMapView on the Native end.

Demo examples of the Flutter plug-in are displayed

The Demo sample

The Demo UI uses a set of UI components that Flutter supports in the Material Design style. The interface of the map instance displayed by the Map SDK called Flutter Demo is as follows:

Screenshot_20210324_164152_com.tencent.tencentmap_example.jpg

Demo also realizes relevant functional interfaces for basic map operations, such as the drawing of related coverings, as shown in the figure below:

Screenshot_20210324_164210_com.tencent.tencentmap_example.jpg

Screenshot_20210324_164237_com.tencent.tencentmap_example.jpg

Potholes encountered during version upgrade

During the actual version upgrade, the demo of the original project runs on a blank screen, and the console displays the following information:

**

[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized. If you're running an application  and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first. If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding. #0 defaultBinaryMessenger.<anonymous closure> (package:flutter/src/services/binary_messenger.dart:76:7) #1 defaultBinaryMessenger (package:flutter/src/services/binary_messenger.dart:89:4) #2 MethodChannel.binaryMessenger (package:flutter/src/services/platform_channel.dart:140:62) #3 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:314:35) #4 MethodChannel.invokeMapMethod Package: flutter/SRC/services/platfo <... >Copy the code

According to the console output information, after consult relevant information to find the reason: the problem caused by Flutter version upgrade lead to significant changes: groups.google.com/g/flutter-a… Dart file, call the following code explicitly before runApp() :

**

WidgetsFlutterBinding.ensureInitialized();
Copy the code

conclusion

This paper mainly introduces the construction of Tencent map SDK Flutter plug-in project, map instance loading, demo presentation, and the packaging details of basic functional interface of map, which will be explained continuously in subsequent articles.