One, a brief introduction

For the construction of Flutter applications, the development language Dart, VIRTUAL machine and construction tools are different from those of our Native applications, and the platform virtual machine does not support them. Therefore, Flutter SDK is needed to support Flutter applications, just as Android SDK is needed to build Android applications. Download the Flutter SDK in one of two ways:

  1. Download the built ZIP package from the official website that contains the complete Flutter base Api, Dart VM, Dart SDK, and more
  2. Manually build, Clone the Flutter source and run itflutter --packages getOr other commands with detection types such asbuild,doctorThe Dart SDK and the Flutter engine artifacts are automatically built and downloaded

Depending on the local download of the Flutter SDK by each developer, this method does not guarantee the consistency and automatic management of the Flutter SDK versions. If the Flutter SDK versions are inconsistent during development, Dart layer Api compatibility or Flutter virtual machine inconsistencies occur because each version of a Flutter has a corresponding Flutter VIRTUAL machine, and the build product contains the corresponding build virtual machine. The construction of a Flutter project requires a directory of the standard Flutter engineering structures and depends on the local Flutter environment. Each corresponding Flutter project has a corresponding Flutter SDK path. In the Generated. Xcconfig file, this path is read when the Native project relies on the build of the Flutter project to obtain the engine, resources and build the Flutter project. When a flutter command is invoked to build the flutter project, the flutter SDK path where the current flutter command is located will be obtained, from which the engine, resources and build the flutter project will be obtained. Therefore, the build environment of the FLUTTER command must be consistent with the environment variables of the platform project of the FLUTTER project, and this environment variable must change dynamically with the flutter execution. There should also be a corresponding Build of Flutter for each release of the packaged Flutter project, either locally or on the packaged platform

We know that the engineering structure of Flutter application is different from that of Native application engineering. The main difference is that Native engineering is a subproject of Flutter engineering, and the outer layer is managed by Pub. In this way, the dependencies of the Flutter Plugin/Package code can be shared with multiple platforms. When the Native sub-project is packaged, only the engineering code and the platform code of the library that Pub depends on can be packaged. The Flutter project uses Flutter_tools to package Dart code in lib directories and libraries that Pub depends on. To return to the topic, due to the differences in engineering structures, if one of the functional modules of Flutter is developed based on the existing Native engineering, generally the hybrid development must ensure at least the following characteristics:

  1. No intrusion to Native engineering
  2. Zero coupling to Native engineering
  3. It does not affect the development process and packaging process of Native engineering
  4. Easy local debugging

Obviously, the plan to change the engineering structure can be directly ignored. The official government also provides a plan to rely on Flutter locally to the existing Native. However, if this plan relies on Flutter directly without changing optimization, the development of other students without Flutter environment will be directly affected and the development process will be affected. And the packaging platform does not support this dependent way of packaging

When we talk about the Flutter SDK, it is inevitable to change or customize the Flutter SDK directly due to the Bug of the Flutter SDK or the script code linked to the platform in the Flutter SDK. Although this method can solve the problem or customize the Flutter SDK, However, this approach is not recommended as it is not friendly to subsequent smooth upgrade of the Flutter SDK and incurs more maintenance costs later

Next, this paper mainly introduces how to solve and realize the above problems:

  1. Flutter SDK version consistency and automated management
  2. Without invading the Flutter SDK source code for BugFix or customization
  3. The Flutter hybrid develops a componentized architecture
  4. The Flutter hybrid development engineering architecture

Ii. Four engineering types of Flutter

Flutter engineering usually includes the following engineering types, which are briefly summarized as follows: The Application of the Flutter Module includes the Dart layer and the Native platform layer. The Application of the Flutter Module includes the Dart layer and the Native platform layer. The subengineering of the Native platform layer is the hidden engineering automatically generated by the Flutter. The Dart Package contains only the implementation of the Dart layer, often defining some common widgets

3, Flutter project Pub dependency management

The dependencies between Flutter projects are managed by Pub. The product of the dependencies is a direct source dependency, which is similar to Pod in IOS. Both dependencies can be interval qualified with library versions and Git remote dependencies. The dependencies are based on the syntax of YAML, a language specifically designed to write file configurations. Here is an example of remote dependencies via Git addresses:

