Product | youdao ZhiYun
Edit | Ryan
Source | youdao technical team (ID: youdaotech)

1. Why Flutter

There are many cross-end technologies. Why Flutter? What are the advantages and disadvantages of it?

Let’s take a look at the specific engineering effects:

V.qq.com/txp/iframe/…

Web side effect experience:

Test-pupilmath.youdao.com/pupil-flutt…

1.1 Flutter VS Native

In any case, the native operational efficiency is undoubtedly the highest, but in terms of engineering workload, especially the rapid trial-and-error and business expansion phases, the Flutter is by far the preferred tool.

Flutter VS Web 1.2

Any cross-terminal technology is based on the multi-terminal thinking to solve the problem of engineering efficiency. Many previous cross-terminal technologies, such as React Native, are web-based cross-terminal solutions. However, as we all know, there is a huge gap between the operating efficiency of the Web on mobile terminal and that on PC. This leads to an RN can’t effectively in the mobile terminal to complete various complex interactive operations (such as complex animation, interactive performance, etc.), even if is the introduction of an Lottie engine will still be on low-end phones seem to be very caton (of course, you can also use some of the research of engine technology for each client to solve, But that misses the point of crossing).

1.3 the Flutter performance

The compilation mode and product of the Flutter are the prerequisite for its efficient operation, unlike the cross-end compilation of the Web (most cross-end compilation of the Web uses the concept of “bridge” to call the compilation product, usually using the native side entrance + web side bridge), The Flutter is basically the source code of dart that is translated into a platform by platform. This “unbridged” result is what we expect to get with the native performance. (Of course, dart was originally designed with a lot of front-end architecture in mind. The front end is particularly evident in the syntax, and the original Dart2JS was based on the same “bridge” concept).

For example, Google released the new Version of Flutter on September 23, in support of the Windows compilation product, through a compilation tool like Visual Studio (if you want to compile your Project Flutter into a Windows product, You need to install some vs-related compilation plug-ins in advance), generate the engineering solution for Windows. SLN, and finally generate the DLL call mode, running smoothly, you can download the attached Release. Zip to try to run. (Download from release.zip)

(PS: All compilation projects here are done through the same set of code, including the web address, mobile case and here in the Windows case)

1.4 Performance comparison with RN

The above is the comparison of some data of Flutter and RN under the same functional module, and a relatively representative group is extracted from numerous data.

1.5 Diversity of cross-terminal platforms

1.6 the engine

Flare-flutter is a very good Flutter animation engine. The generated animations have been pro tested on Windows, mobile and the Web.

1.7 syntactic sugar

A? .B

If A is equal to null, then A? B is null

If A is not equal to null, then A? A. is b. is C. is D. is

Animal animal = new Animal(‘cat’);

Animal empty = null;

// if animal is not null, return the value cat from animal

print(animal? .name);

// Empty is empty and returns null

print(empty? .name);

A?? B

If A is null, then A?? B to B

If A is not null, then A?? B to a.

1.8 Comprehensive Evaluation

1.9 Interactive Applications

The interactions generated by the Flutter can be embedded in any end using a streamlined instruction set for interaction, which holds great promise for interactive scenarios (teaching scenarios, etc.). Here is a demo of live simultaneous interactions.

Ii. Business structure of Flutter

There is no existing MVVM framework in Flutter, but we can implement MVVM using the Element tree feature.

2.1 the ViewModel

abstract class BaseViewModel {
  bool _isFirst = true;
  BuildContext context;

  bool get isFirst => _isFirst;

  @mustCallSuper
  void init(BuildContext context) {
    this.context = context;
    if (_isFirst) {
      _isFirst = false;
      doInit(context);
    }
  }

  // the default load data method
  @protected
  Future refreshData(BuildContext context);

  @protected
  void doInit(BuildContext context);

