preface

Flutter has been around for 2 years now and although we have been keeping an eye on Flutter, we still want to wait for the ecology to mature before stepping on the pit. There was a recent project that required cross-platform technology, and after discussion we chose to use Flutter. After the development is completed, I summarize some important points here for your reference. Of course, if you want to learn, you need to read the document and then code yourself.

Environment configuration:

Refer to official documentation

The Dart language

Flutter uses the Dart language. My experience with Flutter is that the syntax is basically equal to Java + Javascript + some other common syntax. It is not very expensive to learn and not very interesting.

  • All variables are objects

  • Static language

  • Support closures

  • The method is top-level

  • Reflection is supported (Flutter does not support reflection)

  • Attributes/classes without visibility modifiers preceded by _ are private

  • Stream: supports map… Operators, subscriptions, etc

  • Asynchronous: Dart’s asynchronous operations are also implemented as Futrue (as in Javascript Promise), and async await syntactic sugar (automatically wrapped as Futrue) is also supported. This is not a Dart specific feature, and there are plenty of resources available online.

  • The assignment operator

    • ? :
    • ??
    • ?? =
  • Optional method parameter

 void setUser(String name,{id = '0'}); / / callsetUser('mario',id : '01');
Copy the code
  • Cascade operator
var profit = Profit() .. fund ='fund'
     ..profit = 'profit'
     ..profitValue = 'profitValue';
Copy the code
  • Dynamic can refer to any type without type checking.
var a = 'test'; (a as dynamic).hello(); // The compiler will not report an errorCopy the code

Flutter

The Widget concept

Almost all objects in Flutter are widgets. Unlike the “controls” in native development, the concept of widgets in Flutter is broader. Widgets can represent not only UI elements but also functional components such as: GestureDetector widgets for gesture detection, themes for APP Theme data delivery, and so on, while controls in native development usually just refer to UI elements.

Widget work = HTML + CSS work. And many of the configuration styles have properties with names similar to those in CSS.

There are two types of widgets: StatelessWidget StatefulWidget. Their core method is to return a Widget through the build() method.

  @protected
  Widget build(BuildContext context);
Copy the code
  • StatelessWidgetbuild()In the Widget.
  • StatefulWidgetBecause you must create the correspondingState<T extends Widget>, so includingbuild()All of the relevant lifecycle methods are presentStateIn the.

    The following isStateSince an image is also a Widget, it is also an image’s life cycle.

The Widget catalog (link )

Here are all the widgets that are officially available, and you can see that basically all uI-related content is implemented with different types of widgets, nested with the Child/Children parameter.

Different styles of widgets

In addition to the basic widgets, widgets are available in Material(Android) + Cupertino(ioS) visual styles. For example, you can use a marterial-style RaisedButton or a cuptino-style CupertinoButton, and you don’t have to worry about designers making Android look like ioS.

Layout Widget

There is also a Layout Widget for controlling the Layout, which can be used as a container.

  • Container
  • Padding
  • Center
  • Stack
  • Column
  • Row
  • Expanded
  • ListView

Interaction model Widget

Widgets that control clicking, swiping, and other interactions. Click events in Flutter are not setOnClickListener. Instead, a layer of interactive widgets is added to the Widget, such as clicking to use GestureDetector. For example, add a click event to the Image in the Splash screen above.

  @override
  Widget build(BuildContext context) {
    return Container(
      height: double.infinity,
      width: double.infinity,
      child: Image.asset('images/logo.png')); } ==> @override Widget build(BuildContext context) {returnContainer(height: double. Infinity, width: double. Infinity, child: GestureDetector(onTap: () {// Click the event}, child: Image.asset('images/logo.png'),),); }Copy the code

Sample

So, what does a basic Widget look like? This is a Splash screen with a login check.

  • StatelessWidget
class SplashPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    checkLogin();
    return Container(
      height: double.infinity,
      width: double.infinity,
      child: Image.asset('images/logo.png')); } // use async syntax to automatically wrap as Futrue, meaning this method is asynchronous. checkLogin(BuildContext context) async { var sp = await SharedPreferences.getInstance(); var token = sp.getString("X-Auth-Token");
    if(token ! = null && token ! ="")
      Navigator.pushNamedAndRemoveUntil(
          context, HomePage.routeName, (_) => false);
    elseNavigator.pushNamed(context, LoginRegisterPage.routeName); }}Copy the code
  • StatefulWidget
Class SplashPage extends StatefulWidget {// Create the corresponding state@override State createState() => _SplashState(); } class _SplashState extends State<SplashPage> { @override voidinitState() {
    super.initState();
    checkLogin(context);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: double.infinity,
      width: double.infinity,
      child: Image.asset('images/logo.png')); } // use async syntax to automatically wrap as Futrue, meaning this method is asynchronous. checkLogin(BuildContext context) async { var sp = await SharedPreferences.getInstance(); var token = sp.getString("X-Auth-Token");
    if(token ! = null && token ! ="")
      Navigator.pushNamedAndRemoveUntil(
          context, HomePage.routeName, (_) => false);
    else
      Navigator.pushNamed(context, LoginRegisterPage.routeName);
  }
  
  @override
  void dispose() { super.dispose(); }}Copy the code