dependencies:
  uuid:
    git:
      url: git://github.com/Daegalus/dart-uuid.git
      ref: master
Copy the code

After declaring dependencies, run the flutter Packages get name to pull the dependencies from remote or local sources and generate a pubspec.lock file. This file is very similar to IOS podfile. lock. The update will only happen when the flutter packages upgrade is executed. Also, the pubspec.lock file should be submitted to Git as a version management file, not gitignore

1. Pub relies on conflict handling

For Pub and Pod, this dependency management tool for conflict handling ability is much worse than Android Gradle dependency management, so when the same library version conflict, we can only handle manually, and with the expansion of the development scale, there will certainly be conflicts between the dependency transfer libraries

There are two main types of Pub dependency conflicts:

  1. The current version of the dependent library conflicts with the current Dart SDK environment version
  2. A library version inconsistency conflict occurred while passing dependencies

The first type of flutter packages get will report errors and indicate why the conflict occurred and what is the minimum required version as follows:

The current Dart SDK version is 2.1.0-dev.5.0.flutter-a2eb050044.

Because flutter_app depends on xml >=0.1.0 <3.0.1 which requires SDK version <2.0.0, version solving failed.                        
pub get failed (1)
Copy the code

This can be resolved directly by following the prompts to upgrade the version of the dependent library

The second one is A bit more complicated. If there are three libraries A, B and C, and both A and B depend on C library, if A version of A depends on C and B depend on C version is inconsistent, there will be A conflict. How to resolve this conflict? There are two ways

Mysql > alter database A; mysql > alter database B;

dependencies:
	A: any
	B: any
Copy the code

When the flutter packages get is passed, there will not be A conflict-free version error because Pub has automatically selected the version numbers of libraries A and B that make the C library version consistent. Open the pubspec.lock file in the corresponding directory to search for libraries A and B, and there will be corresponding conflict-free version numbers. Finally, replace the any version with the any version, and the version conflict is resolved

2. Solve the problem through version coverage

2. Pub depends on version coverage

In the Pub dependency management, since it supports passing dependencies, it also provides a version override method, which means to specify a version forcibly. This is similar to the Android Gradle Force. The same version override method can also be used to resolve conflicts, if you know a certain version will not conflict. Can be directly solved by version coverage:

Dependency_overrides: A: 2.0.0Copy the code

4. Flutter links to Native engineering principles

There is a way to rely on the existing Native project. Flutter local dependence, this method is too dependent on the local environment and invasion of Native engineering will affect other developers, and the packaging platform does not support this way of packaging, so it must be optimized and modified based on this method. I will talk about this later. First, the principle of local dependence at both ends of Native

1. Android

In Android, the local dependency mode is:

  1. insettings.gradleIn the injectioninclude_flutter.groovyThe script
  2. In the dependent modulebuild.gradleaddproject(':flutter')Rely on

For Android local dependencies, include_flutter. Groovy and flutter. Gradle scripts are responsible for local dependencies and product construction of Flutter

1. include_flutter.groovy

When injected into settings.gradle, the include_flutter. Groovy script is bound to the context in which gradle is currently executed and does three things:

  1. In the include FlutterModule.android/Flutterengineering
  2. In the include FlutterModule.flutter-pluginsContains the Android Module file in the Flutter project path
  3. Configured for all projectsbuild.gradleThe configuration execution phase depends on:flutterThe project, that is, it performs the configuration phase first

The flutter-plugins file is automatically generated according to the current dependencies. It contains the k-V relationship between the absolute path of the flutter subprojects that the current flutter engineering depends on (both direct and transfer dependencies). A subproject may be a Flutter Plugin or a Flutter Package. The following is an example of the contents of a. Flutter -plugins:

Url_launcher = / Users/Sunzxyong /. Pub - cache/hosted/pub. The flutter - IO. Cn/url_launcher 4.0.2 /Copy the code

2. flutter.gradle

This script, located in the Flutter SDK, looks very long and does three things:

  1. Select the Flutter engine that matches the corresponding architecture (Flutter. So)
  2. Parsing the.flutter-pluginsAdd the corresponding Android Module to the Native project dependencies.
  3. Hook mergeAssets/processResources Task, advance execution FlutterTask, callflutterCommand to compile the Dart layer codeflutter_assetsProduct, and copy it toassetsdirectory

