Series of articles:
Dart interface to generate engine Fluttify
How to use Fluttify to develop a new Flutter plugin
Fluttify output Flutter plugin works in detail
(4) Introduction to the principle of Fluttify compiler
Fluttify: fluttify.com
Fluttify
What is?
Fluttify is a tool that generates the Dart interface for the native SDK. Github address: github.com/fluttify-pr… .
Fluttify
What problem was solved?
Here are a few ways to start plug-in development:
- Normal plug-in development (Native thick, Dart thin)
The normal development approach is to sink functionality implementations into the native side and then implement a thin layer of abstraction on the WRAPPED native methods on the Dart side. When the SDK interface design is the same at both ends, there will be less resistance. For example, the official plug-in of Google Map is developed in this way.
When I developed amap_base at the end of last year (2018), I also developed amap_base in accordance with the routine of Google Map plug-in at the beginning. The Android side was very smooth, because most of the SDK interface of Autonavi’s Android was copied from Google Map. But when it comes to iOS, it’s a bit of a headache, because iOS doesn’t copy Google Map’s interface design (even the same function requires a different number of methods!). As a result, it was extremely difficult to sink the functionality implementation to both ends and then do a thin layer of abstraction in Dart, and a large number of fields needed to be compared between the two ends. The process of developing AMAP_Base was exhausting for me.
Fluttify
Plug-in development (Native thin, Dart thick)
My experience with amap_base got me thinking about how to minimize mechanical effort in the plug-in development process. After a period of mental programming, I believe that the work of abstracting the native interfaces on both ends must be done on the Dart side, rather than implementing good interfaces on the native side and then making a thin layer of abstraction on the Dart side. The Dart side must be thick and the native side must be thin.
So how thin should the native be? We keep adjusting this degree in the beginning of coding. At first I just wanted to generate code for the public interface of the non-Model class, which translates into Dart line by line. It’s not easy to translate each sentence as you write, and how do you tell if a class is a Model class or a non-model class? That’s not going to happen. So after that I decided to generate the Dart class for all the public classes, and only distinguish between the View class and the non-View class, because the View class needs to generate the corresponding PlatformView, and then use the MethodChannel to wire the Dart/Native together.
Fluttify aims to make it possible for developers who understand only one end, or neither, to develop with the Dart interface that Fluttify generates, thereby masking native code.
The principle is introduced
reflection
With the concept of Fluttify in mind, the first approach I came up with was naturally to rely on a template project for the SDK, then reflect all the interfaces in the native SDK through reflection, and then splicing the generated code. This solution has several bottlenecks:
- On Android, it is possible to implement code reflection by relying on the target JAR in gradle, but what about aar?
- In order for iOS to rely on the Framework, it must be an Xcode project, so it must have a MacOS environment
Fluttify
Make it a service and deploy it on a server and then deploy it on a MacOS server? - The follow-up implementation
Flutter for web
andFlutter Desktop
It became a completely unknown situation, and there was also the engineering structure of the corresponding platform and the reflection mechanism of the corresponding language.
After briefly experimenting with reflection, I decided that reflection was not the answer.
Code parsing
After the reflection solution failed, I began to wonder if I could directly parse the source files to generate the AST, which would be much less restrictive and would not incur additional learning costs if the platform was connected later. But how to parse it? After a few days of searching without understanding how compilation works, I was about to give up. One day after searching the language parse keyword on Github, I found the answer: Antlr.
Antlr provides a nice abstraction layer to traverse the source files, so parsing the code is no longer a problem. Antlr also provides a wide range of syntax files, including Java, Objective-C, and subsequent Flutter for Web and Flutter Desktop, paving the way for the future development of Fluttify.
The core principle of Fluttify is to generate a structured SDK representation after antLR parsing, and then generate the Flutter plug-in project according to this SDK representation. It’s even possible to extend this structured SDK representation to generate plugins for React Native and other frameworks.
Case analysis
Here provides a typical case of AMap SDK to do an analysis. Amap provides the ability to display marks on the map, which is called Marker on the Android end and Annotation on the iOS end. What’s wrong with the tagged interface?
On Android, AMap::addMarker(MarkerOption) is called in one step. All configuration items are in MarkerOption and will return the corresponding Marker object for you to manipulate.
On iOS, there are three steps:
- call
MAMapView::addAnnotation(MAAnnotation)
; - call
MAMapView::delegate
Configure the callbackself
Because thedelegate
Is weak reference; - implementation
MAMapViewDelegate::mapView:viewForAnnotation
According to step 1MAAnnotation
configurationMAPinAnnotationView
And returnMAPinAnnotationView
In Android, the tag parameters that are configured once are scattered inMAAnnotation
neutralizationMAPinAnnotationView
;
How do you think you can unify the abstraction on the Native side for Dart? It’s not that it can’t be done, but that it requires additional complexity, which must be handled carefully once the code is scaled up. In addition, how can iOS construct a tag object to return to Dart as Android does? I sometimes think that the brain circuit of the iOS SDK authors of Autonavi is also really strange, how could they come up with such a design, like the Android SDK of Autonavi copy Google Map is not sweet? What is the most funny, baidu map interface design with gaode hair, I do not know who is copied who.
When DEVELOPING amap_base, I introduced some extra complexity to try to unify the Android and iOS interfaces, but the effect was still not ideal. For example, when the native interface return type was non-model type in SDK (addMarker method), I can’t return it to the Dart side, so I can’t control a single Marker on the Dart side, unless I introduce some complexity on the Native side, such as putting the Marker object into a global list or Map.
With Fluttify, all of these inconsistencies can be dispatched on the Dart side, and Native code takes care of the output.
Known limitations and interim solutions
Fluttify does not currently generate all interfaces. Some are due to the limitations of Flutter itself, while others are due to the object-oriented nature of Flutter.
Native calls the Dart method to synchronize the return
When Native calls Dart and needs to return the object constructed on the Dart side synchronously, the ideal way to do this is to call Dart through MethodChannel, construct the corresponding object on the Dart side, and then return it to the callback method. However, since Native calls Dart are asynchronous, So can’t sync is returned to the callback method, the specific case is MAMapViewDelegate: : mapView: viewForAnnotation this method. The solution is to write only native code by hand. There is an issue on Github about synchronizing methodChannels. If you are interested, check out github.com/flutter/flu…
Dart calls Native to return the interface type
Because in object-oriented, subclass instances can be assigned to superclass variables, when Native returns an interface superclass to Dart, Dart cannot construct the interface class. The current solution is to find the first class in the SDK that implements the interface and instantiate it. Particular case is MAMapViewDelegate: : mapView: DidAnnotationViewTapped method.
How to useFluttify
?
Please email me ([email protected]) and explain the source.
The roadmap
- (in progress) Implement most of the features on the AMap SDK (autonavi was chosen only because the use cases are more complete);
- Build a website (using Flutter build if possible) to start beta testing;
- Open use;
Qq group