App structure

The figure above shows the structure of the whole Flutter App, starting from the parent node:

  1. MyApp: The entrance of the entire App is inmain.dartthemain()Function, callrunApp(MyApp())MyApp is also a Widget that defines global content such as themes, multilingual, and routing
  2. MaterialApp: A Material style theme, and CupertinoApp.
  3. MyHomePage MyHomePageState: an image that is also a Widget.
  4. Scaffold: Defines some basic effects of a screen, such as AppBar and sliding effects, which adopt Material style, as well as ioS styleCupertinoPageScaffold.
  5. That leaves some basic components.

A basic main.dart might look like this:

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

class MyApp extends StatelessWidget {
  static final navigatorKey = GlobalKey<NavigatorState>();
  static NavigatorState get navigator => navigatorKey.currentState;

  @override
  Widget build(BuildContext context) {
    return  CupertinoApp(
        title: ' ', theme: CupertinoThemeData( primaryColor: Color(0xFFFFFFFF), barBackgroundColor: Color(0xFF515669), scaffoldBackgroundColor: Color(0xFF3C3B45), ), navigatorKey: navigatorKey, routes: { HomePage.routeName: (_) => HomePage(), LoginRegisterPage.routeName: (_) => LoginRegisterPage(), LoginPage.routeName: (_) => LoginPage(), ForgetPswPage.routeName: (_) => ForgetPswPage(), RegisterPage.routeName: (_) => RegisterPage(), }, ), home: SplashPage(), ); }}Copy the code
  • themeWe have defined a CupertinoApp theme in ioS style. We may need to use the Material Cupertino control in development, so we need to customize the theme.
  • routesParameter registers the routing table
  • homeParameter Setting The Splash screen that is first loaded

routing

Similar to routing on the Web, you register the url and screen in the routing table. Basic method

  • push / pushNamed / pushNamedAndRemoveUntil/…
  • pop / popUntil / …

Basic use:

Future pushNamed(BuildContext Context, String routeName,{Object arguments}) Pass a 00 navigator.of (context).pushnamed ("home_page", arguments: '00'); Var arg = modalroute.of (context).settings.arguments); // Close a screen and return 01 navigator.of (context).pop(01);Copy the code
  • There’s actually a better way to deal with passing values
  • You can seepushNamedThe method return value is oneFuture, indicating an asynchronous operation because the screen that opens can be acceptedpopReturned when closedresult, in herepopIs returned with a 01, so it can be received like this.
var result = async Navigator.of(context).pushNamed("home_page", arguments: 'arg');
Copy the code

Network request and serialization

Flutter’s web request library is not perfect and currently uses Dio, which is roughly a simplified version of OKHTTP.

Since reflection is prohibited for Flutter because runtime reflection interferes with Dart’s tree shaking, serialization by reflection like Gson’s is not feasible. There are two possible solutions:

  • Writing:DioThe return value is parsed as a Map/List, so you can write it by hand:
  Future<Profits> requestProfits() async {
    var response = await dio.get("u/profits");
    var data = response.data;
    print("requestProfits:$data"); var profit = Profit() .. fund = data['profit'] ["fund"]
      ..profit = data['profit'] ["profit"]
      ..profitValue = toMoney(data['profit'] ['profitValue']);

    returnProfits() .. miningProfit = data['miningProfit']
      ..lastMiningProfit = data['lastMiningProfit']
      ..profit = profit;
  }
Copy the code
  • Generate code: use jSON_serializable
//user.dart

import 'package:json_annotation/json_annotation.dart'; // user.g.art will automatically generate the part after we run the build command'user.g.dart'; @jsonSerialIZABLE () class User{User(this.name, this.email); @jsonSerializable () {this.name, this.email); String name; String email; // Different classes use different mixins factory user.fromjson (Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);  
}
Copy the code

Of course, you still need to write the fromJson toJson template code, which can also be generated.

Platform-specific code

Flutter is mainly responsible for the UI part of the build. Platform-specific code is still implemented natively, which is handled in two main ways:

  • Platform ChannelThe registration convention between the Flutter end and the native end is goodplatform_channel_namePlatform Channel, and then call the method and pass the parameter, the other end of the parse line. This is how a plugin with native capabilities is implemented. Such as
//flutter
MethodChannel('method_channel_mobile').invokeMethod('sendMobile'.'13000000000')

//Android MainActivity

MethodChannel(flutterView, MOBILE_CHANNEL)
            .setMethodCallHandler { methodCall, result ->
                when {
                    TextUtils.equals(methodCall.method, "mobile") -> {
                        mobile = methodCall.arguments.toString()
                        result.success("success")
                    }
                     result.notImplemented()
                }
            }
Copy the code
  • PlatformViewNested native views directly into Flutter, but this is not efficient. The other thing to be careful about is not passing in a View toPlatformViewOtherwise the Flutter end may call this multiple timesPlatformViewStates will co-exist, and will not be destroyed.