With the above three steps, you can automatically build the code in the Flutter project and automatically copy the products into Native by running the build directly in the Native project

2. IOS

In IOS, the local dependency mode is as follows:

  1. Pass in the Podfileeval bindingCharacteristics of injectionpodhelper.rbScript that is executed during POD install/update
  2. In the IOS construction phaseBuild PhasesThat needs to be executed when the build is injected intoxcode_backend.shThe script

For IOS local dependencies, podhelper.rb and xcode_backend.sh are responsible for the Pod local dependencies and product construction of Flutter

1. podhelper.rb

Since the Podfile is written in Ruby, this script is also a Ruby script, which does three things when pod install/update occurs:

  1. Pod native dependencies on the Flutter engine and the Flutter plugin registry
  2. Pod native source code dependencies.flutter-pluginsThe file contains ios projects in the Flutter project path
  3. After pod Install completespost_installGet the current target project object and import itGenerated.xcconfigConfiguration, all of which are environment variable configurations, mainly for the construction phasexcode_backend.shPreparing for script execution

These things ensure that the Flutter engineering and the dependencies that transfer them into the Native engineering via POD. The next step is construction

2. xcode_backend.sh

This Shell script, which is located in the Flutter SDK, does two things:

  1. Build products (app. framework, Flutter_assets) by compiling the flutter command
  2. Copy the products (*. Framework, Flutter_assets) into the corresponding XCode build products directory as follows:$HOME/Library/Developer/Xcode/DerivedData/${AppName}

Frameworks are copied to ${BUILT_PRODUCTS_DIR}”/”${PRODUCT_NAME}”. App /Frameworks”

Copy flutter_assets to ${BUILT_PRODUCTS_DIR}”/”${PRODUCT_NAME}”.app”

${AppName}/Products/${AppName}.app

5. Flutter communicates with Native

There are three ways that Flutter can communicate with Native, which are briefly described here:

  1. MethodChannel: method invocation
  2. EventChannel: Event listener
  3. BasicMessageChannel: message passing

Flutter and Native communication are bidirectional channels that can call and message each other

The following is the main content of this paper. The above content is mainly to introduce the important content of Flutter engineering and prepare for the following lecture. Of course, there are also packaging mode, construction process, etc., which can be discussed in a separate article later

Vi. Consistency and automated management of Flutter versions

There is a problem that must be solved with the build consistency and automatic management of the Flutter SDK in team multiplayer development mode. This problem can be solved by looking at the version management mode of The Flutter SDK.

Gradle builds Gradle projects in gradle-wrapper.properties configuration file. Gradle builds Gradle projects in gradle-wrapper.properties If you do not have a local Gradle version of the current project at the time of the build, the required Gradle version is automatically downloaded and the build is executed in./gradlew wrapper mode, so that the local Gradle environment is isolated from the project environment. The corresponding project is always built with the same Gradle version

This wrapper approach to versioning allows for isolation from globally configured environments on each machine, and for maintaining the same build for the same project under multi-team collaboration

Therefore, we add three files to the root directory of each Flutter project using Gradle version management:

wrapper/flutter-wrapper.properties
flutterw
flutterw.bat
Copy the code

After the addition of the project structure, there are three more files, as follows:

The above flutter-wrapper.properties configuration file of the current project Flutter SDK is as follows:

distributionUrl=https://github.com/flutter/flutter flutterVersion = 1.0.0Copy the code

Of course, some additional configurations can be added if necessary. Currently, these two configurations are sufficient. The remote address and version number of Flutter are specified

While Flutterw is a Shell script, internal version management mainly does the following:

  1. Read the configured version number of the Flutter SDK to verify that the Flutter SDK version does not exist
  2. Update the Androidlocal.propertiesAnd in the IOSGenerated.xcconfigFile containing the Flutter SDK address
  3. Finally, the parameters passed from the command line are linked to Flutter in the Flutter SDK for execution

Then build the Flutter project with the flutterw command:

./flutterw build bundle
Copy the code

