Adapting Flutter dependencies in iOS and Android systems for high-fidelity e-commerce
This article is part of Alibaba’s Utilizing Flutter series.
E-Commerce platforms have traditionally been split between iOS and Android systems, requiring developers to design and develop two distinct systems. Now, thanks to Flutter technology, it is possible for developers to create high-performance and high-fidelity apps for both iOS and Android from a single codebase.
This development, however, poses the problem of integrating the technology with existing and live projects. In this article, the technical team at Alibaba’s Xianyu (闲鱼, China’s emerging, second-hand market platform) presents a general engineering transformation plan for project practitioners hoping to address common concerns, and provides a reference for preparing to transform projects to Flutter.
Xianyu’s Flutter Tech
In the first half of 2018, the Xianyu technical team took the lead in utilizing Flutter technology in the unification of client development, and successfully transformed and launched the complex commodity detail service. During this process, the priority problem of seamlessly bridging the connection between Flutter and initial iOS and Android projects emerged and had to ensure that the development efficiency was not affected.
Flutter has a special engineering structure, consisting of the Flutter directory, which includes the directories of native iOS and Android projects. By default, a native project that introduces Flutter cannot be built and run independent of the parent directory because it inversely relies on the related libraries and resources of Flutter.
With a native project, a developer is unlikely to create a new Flutter project to rewrite the entire project. Therefore, the Flutter project will contain the existing native project, which introduces the following problems:
· Building packages
After the introduction of Flutter, the native project is coupled with Flutter and cannot be compiled and constructed independently. In the Flutter environment, the construction of a project begins with Flutter’s construction command, for which the execution process includes the construction of the native project. The developer must configure the Flutter run-time environment to complete the entire process.
· Reduction of development efficiency caused by hybrid compilation
In the process of transitioning to Flutter, there must be numerous services still being developed in the native environment. The change of project structure does not allow development to be carried out in the native environment. Moreover, the adaption to the Flutter engineering structure creates unnecessary construction steps for purely native development, thus resulting in reduced development efficiency.
In response to the above problems, the Xianyu technical team propose the following transformation objectives and strive to minimize the dependence of a native project on the related files of Flutter, so that:
· The native project can be compiled and debugged independently, thereby minimizing interference to related developers and freeing the packaging platform from dependency on the Flutter environment and related processes.
· The native project in the Flutter environment (i.e. iOS or Android subdirectories) can depend on the relevant libraries and files, and implement various types of Flutter functions, such as dart code construction, debugging, and hot reload. This will ensure the correctness of the development under the Flutter environment.
In order to achieve the above objectives, the tech team at Xianyu incorporated two modes for the two directories, organized dependency, and introduced dependency as outlined below.
Two modes, two directories
The tech team’s solution was to differentiate between projects based on their directory type; native projects in a stand-alone directory environment are defined as Standalone mode and those in the Flutter directory are defined as Flutter mode. Pure native development or platform packaging is considered Standalone mode. In this mode, Flutter remains transparent to developers and the packaging platform, and does not affect construction and debugging. The Flutter’s code is developed in Flutter mode, and the generation, compilation, and debugging of related libraries follow the process defined by Flutter.
From the above definition, the core of the transformation is to extract the Standalone mode, then to sort out the dependency of the Standalone mode on Flutter and extract it into a third-party library, resource, or source file. Taking iOS as an example, by reading the source code built by Flutter, the Xcode project has the following dependencies on Flutter:
Related files of application source code written by dart.
Flutter engine library files.
· .pubs plugin directory and files for indexing
Plugins of Flutter, including various systems and custom channels.
Resources that Flutter relies on, such as fonts, pictures, and so on.
During the transformation process, the Xianyu tech team experimented with two types of strategies to introduce dependency: local dependency and remote dependency.
The first strategy is to modify the Flutter construction process with local dependency, where the library files, source code, and resources are placed in the subdirectory of the native project for reference. For iOS, the .Flutter.framework and related plugins are made into local pod dependency and the resources are also copied to the local repository for maintenance.
As a result, the Standalone mode obtains the ability to build and execute independently. For purely native developers, there is the option to ignore Flutter, as it is just a collection of second-party libraries and resources.
Under Flutter mode, the construction process of the dart source code is unchanged and does not affect the compilation and debugging. Meanwhile, due to the local dependency, various changes under Flutter mode can be synchronized to the subdirectory of the native project in real time, so that the Standalone mode also has the latest Flutter-related functions once the changes are submitted.
Local dependency allows for convenient synchronization of changes of the related Flutter content to Standalone mode, which is a significant advantage. The main disadvantage, however, lies in Flutter’s original construction process which requires a slightly complicated modification that will conflict with the subsequent Flutter code merge. The content of the native project and that of Flutter is still coupled, so they are not wholly independent.
The second strategy is to modify the Flutter construction process with remote dependency, where all content that Flutter depends on is placed in a separate remote repository. Under Standalone mode, the related resources, source code, and library files in the remote repository are referenced, while under Flutter mode, the construction process and reference method are unchanged.
The advantage of remote dependency is that it does not drastically modify Flutter’s own construction process, effectively solving the problem of local coupling. However, this strategy also leads to a more cumbersome synchronization process. In addition, the changes of Flutter content must first be synchronized to the remote repository and then to the Standalone mode to take effect.
In lieu of these disadvantages, the Xianyu tech team ultimately settled on the remote dependency strategy to introduce dependency due to the main benefit of the local coupling solution.
Realizing transformations includes effectively organizing the directory and implementing remote dependency, as outlined below.
The way in which directories are organized has a significant effect in realizing transformation. Under Flutter mode, the iOS and android subdirectories in the parent project directory respectively contain the corresponding native projects. In terms of code management, the subprojects can use the submodule form of git to ensure independence between the directories.
Remote dependency implementation
Under Standalone mode, Flutter’s dependent content points to the corresponding file in the remote repository and the dependency method in Flutter mode is unchanged. In order to implement remote dependency, the following issues need consideration:
· Synchronize Flutter changes to Standalone mode
Since the cumbersome synchronization of changes is the main issue with remote dependency, the Xianyu team developed a series of scripting tools to automate the process as much as possible. Assuming that the Flutter content (which may be service source code, engine library, or some resource files) changes, once construction is complete in Flutter mode the script will extract all the generated dependent files to the remote repository, submit and tag, and then generate new remote dependency descriptions based on the tags (for example, the podspec under iOS). Finally, the dependency of Flutter will be modified to the latest version in Standalone mode to complete the entire synchronization process.
· Synchronization timing
It is recommended that during the period of estimation and grayscale, each submission of the Flutter service triggers the execution of the synchronization script and the packaging of the app and that the synchronization is maintained once a day during the development.
Cures for Common Problems
This section outlines common problems with transformation realization and offers solutions that the Xianyu tech team found most effective.
iOS project name problem
Flutter requires the default use of Runner.xcodeproj as the iOS project name, otherwise, the construction will fail. The project name can be customized by modifying the code of flutter tools (the code can be found at flutter/packages/flutter_tools and relevant logic can be viewed by globally searching Runner.xcodeproj), but future conflicts may occur when merging the code. The Xianyu team has addressed this issue and changed its project name to Runner. The names of other projects can be determined according to their actual situation.
When Flutter is built, the pod installation can be automatically executed if certain conditions are met. However, in Alibaba’s taobaoenv environment, errors often occur. Even if the construction is successful, it usually takes a long time for pod installation to complete execution. After the execution, the whole project has to be recompiled, which is time-consuming. Therefore, the Xianyu team has commented out Flutter’s processing on pod, and left it up to the developer to manually perform the pod operation.
Manual execution may be needed to switch branches and upgrade the library version, and it is likely that re-updating the pod is a real requirement. Moreover, manual execution may be required when the construction options of Flutter switch between release, debug and profile, and the Flutter.framework they introduce have different sources. In such cases, special attention can be paid to the re-update of pod.
In order to solve the engineering adaptation problem after the introduction of Flutter, the Xianyu tech team extracted the relevant dependency of Flutter and put it on the remote repository for the pure native project to refer, thus ensuring the mutual independence and parallel execution of Flutter and the pure native development.
The program has been implemented in several versions of Xianyu and has been exported back to the Flutter team to provide direction and reference for the organization plan of its subsequent hybrid project. Meanwhile, the program can also be used to help teams that are transforming their work to Flutter. Obviously, different projects lead to different programs and so the team looks forward to exchanges for better methods and opinions.
(Original article by Kang Kai康凯)