preface

As the current general industry cross-platform solution, Flutter opens up a new set of design concepts. Through self-developed UI framework, Flutter supports the efficient construction of multi-platform applications, while maintaining the same high performance as native applications. In the development process of Flutter project, the development and reuse of plug-ins can improve the development efficiency and reduce the coupling degree of the project. Flutter developers can introduce plug-ins to quickly integrate capabilities into their projects and focus on the implementation of specific business functions. In the Flutter project, developers needed to develop new components when faced with scenarios such as splitting common business logic, or encapsulating native capabilities.

In order to reduce the cost for developers to develop Android and iOS applications at the same time, improve development efficiency and lower the threshold of integrated map SDK, Tencent’s location service team also plans to package a set of map Flutter plug-in based on the native map SDK capability in business practice, enabling Flutter developers to call map SDK interface across platforms. During my internship in 2019, based on the latest version 4.2.4 Android map SDK at that time, the author encapsulated some basic map operation functions commonly used in the map SDK and built a set of Android map SDK Flutter plug-in.

Now that the Map SDK has been iterated to version 4.4.0, I have also updated the Flutter plugin for a related version. This article covers building the Map Flutter plug-in project, loading the map instance, and rendering the demo sample. The functional encapsulation details of the basic operation of the map will be explained in detail in the following articles.

Build the Map Flutter plug-in project

Map Flutter plugin project structure

The overall structure of the Map Flutter plug-in project architecture is shown below:

Android/iOS directory: native code. Corresponding to the Android/iOS Flutter plugin directory. Lib directory: DART code. Flutter developers will use the interface implemented here by the Flutter plug-in. Example directory: Map SDK demo program. A use example to verify the usability of the Flutter plug-in.

The map Flutter plug-in relies on configuration items

The Flutter plugin configuration on Android is similar to the configuration instructions for the Android Maps SDK on the official website. You need to configure two files in the Android directory: build.gradle and AndroidManifest.xml. The Flutter plugin on Android has the package name com.t09.tencentMap, and the AndroidManifest. XML file is configured 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" / > <! (permission android:name="android.permission. Write_external_storage "/> <!) (b) <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <! (> <uses-permission android:name="android.permission.READ_LOGS" /> <!) -- Tencent map SDK required permissions (end) --> <! -- Tencent location SDK required permissions (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" / > <! -- Modify WiFi state. A WiFi scanning, need WiFi information for network positioning - > < USES - permission android: name = "android. Permission. CHANGE_WIFI_STATE" / > <! -- Access the network state and test the availability of the network. 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" / > <! (3) -- To access the current state of the phone, the device ID is required for network positioning --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <! > <application> <! -- If your key is confirmed correct, but the authorization is still not passed, Android :value="Your key"/> </application> </manifest>

The Android version of the map SDK used in this paper is 4.4.0. Meanwhile, the implementation language of the Flutter plug-in in this article is based on the Kotlin implementation. The dependencies of build.gradle are as follows:

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"}

The Map Flutter plugin loads the map instance

The Flutter plug-in acts as a bridge between the upper UI DART end and the lower Native SDK end. The communication process between the Flutter end and the Native end is shown in the figure below:

Flutter can communicate with Native code through MethodChannel. The client uses MethodChannel to pass method calls and arguments to the server, and the server receives the associated data through MethodChannel. Therefore, MethodChannel and EventChannel are two classes that can’t be avoided in Flutter plug-in development. Explain the functions of these two classes in plain English:

The purpose of MethodChannel is to pass method calls, such as calling methods on the Flutter side on the Native side or calling methods on the Flutter side on the Native side. MethodChannel is primarily used for method calls.

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

Future articles will cover the use of MethodChannel and EventChannel in the Maps SDK plug-in in more detail. Without further ado, this article focuses on the flow of using PlatformView to load a map instance. The use of PlatformView is similar to the use of MethodChannel, and the process of loading a map instance is as follows:

(1) Create TencentMapView at the Native end

TencentMapView inherits from PlatformView. PlatformView is a generic component in Flutter 1.0, and is divided between Android and iOS. On the Android platform, it’s called the AndroidView component, and on the iOS platform, it’s called the UikitView component. Therefore, we use PlatformView to build the life cycle of loading the map instance in the Native SDK and maintaining the map instance in PlatformView. TencentMapView also adds MethodChannel and EventChannel registration logic, which is mainly used for two-way interaction of map interface. The explanation of these two parts will be described in detail in the following articles. The implementation of TencentMapView on Android 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()}} // Flutter calls native SDK 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 informs the flutter layer of the associated message EventChannel val mapEventChannel = EventChannel(registrar.messenger(), "$mapChannelName$id") } }

(2) Register the newly written TencentMapView instance TencentMapView in the plug-in Native layer’s entry file TencentMapplugin.kt:

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

(3) Use AndroidView in the DART code of the Flutter end and embed AndroidView into TencentMapView:

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(), ); }}}

One thing to note here is that the string values in the viewType registered on the Android side and the Flutter side must be consistent for unique identification. The identification string in this paper is ‘com.tencentmap/map’, which establishes the association between the AndroidView of the Flutter end and the TencentMapView of the Native end.

The Flutter plug-in is rendered with the Demo example

The Demo sample

The demo UI uses a set of UI components in Flutter’s self-supported Material Design style. Flutter Demo calls the map SDK to show the interface of the map instance as shown in the figure below:

Related functional interfaces for basic map operations are also implemented in Demo, such as the drawing of related coverings, etc., as shown in the following figure:

Pits encountered during the version upgrade process

During the actual version upgrade process, the demo of the original project runs on a white screen, and the console prints out the following message:

[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 <... >

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: https://groups.google.com/g/f… In the main method of the main.dart file, we need to explicitly call the following code before runApp() :

WidgetsFlutterBinding.ensureInitialized();

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 the basic functional interface of the map, which will be explained continuously in the following articles.