The local global configuration of the FLUTTER command is not needed to avoid the problem of the different versions of the flutter development students. In this way, students who are new to flutter development do not need to download the FLUTTER SDK manually, just execute any Flutterw command. For example,./ Flutterw –version can automatically trigger the download and installation of the corresponding Flutter SDK, implementing elegant automation management. This approach also provides the basis for the packaging platform to support the Flutter project

Vii. The hybrid development of Flutter component architecture

If Flutter is to be used to develop a module or function in our existing Native engineering, it must not change the structure of Native engineering or affect the existing development process. Then, how to carry out the mixed development? As for the four engineering models of Flutter, we can directly ignore the Flutter App, because this is a project to develop a new Flutter App. For the Flutter Module, To build a Flutter project, you must have a main.dart portal, which also happens to have a main portal in the Flutter Module

Therefore, component partitioning is carried out by using the Flutter Module as the aggregation entrance of all modules or functions implemented by Flutter, through which bidirectional association of the Flutter layer to the Native layer is carried out. Where is the Flutter development code written? Of course, we can write our Dart code directly into the Flutter Module. If we develop multiple modules and components later, we cannot write all our Dart code into the Flutter Module lib/. This is the simplest way to differentiate modules by creating subdirectories under the Lib/directory. However, there are some problems with this. All modules share a remote Git address. First, it is completely coupled in component development isolation, second, each module component does not have a separate version number or Tag, and the subsequent increase in module components brings more test regression costs

Componentized right way for a component with a separate remote Git address management, so that each component in the formal version has a version number and the Tag, and completely isolated on each component development, increasing follow-up component does not affect the other components, a component of new requirements and does not need to return to the other components, results in lower test cost

As mentioned above, the Flutter Plugin can have the corresponding Dart layer code and platform layer implementation. Therefore, it can be designed that a component corresponds to a Flutter Plugin, and a Flutter Plugin is a complete Flutter project with an independent Git address. Therefore, these components are in the business layer, which can be called business components. The communication and common services between these business components can be divided into another basic layer, which can be called basic components. All business components depend on the basic layer. The Flutter Module, as an aggregation layer, depends on all the Flutter components. The dependencies between these Flutter projects are managed through Pub dependencies

Therefore, the overall componentized architecture can be designed as follows:

Positioning of business components versus base components

For the basic components above, finer granularity can also be divided, but too much division is not recommended. For communication with Native platform layer, each business component corresponds to a Channel. Of course, finer granularity Channel can also be divided internally. This Channel is responsible for providing the Native layer services for the Flutter layer to consume. The Native layer should call the Api of the Flutter layer as little as possible, only when some value callbacks occur

Because the essence of Flutter is to run on both ends of development at once, if there are too many platform-dependent implementations, they will be violated and the UI will just write one. The implementation of the platform layer should also try to maintain one principle, namely:

Try to make the Native platform layer become the service layer, and make the Flutter layer become the consumer layer to invoke the services of the Native layer. That is, Dart calls the Native Api, so that the Flutter developers can use and develop the Flutter smoothly after the two developers write a consistent base of service interfaces

As for the design of the Dart Api layer of the public service component in the basic component, because the public service mainly calls the services of the Native layer, the public Dart Api is provided in Flutter as a bridge from Native to Flutter. There are many kinds of Native services. The design of the corresponding Api is that a DART file corresponds to a type of service. The entire public service component provides a unified exposed DART. The internal fine-grained DART implementation is imported through export. Dart is common_service. Dart:

library common_service; export 'network_plugin.dart'; export 'messager_plugin.dart'; .Copy the code

Upper-layer business component Api calls only need to import a DART, which is transparent to upper-layer business component developers without requiring them to know what apis are available:

import 'package:common_service/common_service.dart';
Copy the code

Viii. Engineering architecture of Flutter mixed development

We have set up the basic componentized architecture. The next step is how to make Flutter hybrid development complete engineering management. We all know that we cannot use the official local dependency mode directly, because it will directly affect the Native engineering, development process and packaging process. Therefore, we need to optimize the Flutter based on the official dependence, so we derive two ways to link Flutter to Native works:

  1. Local dependency (source dependency)
  2. Remote dependencies (product dependencies)