  void dispose();
Copy the code
class ViewModelProvider<T extends BaseViewModel> extends StatefulWidget { final T viewModel; final Widget child; ViewModelProvider({ @required this.viewModel, @required this.child, }); static T of<T extends BaseViewModel>(BuildContext context) { final type = _typeOf<_ViewModelProviderInherited<T>>(); _ViewModelProviderInherited < T > Element provider = / / query tree InheritedElement cache context.ancestorInheritedElementForWidgetOfExactType(type)? .widget; return provider? .viewModel; } static Type _typeOf<T>() => T; @override _ViewModelProviderState<T> createState() => _ViewModelProviderState<T>(); } class _ViewModelProviderState<T extends BaseViewModel> extends State<ViewModelProvider<T>> { @override Widget build(BuildContext context) { return _ViewModelProviderInherited<T>( child: widget.child, viewModel: widget.viewModel, ); } @override void dispose() { widget.viewModel.dispose(); super.dispose(); }} / / InheritedWidget Element can be tree cache class _ViewModelProviderInherited < T extends BaseViewModel > extends InheritedWidget {  final T viewModel; _ViewModelProviderInherited({ Key key, @required this.viewModel, @required Widget child, }) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidget oldWidget) => false;Copy the code

2.2 DataModel

import 'dart:convert'; import 'package:pupilmath/datamodel/base_network_response.dart'; import 'package:pupilmath/datamodel/challenge/challenge_ranking_list_item_data.dart'; import 'package:pupilmath/utils/text_utils.dart'; / / / history list class ChallengeHistoryRankingListResponse extends BaseNetworkResponse < ChallengeHistoryRankingData > { ChallengeHistoryRankingListResponse.fromJson(Map<String, dynamic> json) : super.fromJson(json); @override ChallengeHistoryRankingData decodeData(jsonData) { if (jsonData is Map) { return ChallengeHistoryRankingData.fromJson(jsonData); } return null; } } class ChallengeHistoryRankingData { String props; int bestRank; Int onlistTimes; Int total; / / the total number challenge List < ChallengeHistoryRankingItemData > ranks; String get qrcode => textutils.isempty (props)? '' : json.decode(props)['qrcode'] ?? ''; ChallengeHistoryRankingData.fromJson(Map<String, dynamic> json) { props = json['props']; bestRank = json['bestRank']; onlistTimes = json['onlistTimes']; total = json['total']; if (json['ranks'] is List) { ranks = []; (json['ranks'] as List).forEach( (v) => ranks.add(ChallengeHistoryRankingItemData.fromJson(v))); }}} / / / historical record of the item class ChallengeHistoryRankingItemData {ChallengeRankingListItemData champion; / / the best ChallengeRankingListItemData user; ChallengeHistoryRankingItemData.fromJson(Map<String, dynamic> json) { if (json['champion'] is Map) champion = ChallengeRankingListItemData.fromJson(json['champion']); if (json['user'] is Map) user = ChallengeRankingListItemData.fromJson(json['user']); }Copy the code

2.3 the View

import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:pupilmath/datamodel/challenge/challenge_history_ranking_list_data.dart'; import 'package:pupilmath/entity_factory.dart'; import 'package:pupilmath/network/constant.dart'; import 'package:pupilmath/network/network.dart'; import 'package:pupilmath/utils/print_helper.dart'; import 'package:pupilmath/viewmodel/base/abstract_base_viewmodel.dart'; import 'package:rxdart/rxdart.dart'; / / the daily challenges history record class ChallengeHistoryListViewModel extends BaseViewModel {BehaviorSubject < ChallengeHistoryRankingData > _challengeObservable = BehaviorSubject(); Stream<ChallengeHistoryRankingData> get challengeRankingListStream => _challengeObservable.stream; @override void dispose() { _challengeObservable.close(); } @override void doInit(BuildContext context) { refreshData(context); } @override Future refreshData(BuildContext context) { return _loadHistoryListData(); } _loadHistoryListData() async { Map<String, dynamic> parametersMap = {}; parametersMap["pageNum"] = 1; parametersMap["pageSize"] = 10; HandleDioRequest (() => networkHelper.instance.getdio ().get(challengeHistoryListUrl, queryParameters: parametersMap), onResponse: (Response response) { ChallengeHistoryRankingListResponse rankingListResponse = EntityFactory.generateOBJ(json.decode(response.toString())); if (rankingListResponse.isSuccessful) { _challengeObservable.add(rankingListResponse.data); } else { _challengeObservable.addError(null); } }, onError: (error) => _challengeObservable.addError(error), ); } Future<ChallengeHistoryRankingData> syncLoadHistoryListData( int pageNum, int pageSize, ) async { Map<String, dynamic> parametersMap = {}; parametersMap["pageNum"] = pageNum; parametersMap["pageSize"] = pageSize; try { Response response = await NetWorkHelper.instance .getDio() .get(challengeHistoryListUrl, queryParameters: parametersMap); ChallengeHistoryRankingListResponse rankingListResponse = EntityFactory.generateOBJ(json.decode(response.toString())); if (rankingListResponse.isSuccessful) { return rankingListResponse.data; } else { return null; } } catch (e) { printHelper(e); } return null; }Copy the code

2.4 Some infrastructure

2.5 How do views and ViewModels initialize and interact

2.6 Business architecture withdrawal of Flutter

If business form is unified series products, can also be pulled out a set of core architecture, reuse product in the same production line, such as the current product line is given priority to with education, with the property of the Flutter yard multiterminal, you can put the test version of the production factory, rendering engine, adaptation framework, as well as the framework of the interface are pulled out across the end, Quickly developing reusable templates can eliminate the trial-and-error cost of business with half the effort, as can any other product line of business.

Iii. Fitment of the Flutter

UI adaptation in any framework is particularly heavy work, especially across terminals, so it is important to translate across platforms within the same set of layouts. Initially, there was no dp or SP adaptation provided in the Flutter. In addition, if the conversion ratio of the underlying Matrix is directly changed, the display of the original HD resolution mobile phone may not be so clear. The width and height units of the Flutter are num, which will be corresponding to the unit size of each platform in the final compilation.

To ease the design burden on the designer, an iOS design is usually used. A generic design of 375 x 667 is converted to 360 x 640 (1080 x 1920) on Android. The unit of the flutter is also related to the pixel density of the corresponding phone.

3.1 Construct a conversion utility class:

// Import 'dart: IO '; import 'dart:ui'; import 'dart:math'; import 'package:pupilmath/utils/print_helper.dart'; bool initScale = false; Double iosScaleRatio = 0; // Double iosScaleRatio = 0; // Double androidScaleRatio = 0; // Double androidScaleRatio = 0; // Double androidScaleRatio = 0; // Double textScaleRatio = 0; const double baseIosWidth = 375; const double baseIosHeight = 667; const double baseIosHeightX = 812; const double baseAndroidWidth = 360; const double baseAndroidHeight = 640; void _calResizeRatio() { if (Platform.isIOS) { final width = window.physicalSize.width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; final widthScale = (width / ratio) / baseIosWidth; final heightScale = (height / ratio) / baseIosHeight; iosScaleRatio = min(widthScale, heightScale); } else if (Platform.isAndroid) { double widthScale = (baseAndroidWidth / baseIosWidth); double heightScale = (baseAndroidHeight / baseIosHeight); double scaleRatio = min(widthScale, heightScale); AndroidScaleRatio = double-.parse (scalerati.toString ().subString (0, 4)); androidScaleRatio = double-.parse (scalerati.toString ().subString (0, 4)); } } bool isFullScreen() { return false; } double resizeUtil(double value) {if (! initScale) { _calResizeRatio(); initScale = true; } if (Platform.isIOS) { return value * iosScaleRatio; } else if (Platform.isAndroid) { return value * androidScaleRatio; } else { return value; } // The scale ratio of each screen is not the same, if you ask a question on aN iOS device, you need to convert the coordinates of the question to the original coordinates, Double unResizeUtil(double value) {if (iosScaleRatio == 0) {_calResizeRatio(); } if (Platform.isIOS) { return value / iosScaleRatio; } else { return value / androidScaleRatio; }} / / text zooming _calResizeTextRatio () {final width = window. PhysicalSize. The width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; double heightRatio = (height / ratio) / baseIosHeight / window.textScaleFactor; double widthRatio = (width / ratio) / baseIosWidth / window.textScaleFactor; textScaleRatio = min(heightRatio, widthRatio); } double resizeTextSize(double value) { if (textScaleRatio == 0) { _calResizeTextRatio(); } return value * textScaleRatio; } double resizePadTextSize(double value) { if (Platform.isIOS) { final width = window.physicalSize.width; final ratio = window.devicePixelRatio; final realWidth = width / ratio; If (realWidth > 450) {return value * 1.5; } else { return value; } } else { return value; } } double autoSize(double percent, bool isHeight) { final width = window.physicalSize.width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; if (isHeight) { return height / ratio * percent; } else { return width / ratio * percent; }Copy the code

3.2 Specific use:

In this way, whenever there is a change in resolution or adaptation scheme, the resizeUtil can be directly modified. However, the problem brought by this is that the unit becomes very lengthy in the writing process, and people who are not familiar with the team project will easily forget to write it, resulting in a long time for error checking and high code intrusion. The dart language’s extension function features were used to make some improvements to resizeUtil.

3.3 Low intrusive resizeUtil

Construct the desired unit by extending the NUM of the DART, using dp and SP as examples here, and add the extension in resizeUtil:

Extension dimensionsNum on num {/// Convert dp double get dp => resizeUtil(this.todouble ()); Sp double get sp => resizeTextSize(this.todouble ()); Get padSp => resizePadTextSize(this.todouble ());Copy the code

Then write the units directly in the layout:

Some pits in the Flutter

4.1 Pits on generics

When we first started using generics for automatic data parsing on mobile, we used t.tostring to determine the type, but when compiled into a Web release version, a program that works well on mobile doesn’t work well on the Web:

At the beginning, I always focused on the compilation mode, because there were three compilation modes, dev Profile release, and it only failed to work on release. I mistakenly thought that there was a bug in the compilation under release. After further discussion with the Flutter team, In the Web version of release, everything is compressed (including the definition of the type), so in release, t.tostring () returns null, so the generic characteristics are not recognized. For more details: github.com/flutter/flu…

In release mode everything is minified, the (T.toString() == “Construction2DEntity”) comparison fails and you get entity null returned.

If you change the code to (T ==Construction2DEntity) it will fix your app. Finally, it is suggested that it is safest to directly write T== in any mode.

class EntityFactory { static T generateOBJ<T>(json) { if (1 == 0) { return null; } else if (T = = "ChallengeRankingListDataEntity") {/ / / the daily challenges list return ChallengeHomeRankingListResponse. FromJson (json) as T; } else if (T == "KnowledgeEntity") { return KnowledgeEntity.fromJson(json) as T; }}Copy the code

4.2 How to use iframe to load other web pages after being translated into Web products

For mobile devices, webview_flutter solves the problem of loading the Web. However, when compiled into the Web product, it is no longer possible to use the WebView plug-in to load the web. In this case, some of the ways that DART was originally designed to write web pages are needed. Namely HtmlElmentView:

import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html' as html;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
           child: Iframe()
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){},
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}
class Iframe extends StatelessWidget {
  Iframe(){
    ui.platformViewRegistry.registerViewFactory('iframe', (int viewId) {
      var iframe = html.IFrameElement();
      iframe.src='https://flutter.dev';
      return iframe;
  });
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      width:400,
      height:300,
      child:HtmlElementView(viewType: 'iframe')
    );
  }
Copy the code

However, this method caused a new underlying refresh rendering problem (when the mouse moves over an element, it will constantly flash to refresh), which has been fixed in the new version, interested students can check out: github.com/flutter/flu…)

4.3 How does Flutter load native HTML and communicate

Built-in HTML is a requirement for many projects. Many materials on the Web are streamed from native HTML and loaded in. This method is not compatible with many files, and it is easy to write files that are too large to be read. We should use IFrameElement to load and communicate, similar to the front end:

4.4 WebView gestures cannot be used properly on iOS 13.4

The official Webview_flutter will cause gestures to be intercepted and cannot be used properly after iOS 13.4. This problem will be solved temporarily by using flutter_webview_plugin (currently, WebView has been targeted to fix the problem). But it has not been verified), but flutter_webview_plugin cannot write user-agent on iOS, so it can be solved by modifying the local plug-in code:

The file location is:

Flutter /. Pub – cache/hosted/pub. The flutter – IO. Cn/flutter_web… (@available(iOS 9.0, *)) {if (userAgent! = (id)[NSNull null]) {self.webview.customUserAgent = userAgent; }}

The webview_flutter gesture issue is still under discussion:

Github.com/flutter/flu…).

About layout and operations

5.1 Container Widgets and render Widgets

5.2 GlobalKey

RenderBox = RenderBox; RenderBox = RenderBox; RenderBox = RenderBox; RenderBox = RenderBox;

5.3 Floating Point Operations

Dart floating-point operations are all high-precision double operations. When the operation length is too long, DART will automatically random the last decimal place. As a result, some floating-point operations are uncertain each time, and in this case, manual precision conversion is required.

5.4 Translation and rotation of Matrix

In the matrix conversion process, if the ordinary matrix.translate is used, it will result in the rotate, and then translate will do the coefficient superposition translation operation on the rotation base, which is not the desired result after the calculation. Therefore, if the rotate operation is used, You should use leftTranslate to ensure that each operation is independent:

Vi. Project optimization

6.1 Avoid build() method time:

6.2 Redraw area optimization:

6.3 Avoid using Opacity

6.4 Single thread model of Flutter

All events in the Microtask Queue are executed first. The events in the Event Queue are not executed until the Microtask Queue is empty.