// Define a PlatformView and PlatformViewFactory to instantiate Native View Class ButtonFactory(private val Context: context) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(p0: Context? , p1: Int, p2: Any?) : PlatformView {return ButtonPlatformView(context)
    }
    class ButtonPlatformView(
        private val context: Context
    ) : PlatformView {

        override fun getView(): Button {
            return Button(context)
        }
        override fun dispose() {}}} // Register for MainActivity ()"native_view").platformViewRegistry()
            .registerViewFactory("native_view",ButtonPlatformFactory)
Copy the code

Widget nesting issues

One of the most common problems discussed online with Flutter nesting is that there are too many layers of Flutter nesting as the UI becomes more complex. There is indeed a problem. As mentioned earlier, widgets include configuration files as well as views, so a Widget like a Button might need 3 or 4 layers of nesting. Here is a login button I wrote in the login screen, feel it:

  CupertinoButton _loginButton() {
    return CupertinoButton(
      padding: EdgeInsets.all(0),
      child: Container(
          width: double.infinity,
          height: 45,
          decoration: BoxDecoration(
            gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: _isLoginAvailable
                    ? <Color>[Color(0xFF657FF8), Color(0xFF4260E8)]
                    : <Color>[Color(0xFFCBCFE2), Color(0xFF73788F)]),
            borderRadius: BorderRadius.all(Radius.circular(6)),
          ),
          child: Center(
            child: Text(
              "Log in", textAlign: TextAlign.center, style: TextStyle( fontSize: 18, color: _isLoginAvailable ? Colors.white : Color(0x76FFFFFF), fontWeight: FontWeight.bold, ), ), )), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), onPressed: ! _isLoginAvailable ? null : _startCustomFlow, ); }Copy the code

In fact, this is just the structure + style section, not the post-click logic. Even you can see that the text in the Button is implemented by nesting a Widget, but this is one of the advantages of Flutter. It no longer requires the writer of the custom Widget to provide a lot of details like whether the text can be bold, color-changing, italic, and so on. Just let you pass a Widget and do it yourself, and there are many similar cases. Another problem is that Widget State can be too many, including the State of each Widget and the State of the screen, reminiscent of the original Android Activity of 50 variables. However, I think this is mainly because Flutter is in the early stage of development and does not have a mature architecture. Currently, Flutter officially provides a library Provider for state management. My current solution is to try to commission methods and separate widgets:

  • A separate Widget that has irrelevant local state for the entire pageStatefulWidget.
  • For those that do not have local states, you take a cut if you need to reuse oneStatelessWidgetReturn the Widget with a method that does not need to be extracted. Refer to the Button above.
  • Finally, inbuild()Only the structure of the whole picture is described in the method.

For example, the build() method for the login screen looks something like this:

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: cusAppBar(context, elevation: 0),
        backgroundColor: $3C3B45, body: Stack( children: <Widget>[ SingleChildScrollView( child: Container( margin: EdgeInsets.only(left: 15, right: 15), child: Column( children: <Widget>[ _logo(), Form( onChanged: _onFormChanged, child: Column( children: <Widget>[ _phoneRow(), _divider(), _passwordColumn(), _forgetPswText(), _loginButton(), _registerText() ], ), ), ], ), ),),),),); }Copy the code

Hot load

Hot overloading of Flutter is a popular feature, not least because Debug mode is JIT compiled and Release mode is AOT-compiled. It actually works pretty well.

The problem

  • Waiting for another flutter command to release the startup lock… Address: rm. / flutter/bin/cache/lockfile
In the end, this is just a summary of some important points, and the final official documentation should definitely be read to familiarize yourself with most of the widgets:The document

conclusion

Advantages:

  • The UI on both Android and iOs devices is highly consistentBecause Flutter uses its own UI drawing engine and logic, it does not use Native View at all, and only calls the Native drawing interface, so that the UI of Flutter can be almost identical between the two platforms. This is why Flutter also needs to be used for all platforms, including Web maCos. I’ve been using Android for debugging during development, and when I finally ran it on Ios, it was pretty much the same (although the UI isn’t too complicated at the moment).
  • Accessing native is relatively easy: Requires functionality implemented natively to passPlatformChannelandPlatformViewMost of them can be done. They can passPlatformChannelTo launch a nativeActivity/FragmentThe implementation. (Such as the scan function)
  • Blue blood: Google’s full support, domestic big factories are also actively trying.
  • Degree of initial availability: Currently I have completed a small project development and have not encountered too many problems with native interaction.

Disadvantages:

  • The absence of basic functionsMany basic functions also need to be implemented by plugin through native, such as Webview Map components, not to mention some SDK, almost all need to write plugin.
  • Cross-platform communication: For heavy useMethodChannelDesign and maintenance issues in case of communication and API differences between platforms.
  • performance: Native Flutter is currently close to native in frame count and user experience, but has a higher memory overhead, especially for video.

Overall: My view is that Flutter is promising across more platforms and is currently suitable for developing apps that interact with native platform apis with less complexity.