The existing environment of the packaging platform can only support the standard Gradle engineering structure for packaging. Besides, local dependence is disastrous for students who do not need to develop businesses related to Flutter. Therefore, remote dependence is introduced. Remote dependencies rely directly on the packaged Flutter products. Android relies on them via Gradle and IOS relies on them via Pod. This is transparent to other business development students, who do not need to care about Flutter or know that Flutter exists

The context in which these two dependency patterns are used is also different

Local dependencies are mainly used by students who need to develop a Flutter. Configure whether to enable the local Flutter Module dependency and the linked local Flutter Module address in the corresponding configuration file of the Native project. In this way, Native projects can automatically rely on local Flutter projects. The whole process is seamless. Local dependencies can also be made through source code. In IOS, local.xcconfig is created locally. The configuration properties of the two platforms are the same:

FLUTTER_MODULE_LINK_ENABLE=true
FLUTTER_MODULE_LOCAL_LINK=/Users/Sunzxyong/FlutterProject/flutter_module
Copy the code

Remote dependencies are to publish the components of the Flutter Module to remote sites and then rely on them in Native projects. This method is the default method of dependencies. This method is transparent to other developers and does not affect the development process and packaging platform

The two dependency modes mentioned above, how to carry out the engineering management and customization of these two dependency modes

1. BugFix and customize the Flutter SDK source code without invading it

The use of the Flutter SDK will inevitably encounter some problems or bugs with the Flutter SDK. However, these problems are usually caused by bugs in the linked scripts of the Flutter SDK on each platform layer. If we want to accommodate existing projects and extend the customization functions, we will directly modify the Flutter SDK source code. This will add to the cost of smooth updates to subsequent SDKS

Bugs or scripts that require customization are usually related to platform linking, excluding the need to modify the DART Api code, which can only be changed to the source code. However, the probability of bugs is relatively small, which is related to the SDK Api level. The following places are usually compatible or customized with high probability of problems:

  1. $FLUTTER_SDK/packages/flutter_tools/gradle/flutter.gradle
  2. $FLUTTER_SDK/bin/cache/artifacts/engine/android-arch/flutter.jar
  3. $FLUTTER_MODULE /. Android/build. Gradle,. Android/Settings. Gradle
  4. $FLUTTER_MODULE/.android/Flutter/build.gradle
  5. $FLUTTER_MODULE/.ios/Flutter/Generated.xcconfig
  6. $FLUTTER_MODULE/.ios/Flutter/podhelper.rb
  7. $FLUTTER_MODULE/.ios/Podfile
  8. $FLUTTER_SDK/packages/flutter_tools/bin/xcode_backend.sh

The problems and customization points we need to have with the Flutter SDK are as follows:

  1. AndroidThe Flutter engine in the Flutter SDK is not supportedarmeabiarchitecture
  2. Android: in the Flutter SDKflutter.gradleLink scripts do not support nonappName of the Application project
  3. Android: in the Flutter SDKflutter.gradleLink script local dependencies existflutter_sharedResource file does not copy Bug
  4. Android: Agents are needed to solve the above problemsbuild.gradleBuild scripts, as well as inbuild.gradleCustomize our build artifact collection Task in the build script
  5. IOS: automatically generated in the Flutter Module iospodhelper.rbRuby scripts use Podpost_installMethods lead to conflicts that Native engineering cannot be used or used, which indirectly intrudes into Native engineering and coupling and is too restrictive
  6. IOS: automatically generated in the Flutter ModulePodfileFile, need to add our own privateSpecsThe warehouse is customized
  7. IOS: solvepost_installProblem after Flutter SDKxcode_backend.shLink script environment variable reading problem

In order to make the Flutter SDK non-invasive, we used proxy Bug fixes and customizations to solve these problems. The following are the implementation strategies for the two platforms

1. Android

For the Android platform, we can automate the armeabi support by scripting. The above mentioned version automation management of Flutterw is also included in the armeabi support script. The advantage of this is that you can remove the support and add the armeabi architecture engine automatically by calling./flutterw armeabi

The problem with the Flutter. Gradle link script in the Flutter SDK will not be changed directly in the source code. Instead, it will be copied as Flutter_proxy. gradle and fixed in the proxy script. The main fixes for flutter_shared support compatibility with app hardcoded names are as follows:

Task copySharedFlutterAssetsTask = project.tasks.create(name: "copySharedFlutterAssets${variant.name.capitalize()}", type: Copy) {
	from(project.zipTree(chosenFlutterJar))
	include 'assets/flutter_shared/*'
	into "src/${variant.name}"
}
Copy the code

Let the copyFlutterAssetsTask task depend on it, and the app hardcoded name compatibility is easier, by configuring the Module name in the Native project local.properties, Add the code to read the property in flutter_proxy.gradle:

String appName = loadRootProjectProperty(project, "FLUTTER_APP_NAME", "app")
Task mergeAssets = project.tasks.findByPath(":${appName}:merge${variant.name.capitalize()}Assets")
Copy the code

Agent for the build. Gradle build scripts, we can through the execution of gradle build time, through the -c command Settings. Gradle agent, and acting out the build. Gradle and specify the build of the Module. The gradle script, as follows:

cd .android ./gradlew assembleDebug -c .. /script/proxy/settings.gradleCopy the code

Gradle: settings.gradle: settings.gradle: build.gradle

getRootProject().buildFileName = 'build_proxy.gradle'
project(":flutter").buildFileName = "build_proxy.gradle"
Copy the code

Where the agent’s script apply in Flutter/build.gradle is changed to the script agent in the fixed Flutter SDK:

apply from: "${project.projectDir.parentFile.parentFile.absolutePath}/script/proxy/flutter_proxy.gradle"
Copy the code

In this way, the Android project can be completely controlled by us during the construction period, including some custom features such as product collection plug-ins and product publishing to remote plug-ins

However, in this way, the proxy script needs to be manually specified when executing the build command, which will not be specified for Native automatic build. Therefore, based on this method, we will optimize it again. Because Flutter in the Module. The android and ios. Engineering is generated automatically by the Flutter SDK internal template, as long as the executive build | packages the get command is automatically generated, such as the first thought is to change the Flutter SDK internal engineering template, It is in the packages/ Flutter_tools /templates directory of the Flutter SDK, but this is against our non-invasive Flutter SDK, so this method cannot be selected

Recall that our Flutter SDK version consistency management is automated through the Flutterw script, which eventually links to the commands in the native Flutter SDK. Therefore, we can add a script to Flutterw that will be used to build the.Android and.ios projects Gradle and settings.gradle scripts are directly replaced with the content of our proxy scripts. This will not invade the Flutter SDK and is convenient for future maintenance. This function is no longer needed. The native build script was restored, and the Flutterw script was executed as follows:

function main() {
		# ...
		link_flutter "$@"
    	inject_proxy_build_script
    	# ...
}
Copy the code

Inject_proxy_build_script This Shell function will replace the corresponding script with our script. There is also a corresponding decision in this function, because Flutterw is mainly used to manage the consistency of the Flutter SDK version. This function only applies to the Flutter Module project. So this approach works perfectly both under locally dependent builds and through command line builds

2. IOS

For IOS, the main support is for Podfile and PodHelper. rb scripts. For Podfiles, we can simply script our own private Specs repository into the Podfile header:

Source 'https://. * * * / XXSpecs git' source 'https://github.com/CocoaPods/Specs.git' platform: ios, '8.0'...Copy the code

This work will also be compatible after flutterw execution, and can be directly commented if it is not needed later. This automatic injection script will only apply to the Flutter Module project

The podhelper.rb script is compatible with Native dependencies. The post_install function is implemented after pod install, which conflicts with Native dependencies. Therefore, post_install was commented out by default after the execution of the Flutterw script, but it certainly could not be commented out without reason. We need to understand the function of this paragraph, which is to set environment variables to prepare for the subsequent construction and execution of xcode_backend.sh script. How to comment out a different way to restore the setting of an environment variable will be discussed later. After the comment, the podhelper.rb script snippet reads:

# post_install do |installer|
#     installer.pods_project.targets.each do |target|
#         target.build_configurations.each do |config|
#             config.build_settings['ENABLE_BITCODE'] = 'NO'
#             xcconfig_path = config.base_configuration_reference.real_path
#             File.open(xcconfig_path, 'a+') do |file|
#                 file.puts "#include \"#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}\""
#             end
#         end
#     end
# end
Copy the code