6.5 Time Consumption Is stored in the Isolate

The Isolate is a thread in the Dart. Each Isolate does not share memory but communicates with each other through messages.

The Dart code runs in the Isolate. Only the codes in the same Isolate can access each other.

Seven, miscellaneous summary

After a long period of exploration and project validation of Flutter, I now have some miscellaneous summaries of my own about Flutter:

7.1

The Flutter performs very well on mobile, and it runs very well in terms of smoothness. The optimized app with a lot of graphics still runs well on older Android phones in 2013, and iOS is as smooth as it should be.

7.2

For web applications, The Flutter is still evolving and there are many unresolved issues, including mobile WebViews and programmed Web applications, that are not yet suitable for large-scale production of the Web.

7.3

As for mixing with Native, in order to avoid memory problems and rendering problems in mixing stack application, it is suggested to design the embedded Native Flutter node on the leaf node as far as possible, that is, after the service stack jumps to the Flutter, it will return to the Native stack as far as possible after completion.

7.4

Based on the “go to the bridge” native compilation, Flutter should be expected to run on various platforms in the future. The current proven mobile applications perform well when packaged into Windows applications, although some larger applications will take time to explore and refine.

7.5

In terms of grammar, the DART in Flutter is becoming easier and easier, and it is also using some good front-end framework grammar, such as React, etc. Kotlin also has a lot of similarities. It feels that the Team is trying to promote the development of the big front-end era.

All in all, the Flutter does bring a lot of surprises that previous cross-end solutions could not satisfy, and it is believed that the multi-end will become increasingly important in the near future, especially when it comes to the cost of exploring new businesses.

These are some brief summaries of the Flutter. Anyone interested in discussing them is welcome.

Netease Youdao, with you, because of love so choose, looking forward to like-minded you to join us.

  • END –