Finally, the above processing script execution flow was automatically supported in Flutterw:

function main() {
		# ...
		link_flutter "$@"
    	# ...
    	podfile_support
    	podhelper_support
    	collect_ios_product "$@"
}
Copy the code

Function internal judgment only applies to the Flutter Module project, after all, other Flutter Plugin projects do not require this treatment

2. Local dependency non-invasive process

We need to open or close the local Flutter Module link dependencies through a property configuration file. It is definitely not possible to open or close the local Flutter Module link dependencies through configuration development. Affect the development of other students without Flutter environment and affect packaging on the packaging platform. The main job of the agent layer is to determine whether there is a corresponding property configuration file locally and whether the property value meets the conditions of local dependency on the Flutter Module. If so, the local dependency on the Flutter Module will be implemented. If not, Return is returned, and no processing is done by default

Therefore, this agent method does not affect the original development process of Native project, and is transparent to other business developers and packaging platform

The implementation of the agent layer is different on Android and IOS platforms

1. Android

Android is automatically managed by a Gradle script that checks the properties of the local.properties configuration file in settings. Gradle and build. Gradle. Determines whether to enable local Flutter Module links

2. IOS

IOS is a bit more complicated, because there are two types of proxy scripts that involve the Ruby execution script proxy in the Podfile and the Shell script proxy in Build Phases: In Ruby and the Shell, the final execution of the proxy script still calls the propped script, just a layer of wrapping logic before calling. In IOS, there is no local configuration file, so we create a new IOS local configuration file named local.xcconfig. This configuration file is not managed with the version and will be gitignore dropped.

eval(File.read(File.join('./', 'FlutterSupport', 'podhelper_proxy.rb')), binding)
Copy the code

The Build Phases call:

chmod +x "${SRCROOT}/FlutterSupport/xcode_backend_proxy.sh"
"${SRCROOT}/FlutterSupport/xcode_backend_proxy.sh" flutterBuild
Copy the code

The post_install function in the podhelper.rb script is commented out and replaced by the xcode_backend.sh environment variable that is executed during the IOS build phase. The environment variables Generated in the Flutter Module are Generated. Xcconfig. If we copy the contents of this file into the corresponding xcConfig of the IOS project, For example, debug.xcconfig and release. Xcconfig are feasible, but will invade the Native project, resulting in many variables in the Native project, which is not elegant. What we need to do is to ensure non-invasion

Since we have used proxy script for proxy, we can obtain these environment variables completely. Through the features of Shell script, the sub-shell will inherit the environment variable value of export from the parent Shell, so add the following code in proxy Shell script:

function export_xcconfig() {
	export ENABLE_BITCODE=NO
	if [[ $# != 0 ]]; then
		local g_xcconfig=$1/.ios/Flutter/Generated.xcconfig
		if [[ -f "$g_xcconfig" ]]; then
			# no piping.
			while read -r line
			do
  				if [[ ! "$line" =~ ^// ]]; then
					export "$line"
				fi
			done < $g_xcconfig
		fi
	fi
}
Copy the code

Note that you cannot use pipes; pipes are in a different Shell process

3. Remote dependency product packaging process

The remote dependencies of Flutter, Android via Aar and IOS via.A and.framework static libraries, are easy to implement. The key is how to package the dependencies and upload them to remote, because the existing componentized packaging, Aar, App. Framework, Flutter_assets and other products are generated in the aggregation layer Flutter Module, as well as corresponding packaged products in business components and basic components. These packaging products will be packaged with different types of products for each platform, Android or AAR, while IOS is a static library. The following is the packaging process of Android and IOS respectively

1. Android

Android packaging is relatively simple. By executing the./ Gradlew assembleRelease in the. Android subproject of the Flutter Module, the aar products will be output in the build directory of the corresponding Android subproject of the Flutter Module. The key to obtaining the products of the dependencies of the Flutter Plugin is through the. Flutter -plugins file. These files are automatically generated in the packages Get and contain the libraries that the Flutter project relies on through the Pub. To get the artifacts of the corresponding dependent library

2. IOS

Packaging on IOS is a bit more complicated than on Android. We use.ios/Runner to package static libraries and other artifacts, so we also need to set up signatures by executing them directly in the Flutter Module. This command automatically executes pod install, so we don’t need to execute it alone. The product acquisition built on IOS is relatively cumbersome. Besides obtaining the related products of Flutter, we also need to obtain the static libraries and header files of all the components that Flutter depends on.

Flutter. Framework App. Framework FlutterPluginRegistrant Flutter_assets. A static library of all Plugin dependencies and header files

So, App. Framework is a compilation of the Dart in The Flutter engine. FlutterPluginRegistrant is the registry of all plug-in channels and is automatically generated. Flutter_assets contains fonts and other resources. A static library is the implementation of each component on the IOS platform layer

In addition to collecting *. Framework static libraries and Flutter_assets in the. IOS /Flutter directory, the rest is to collect. A static libraries and corresponding header files

build/ios/$variant-iphoneos
Copy the code

For the variant, the name of the build variant is also retrieved by parsing the.flutter-plugins file. The corresponding.a static library, but the header file is not in the.a static library. Therefore, the header files are obtained separately, because the resolution of the flutter-plugins obtained the KV key pair, and the corresponding V was the project address of the flutter plugin, so we obtained the header files from it

Finally, you need to get the static library of the FlutterPluginRegistrant registry and the header file

3. Product collection and delivery dependency

The products built by the Flutter Module aggregation layer are collected and aggregated into a separate product output directory, all automatically by scripting

On Android, Gradle plugin Hook assembleTask:

collectAarTask.dependsOn assembleTask
assembleTask.finalizedBy collectAarTask
Copy the code

The./gradlew assemble${variant} command is assembled automatically

On IOS, flutterw script was used to determine whether the build command was an IOS build command after the build, and then the products after the build were automatically collected:

function collect_ios_product() {
	if [[ $# != 0 && $# > 2 ]]; then
		if [[ "$1" = "build" && "$2" = "ios" ]]; then
			# do collect...
		fi
	fi	
}
Copy the code

The key script code corresponding to. A static library and header file collection is as follows:

while read -r line
do
	if [[ ! "$line" =~ ^// && ! "$line" =~ ^# ]]; then
		array=(${line//=/ })
		local library=$product_dir/${array[0]}/lib${array[0]}.a
		if [[ -f "$library" ]]; then
			local plugin=$dest_dir/plugins/${array[0]}
			rm -rf $plugin
			mkdir -p $plugin
			cp -f $library $plugin
			local classes=${array[1]}ios/Classes
			for header in `find "$classes" -name *.h`; do
				cp -f $header $plugin
			done
	else
		echo "The static library $library do not exist!"
		fi
	fi
done < $flutter_plugins
Copy the code

The directory structure of packaged products of Android and IOS is as follows:

We know that a single AAR file and podSpec declaration of static library artifacts will lose the transfer dependencies. Losing the transfer dependencies may result in tripartite libraries not used in our Native project, referenced in the Flutter project, and then Crash the App. To ensure the transfer of dependencies, Android will publish them to remote Maven. Finally, through remote dependencies, the above products are only local dependencies. IOS will parse all the PodSpec files in the Flutter plug-in and restore them to JSON format. Get the corresponding dependency library name and version number, and finally add the dependencies to the IOS remote artifact’s PodSpec profile

For remote dependencies on IOS, we know that it is possible to create a separate Git repository for remote dependencies on IOS Native by configuring podSpecs. However, large files, including Flutter. Prepare_command is a feature of PodSpec that will execute a script to pull the two products down when the pod library is installed. For now, you can upload them to Git first. This is more intuitive and controllable, easy to version management

4. Overall engineering process of Flutter mixed development

Nine, after the order

For the mixed development of existing projects using Flutter, there are still some drawbacks, such as performance and page stack management, but they have not been used yet. In addition, some basic libraries on Flutter are not mature at present. It is still not recommended to use Flutter for the development of important pages and pages with high dynamic intensity within the project. Instead, lightweight pages can be used. For the communication between Flutter and Native layer, Flutter should be used as the consumer layer to consume the services provided by the Native layer. The Native end should make as few changes as possible, preferably only one line of code. Replace page route hops with Flutter page routes to ensure zero coupling between Native and Flutter