Dart Mixin介绍

关于 Dart mixin 的一些理解。理解 mixin 概念的关键在于理解中间类。

Mixins are a way of reusing code in multiple class hierarchies

先来看一个简单例子:

class Piloted {
  int astronauts = 1;
  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

class PilotedCraft extends Spacecraft with Piloted {
  // ···
}

PilotedCraft 拥有 astronauts 字段和 describeCrew() 方法。

mixin 是什么?

维基百科中这样定义 mixin:

In object-oriented programming languages, a Mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

即,mixin 是另外一个普通类,我们可以在不继承这个类的情况下从这个类”借用”方法和变量。

Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead.

从这个角度来讲,mixin 不过是 abstract class

Java tries to make up for this by using Interfaces, but that is not as useful or flexible as mixins.

从这个角度来讲,可以认为 mixin 是带实现的接口。

小节

  • mixin 有点类似 abstract class
  • mixin 有点类似 interface
  • 不能继承 mixin
  • 可以使用 mixinabstract classclass 来作为 mixin

如何使用 mixin?

使用 mixin 的方法很简单:with 关键字后面跟上 mixin 的名字即可。

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

实现 mixin 的方法同样也很简单:创建一个继承自 Object 的类并且不要声明构造方法。如果想让 mixin 作为普通类使用,使用 class 关键字;如果不想让 mixin 作为普通类使用,使用 mixin 关键字代替 class

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

on 的用法

The keyword on is used to restrict our mixin’s use to only classes which either extends or implements the class it is declared on. In order to use the on keyword, you must declare your mixin using the mixin keyword

class B {}
mixin Y on B {
  void hi() {
    print('hi');
  }
}
class Q with Y {}

则有如下错误提示:

Error: 'Object' doesn't implement 'B' so it can't be used with 'Y'.

on 关键字限制了 Y 的使用范围:Y 只能用于继承或实现了 B 的类。修复方式是让 Q 继承自 B

class Q extends B with Y {}

mixin 解决了什么问题?

mixin 解决了多重继承中的 Deadly Diamond of Death(DDD) 问题。

多重继承问题简单描述。各个类的继承关系如下:

class Performer {
    abstract void perform();
}

class Dancer extends Performer {
    void perform() {}
}

class Singer extends Performer {
    void perform() {}
}

class Musician extends Dancer, Singer {
}

问题来了,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是模糊的

来看 mixin 如何解决这个问题。见 Dart for Flutter : Mixins in Dart - Flutter Community - Medium

class Performer {
    abstract void perform();
}

mixin Dancer {
    void perform() {}
}

mixin Singer {
    void perform() {}
}

class Musician extends Performer with Dancer, Singer {
}

现在,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是确定的。在这里是调用 Singer.perform()

mixin 有一套明确的机制来选择调用哪个方法。

假设 Musician 类使用多个 mixin (DancerSinger)。该类有个方法名为 perform()Musician 类继承自 Performer 类。

  • 首先,将 Performer 类置于栈顶
  • 其次,后声明的 mixin 优先于先声明的 mixin。按顺序将 mixin 置于栈中,在这里分别是 DancerSinger
  • 最后,将 Musician 类自己置于栈中。Musician 类中的 perform() 被优先调用

Dart 使用的是单重继承 (Java 也是单重继承,C++ 是多重继承)。多重继承更为强大,但会引起 Deadly Diamond of Death(DDD) 问题。

Java 使用接口(interface)来部分实现多重继承。多重继承的问题是需要在每个类中实现接口(interface),所以并不是一个好的方案。(实际上 Java 已经通过默认方法修复了这个问题)

所以 Dart 中就有了 mixin。

理解 mixin

Mixins in Dart work by creating a new class that layers the implementation of the mixin on top of a superclass to create a new class — it is not “on the side” but “on top” of the superclass, so there is no ambiguity in how to resolve lookups.
Mixins is not a way to get multiple inheritance in the classical sense. Mixins is a way to abstract and reuse a family of operations and state. It is similar to the reuse you get from extending a class, but it is compatible with single-inheritance because it is linear.
StackOverflow

class A {
  String getMessage() => 'A';
}

class B {
  String getMessage() => 'B';
}

class P {
  String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
  String result = '';

  AB ab = AB();
  result += ab.getMessage();

  BA ba = BA();
  result += ba.getMessage();

  print(result);
}

以上这段代码输出 BA

从语义上讲以上这段代码等同于:

class PA = P with A;
class PAB = PA with B;

class AB extends PAB {}

class PB = P with B;
class PBA = PB with A;

class BA extends PBA {}

继承结构图是这样的:

Since each mixin application creates a new class, it also creates a new interface (because all Dart classes also define interfaces). As described, the new class extends the superclass and includes copies of the mixin class members, but it also implements the mixin class interface.
In most cases, there is no way to refer to that mixin-application class or its interface; the class for Super with Mixin is just an anonymous superclass of the class declared like class C extends Super with Mixin {}. If you name a mixin application like class CSuper = Super with Mixin {}, then you can refer to the mixin application class and its interface, and it will be a sub-type of both Super and Mixin.

理解 mixin 的关键在于它是线性的。

使用场景

  • 在没有共同父类的各个类中共享代码时
  • 在父类中实现某种方法无意义时

在 Dart 语言中,我们经常可以看到对 mixin 关键字的使用,根据字面理解,就是混合的意思。那么,mixin 如何使用,它的使用场景是什么呢。

继续阅读Dart Mixin介绍

Flutter构建Windows应用

从2.10之后的版本,flutter已经正式支持构建Windows应用。不过距离实现全平台构建,还需要考虑很多问题,flutter构建的应用更加适合移动端的使用习惯,如果需要构建Windows应用,我的建议是使用一些适合桌面端的widget。

具体方法如下:

条件

  • 需要安装Visual Studio 2019或者Visual Studio 2022,简言之,需要安装Windows 10 SDK。
  • 2.10之前的版本是默认关闭的。Linux和Mac是默认关闭的,可以手动进行打开。

方法

1. 打开Windows的平台支持

PowerShell输入如下指令(如下命令可以使用flutter config查看,2.10以上的版本默认开启

flutter config --enable-windows-desktop

# 其他平台
flutter config --[no-]enable-macos-desktop

flutter config --[no-]enable-linux-desktop

可以使用flutter doctor查看情况

如果报错:

$ flutter doctor
Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source!
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.0.1, on Microsoft Windows [版本 10.0.22000.708], locale zh-CN)
[√] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[√] Chrome - develop for the web
[☠] Visual Studio - develop for Windows (the doctor check crashed)
    X Due to an error, the doctor check did not complete. If the error message below is not helpful, please let us
      know about this issue at https://github.com/flutter/flutter/issues.
    X Exception: Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found while decoding string: [
        {
          "instanceId": "5cb9d9e1",
          "installDate": "2022-06-12T07:42:21Z",
          "installationName": "VisualStudio/17.2.3+32526.322",
          "installationPath": "D:\\Program Files\\Microsoft Visual Studio\\2022\\Community",
          "installationVersion": "17.2.32526.322",
          "productId": "Microsoft.VisualStudio.Product.Community",
          "productPath": "D:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\devenv.exe",
          "state": 4294967295,
          "isComplete": true,
          "isLaunchable": true,
          "isPrerelease": false,
          "isRebootRequired": false,
          "displayName": "Visual Studio Community 2022",
          "description": "����ǿ��� IDE����ѧ��������Դ��������ߺ͸������ʹ��",
          "channelId": "VisualStudio.17.Release",
          "channelUri": "https://aka.ms/vs/17/release/channel",
          "enginePath": "C:\\Program Files (x86)\\Microsoft Visual
          Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
          "installedChannelUri": "https://aka.ms/vs/17/release/channel",
          "releaseNotes":
          "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.2#17.2.3",
          "thirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288",
          "updateDate": "2022-06-12T07:42:21.8130931Z",
          "catalog": {
            "buildBranch": "d17.2",
            "buildVersion": "17.2.32526.322",
            "id": "VisualStudio/17.2.3+32526.322",
            "localBuild": "build-lab",
            "manifestName": "VisualStudio",
            "manifestType": "installer",
            "productDisplayVersion": "17.2.3",
            "productLine": "Dev17",
            "productLineVersion": "2022",
            "productMilestone": "RTW",
            "productMilestoneIsPreRelease": "False",
            "productName": "Visual Studio",
            "productPatchVersion": "3",
            "productPreReleaseMilestoneSuffix": "1.0",
            "productSemanticVersion": "17.2.3+32526.322",
            "requiredEngineVersion": "3.2.2148.26540"
          },
          "properties": {
            "campaignId": "2030:fe40db45-8e98-49e0-82b4-afb6aade85b4",
            "channelManifestId": "VisualStudio.17.Release/17.2.3+32526.322",
            "nickname": "",
            "setupEngineFilePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
          }
        }
      ]
      . The Flutter team would greatly appreciate if you could file a bug explaining exactly what you were doing
      when this happened:
      https://github.com/flutter/flutter/issues/new/choose
      The source bytes were:
      [91, 13, 10, 32, 32, 123, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115, 116, 97, 110, 99, 101, 73, 100, 34, 58,
      32, 34, 53, 99, 98, 57, 100, 57, 101, 49, 34, 44, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115, 116, 97, 108,
      108, 68, 97, 116, 101, 34, 58, 32, 34, 50, 48, 50, 50, 45, 48, 54, 45, 49, 50, 84, 48, 55, 58, 52, 50, 58,
      50, 49, 90, 34, 44, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115, 116, 97, 108, 108, 97, 116, 105, 111, 110,
      78, 97, 109, 101, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100, 105, 111, 47, 49, 55, 46,
      50, 46, 51, 43, 51, 50, 53, 50, 54, 46, 51, 50, 50, 34, 44, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115, 116,
      97, 108, 108, 97, 116, 105, 111, 110, 80, 97, 116, 104, 34, 58, 32, 34, 68, 58, 92, 92, 80, 114, 111, 103,
      114, 97, 109, 32, 70, 105, 108, 101, 115, 92, 92, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 86, 105,
      115, 117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 92, 92, 50, 48, 50, 50, 92, 92, 67, 111, 109, 109, 117,
      110, 105, 116, 121, 34, 44, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115, 116, 97, 108, 108, 97, 116, 105, 111,
      110, 86, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 55, 46, 50, 46, 51, 50, 53, 50, 54, 46, 51, 50,
      50, 34, 44, 13, 10, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 73, 100, 34, 58, 32, 34, 77, 105,
      99, 114, 111, 115, 111, 102, 116, 46, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100, 105, 111, 46, 80, 114,
      111, 100, 117, 99, 116, 46, 67, 111, 109, 109, 117, 110, 105, 116, 121, 34, 44, 13, 10, 32, 32, 32, 32, 34,
      112, 114, 111, 100, 117, 99, 116, 80, 97, 116, 104, 34, 58, 32, 34, 68, 58, 92, 92, 80, 114, 111, 103, 114,
      97, 109, 32, 70, 105, 108, 101, 115, 92, 92, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 86, 105, 115,
      117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 92, 92, 50, 48, 50, 50, 92, 92, 67, 111, 109, 109, 117, 110,
      105, 116, 121, 92, 92, 67, 111, 109, 109, 111, 110, 55, 92, 92, 73, 68, 69, 92, 92, 100, 101, 118, 101, 110,
      118, 46, 101, 120, 101, 34, 44, 13, 10, 32, 32, 32, 32, 34, 115, 116, 97, 116, 101, 34, 58, 32, 52, 50, 57,
      52, 57, 54, 55, 50, 57, 53, 44, 13, 10, 32, 32, 32, 32, 34, 105, 115, 67, 111, 109, 112, 108, 101, 116, 101,
      34, 58, 32, 116, 114, 117, 101, 44, 13, 10, 32, 32, 32, 32, 34, 105, 115, 76, 97, 117, 110, 99, 104, 97, 98,
      108, 101, 34, 58, 32, 116, 114, 117, 101, 44, 13, 10, 32, 32, 32, 32, 34, 105, 115, 80, 114, 101, 114, 101,
      108, 101, 97, 115, 101, 34, 58, 32, 102, 97, 108, 115, 101, 44, 13, 10, 32, 32, 32, 32, 34, 105, 115, 82,
      101, 98, 111, 111, 116, 82, 101, 113, 117, 105, 114, 101, 100, 34, 58, 32, 102, 97, 108, 115, 101, 44, 13,
      10, 32, 32, 32, 32, 34, 100, 105, 115, 112, 108, 97, 121, 78, 97, 109, 101, 34, 58, 32, 34, 86, 105, 115,
      117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 32, 67, 111, 109, 109, 117, 110, 105, 116, 121, 32, 50, 48,
      50, 50, 34, 44, 13, 10, 32, 32, 32, 32, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58,
      32, 34, 185, 166, 196, 220, 199, 191, 180, 243, 181, 196, 32, 73, 68, 69, 163, 172, 185, 169, 209, 167, 201,
      250, 161, 162, 191, 170, 183, 197, 212, 180, 180, 250, 194, 235, 178, 206, 211, 235, 213, 223, 186, 205,
      184, 246, 200, 203, 195, 226, 183, 209, 202, 185, 211, 195, 34, 44, 13, 10, 32, 32, 32, 32, 34, 99, 104, 97,
      110, 110, 101, 108, 73, 100, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100, 105, 111, 46,
      49, 55, 46, 82, 101, 108, 101, 97, 115, 101, 34, 44, 13, 10, 32, 32, 32, 32, 34, 99, 104, 97, 110, 110, 101,
      108, 85, 114, 105, 34, 58, 32, 34, 104, 116, 116, 112, 115, 58, 47, 47, 97, 107, 97, 46, 109, 115, 47, 118,
      115, 47, 49, 55, 47, 114, 101, 108, 101, 97, 115, 101, 47, 99, 104, 97, 110, 110, 101, 108, 34, 44, 13, 10,
      32, 32, 32, 32, 34, 101, 110, 103, 105, 110, 101, 80, 97, 116, 104, 34, 58, 32, 34, 67, 58, 92, 92, 80, 114,
      111, 103, 114, 97, 109, 32, 70, 105, 108, 101, 115, 32, 40, 120, 56, 54, 41, 92, 92, 77, 105, 99, 114, 111,
      115, 111, 102, 116, 32, 86, 105, 115, 117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 92, 92, 73, 110, 115,
      116, 97, 108, 108, 101, 114, 92, 92, 114, 101, 115, 111, 117, 114, 99, 101, 115, 92, 92, 97, 112, 112, 92,
      92, 83, 101, 114, 118, 105, 99, 101, 72, 117, 98, 92, 92, 83, 101, 114, 118, 105, 99, 101, 115, 92, 92, 77,
      105, 99, 114, 111, 115, 111, 102, 116, 46, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100, 105, 111, 46, 83,
      101, 116, 117, 112, 46, 83, 101, 114, 118, 105, 99, 101, 34, 44, 13, 10, 32, 32, 32, 32, 34, 105, 110, 115,
      116, 97, 108, 108, 101, 100, 67, 104, 97, 110, 110, 101, 108, 85, 114, 105, 34, 58, 32, 34, 104, 116, 116,
      112, 115, 58, 47, 47, 97, 107, 97, 46, 109, 115, 47, 118, 115, 47, 49, 55, 47, 114, 101, 108, 101, 97, 115,
      101, 47, 99, 104, 97, 110, 110, 101, 108, 34, 44, 13, 10, 32, 32, 32, 32, 34, 114, 101, 108, 101, 97, 115,
      101, 78, 111, 116, 101, 115, 34, 58, 32, 34, 104, 116, 116, 112, 115, 58, 47, 47, 100, 111, 99, 115, 46,
      109, 105, 99, 114, 111, 115, 111, 102, 116, 46, 99, 111, 109, 47, 101, 110, 45, 117, 115, 47, 118, 105, 115,
      117, 97, 108, 115, 116, 117, 100, 105, 111, 47, 114, 101, 108, 101, 97, 115, 101, 115, 47, 50, 48, 50, 50,
      47, 114, 101, 108, 101, 97, 115, 101, 45, 110, 111, 116, 101, 115, 45, 118, 49, 55, 46, 50, 35, 49, 55, 46,
      50, 46, 51, 34, 44, 13, 10, 32, 32, 32, 32, 34, 116, 104, 105, 114, 100, 80, 97, 114, 116, 121, 78, 111,
      116, 105, 99, 101, 115, 34, 58, 32, 34, 104, 116, 116, 112, 115, 58, 47, 47, 103, 111, 46, 109, 105, 99,
      114, 111, 115, 111, 102, 116, 46, 99, 111, 109, 47, 102, 119, 108, 105, 110, 107, 47, 63, 76, 105, 110, 107,
      73, 100, 61, 54, 54, 49, 50, 56, 56, 34, 44, 13, 10, 32, 32, 32, 32, 34, 117, 112, 100, 97, 116, 101, 68,
      97, 116, 101, 34, 58, 32, 34, 50, 48, 50, 50, 45, 48, 54, 45, 49, 50, 84, 48, 55, 58, 52, 50, 58, 50, 49,
      46, 56, 49, 51, 48, 57, 51, 49, 90, 34, 44, 13, 10, 32, 32, 32, 32, 34, 99, 97, 116, 97, 108, 111, 103, 34,
      58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 34, 98, 117, 105, 108, 100, 66, 114, 97, 110, 99, 104, 34, 58,
      32, 34, 100, 49, 55, 46, 50, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 98, 117, 105, 108, 100, 86, 101,
      114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 55, 46, 50, 46, 51, 50, 53, 50, 54, 46, 51, 50, 50, 34, 44, 13,
      10, 32, 32, 32, 32, 32, 32, 34, 105, 100, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100,
      105, 111, 47, 49, 55, 46, 50, 46, 51, 43, 51, 50, 53, 50, 54, 46, 51, 50, 50, 34, 44, 13, 10, 32, 32, 32,
      32, 32, 32, 34, 108, 111, 99, 97, 108, 66, 117, 105, 108, 100, 34, 58, 32, 34, 98, 117, 105, 108, 100, 45,
      108, 97, 98, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 109, 97, 110, 105, 102, 101, 115, 116, 78, 97, 109,
      101, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100, 105, 111, 34, 44, 13, 10, 32, 32, 32,
      32, 32, 32, 34, 109, 97, 110, 105, 102, 101, 115, 116, 84, 121, 112, 101, 34, 58, 32, 34, 105, 110, 115,
      116, 97, 108, 108, 101, 114, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116,
      68, 105, 115, 112, 108, 97, 121, 86, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 55, 46, 50, 46, 51,
      34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 76, 105, 110, 101, 34, 58, 32,
      34, 68, 101, 118, 49, 55, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 76,
      105, 110, 101, 86, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 50, 48, 50, 50, 34, 44, 13, 10, 32, 32, 32,
      32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 77, 105, 108, 101, 115, 116, 111, 110, 101, 34, 58, 32,
      34, 82, 84, 87, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 77, 105, 108,
      101, 115, 116, 111, 110, 101, 73, 115, 80, 114, 101, 82, 101, 108, 101, 97, 115, 101, 34, 58, 32, 34, 70,
      97, 108, 115, 101, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 78, 97,
      109, 101, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 34, 44, 13, 10, 32,
      32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 80, 97, 116, 99, 104, 86, 101, 114, 115, 105, 111,
      110, 34, 58, 32, 34, 51, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117, 99, 116, 80,
      114, 101, 82, 101, 108, 101, 97, 115, 101, 77, 105, 108, 101, 115, 116, 111, 110, 101, 83, 117, 102, 102,
      105, 120, 34, 58, 32, 34, 49, 46, 48, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 100, 117,
      99, 116, 83, 101, 109, 97, 110, 116, 105, 99, 86, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 55, 46,
      50, 46, 51, 43, 51, 50, 53, 50, 54, 46, 51, 50, 50, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 114, 101,
      113, 117, 105, 114, 101, 100, 69, 110, 103, 105, 110, 101, 86, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34,
      51, 46, 50, 46, 50, 49, 52, 56, 46, 50, 54, 53, 52, 48, 34, 13, 10, 32, 32, 32, 32, 125, 44, 13, 10, 32, 32,
      32, 32, 34, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32,
      32, 34, 99, 97, 109, 112, 97, 105, 103, 110, 73, 100, 34, 58, 32, 34, 50, 48, 51, 48, 58, 102, 101, 52, 48,
      100, 98, 52, 53, 45, 56, 101, 57, 56, 45, 52, 57, 101, 48, 45, 56, 50, 98, 52, 45, 97, 102, 98, 54, 97, 97,
      100, 101, 56, 53, 98, 52, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 99, 104, 97, 110, 110, 101, 108, 77,
      97, 110, 105, 102, 101, 115, 116, 73, 100, 34, 58, 32, 34, 86, 105, 115, 117, 97, 108, 83, 116, 117, 100,
      105, 111, 46, 49, 55, 46, 82, 101, 108, 101, 97, 115, 101, 47, 49, 55, 46, 50, 46, 51, 43, 51, 50, 53, 50,
      54, 46, 51, 50, 50, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 110, 105, 99, 107, 110, 97, 109, 101, 34,
      58, 32, 34, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 34, 115, 101, 116, 117, 112, 69, 110, 103, 105, 110,
      101, 70, 105, 108, 101, 80, 97, 116, 104, 34, 58, 32, 34, 67, 58, 92, 92, 80, 114, 111, 103, 114, 97, 109,
      32, 70, 105, 108, 101, 115, 32, 40, 120, 56, 54, 41, 92, 92, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32,
      86, 105, 115, 117, 97, 108, 32, 83, 116, 117, 100, 105, 111, 92, 92, 73, 110, 115, 116, 97, 108, 108, 101,
      114, 92, 92, 115, 101, 116, 117, 112, 46, 101, 120, 101, 34, 13, 10, 32, 32, 32, 32, 125, 13, 10, 32, 32,
      125, 13, 10, 93, 13, 10]


[√] Android Studio (version 2021.2)
[√] IntelliJ IDEA Ultimate Edition (version 2018.3)
[√] Connected device (4 available)
[√] HTTP Host Availability

! Doctor found issues in 1 category.

则需要更新Flutter SDK到3.0.2以上的版本即可,如下:

$ flutter upgrade

具体原因,参考:[windows] Exception: Bad UTF-8 encoding when running flutter doctor or building/running for Windows #102451

2. 在旧项目中添加Windows平台支持

在项目地址输入如下指令

flutter create --platforms=windows[,macos,linux] .

注意: 项目的名称必须是全小写,如果出现大小写则会报错

"TxxxFxxx" is not a valid Dart package name.

See https://dart.dev/tools/pub/pubspec#name for more information.
3. 构建Windows项目exe
flutter build windows

参考链接


Visual Studio Code调试Flutter报错“[CHROME]:need to run as root or suid”

在最新的 Ubuntu 22.04 系统上使用Visual Studio Code调试Flutter应用,Chrome使用Snap安装。

通过指定 CHROME_EXECUTABLE=/snap/bin/chromium,在调试的时候,报错如下:

Launching lib/main.dart on Chrome in debug mode...
lib/main.dart:1
[CHROME]:need to run as root or suid
Failed to launch browser after 3 tries. Command used to launch it: /snap/bin/chromium --user-data-dir=/tmp/flutter_tools.SEVTFD/flutter_tools_chrome_device.AMHTFX --remote-debugging-port=38457 --disable-background-timer-throttling --disable-extensions --disable-popup-blocking --bwsi --no-first-run --no-default-browser-check --disable-default-apps --disable-translate http://localhost:42407
Failed to launch browser. Make sure you are using an up-to-date Chrome or Edge. Otherwise, consider using -d web-server instead and filing an issue at https://github.com/flutter/flutter/issues.
Exited (sigterm)

解决方法就是指定真实的Chrome安装路径,直接调用,而不是通过Snap的沙箱,解决沙箱无法调试的问题。

如下:

#export CHROME_EXECUTABLE=/snap/bin/chromium
export CHROME_EXECUTABLE=/snap/chromium/current/usr/lib/chromium-browser/chrome

上述方法同样可以解决 Android Studio 在 Ubuntu 22.04 系统上无法找到 Chrome 的问题。

参考链接


How to get the snap-based chromium to access a separately mounted filesystem?

Flutter找不到Android模拟器解决

Flutter配置好后,在Android Studio中找不到设备

完成Flutter的Android配置之后,连上设备,运行flutter doctor,发现已经识别了一个可用设备了

[✓] Connected device (1 available)

但是用Android Studio新建Flutter项目之后,却一直显示未找到设备。

No connected devices found; please connect a device, or see flutter.io/setup for getting started ins

最后在Stack Overflow上找到了解决方案:

https://stackoverflow.com/questions/49222658/device-list-doesnt-shows-in-android-studio-using-flutter

就是要配置一下flutter关联的Android sdk路径和Android Studio文件夹,我的设置完sdk路径就可以正常找到device了,问题解决~。

粗体部分替换成自己的Android sdk路径:

flutter config --android-sdk /path/to/android/sdk

粗体部分替换成自己的android studio文件夹路径(我的不用配置这个就成功了):

flutter config --android-studio-dir /path/to/android/studio

注意,上面的操作在重启之后无效,如果希望重启之后也生效,则编辑 ~/.flutter_settings ,增加如下配置即可:

android-sdk=/path/to/android/sdk

参考链接


三阶贝塞尔曲线cubicTo

flutter中绘制基础引言

Flutter 中实现绘制的主要是CustomPainter类

class MyPainter extends CustomPainter{
  ///实际的绘画发生在这里
  @override
  void paint(Canvas canvas, Size size) {
  }
  ///这样Flutter就知道它必须调用paint方法来重绘你的绘画。
  ///否则,在此处返回false表示您不需要重绘
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

然后放在父控件的child里用 CustomPaint 包裹使用

/// CustomPaint 创建画布
child: CustomPaint(
          size: const Size(200, 200),

          ///这是CustomPainter类的一个实例,它在画布上绘制绘画的第一层
          painter: MyPainter(),

          ///完成绘画之后,子画构将显示在绘画的顶部。
          child: const Center(
              child: Text("Blade Runner"),
          ),

          ///foregroundPaint:最后,这个油漆绘制在前两个图层的顶部
          foregroundPainter: MyPainter(),
          )
三阶贝塞尔曲线

在 flutter 通过 Canvas 来结合 Path 来实现绘制 三阶贝塞尔曲线,三阶贝塞尔曲线就是说两个点之间的线 有两个控制点。

例如我们要绘制上述的椭圆,其中 A、B、C 就是我们的目标点,我们绘制的路径就是 从A到B再到C,然后控制点如下

  • a1 、 b1 点是 A B 的控制点
  • b2 、c2 点是 B C 的控制点

那么我们要绘制出如上图中的效果,代码如下

class CurvePainter1 extends CustomPainter {
  ///实际的绘画发生在这里
  @override
  void paint(Canvas canvas, Size size) {
    ///创建画笔
    var paint = Paint();
    ///设置画笔的颜色
    paint.color = Colors.blue;
    ///创建路径
    var path = Path();

    ///A点 设置初始绘制点
    path.moveTo(0, 55);
    /// 绘制到 B点(100,0)
    path.cubicTo(0, 25, 48, 0, 100, 0);
    /// 绘制到 C点(214, 55)
    path.cubicTo(166, 0, 214, 25, 214, 55);
    ///绘制 Path
    canvas.drawPath(path, paint);
  }

  ///你的绘画依赖于一个变量并且该变量发生了变化,那么你在这里返回true,
  ///这样Flutter就知道它必须调用paint方法来重绘你的绘画。否则,在此处返回false表示您不需要重绘
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

在 flutter 中,通过 path 的 cubicTo 函数来实现三阶贝塞尔曲线

void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) 点(x1,y1)、(x2,y2) 为控制点 (x3,y3) 为目标点

参考链接


flutter绘图基础之三阶贝塞尔曲线cubicTo

Flutter绘制贝塞尔曲线 、折线 、柱状图,支持触摸

之前写过一篇Android原生绘制曲线图的博客,动画效果不要太丝滑,那么现在到了Flutter,该如何实现类似的效果呢?如果你熟悉Android的Canvas,那么恭喜你, 你将很快上手Flutter的Canvas绘制各种图形,因为实现方式基本上与Android是一模一样。

先看下要实现的基本效果:

Flutter中如果想要自定义绘制,那么你需要用到 CustomPaint 和 CustomPainter ; CustomPaint是Widget的子类,先来看下构造方法

const CustomPaint({
    Key key,
    this.painter,
    this.foregroundPainter,
    this.size = Size.zero,
    this.isComplex = false,
    this.willChange = false,
    Widget child,
  }) :super(key: key, child: child);

我们只需要关心三个参数,painterforegroundPainterchild , 这里需要说明一下,painter 是绘制的 backgroud 层,而child 是在backgroud之上绘制,foregroundPainter 是在 child 之上绘制,所以这里就有了个层级关系,这跟android里面的backgroudforeground是一个意思,那这两个painter的应用场景是什么呢?假如你只是单纯的想绘制一个图形,只用painter就可以了,但是如果你想给绘制区域添加一个背景(颜色,图片,等等),这时候如果使用 painter是会有问题的,painter的绘制会被child 层覆盖掉,此时你只需要将painter替换成foregroundPainter,然会颜色或者图片传递给child即可。

如果是Android绘制几何图形,应该是重写View的onLayout() 和 onDraw方法。但是Flutter实现绘制,必须继承CustomPainter并重写 paint(Canvas canvas, Size size)和 shouldRepaint (CustomPainter oldDelegate)方法 ,第一个参数canvas就是我们绘制的画布了(跟Android一模一样),paint第二个参数Size就是上面CustomPaint构造方法传入的size, 决定绘制区域的宽高信息

既然Size已经确定了,现在就定义下绘制区域的边界,一般我做类似的UI,都会定义一个最基本的padding, 一般取值为16 , 因为绘制的内容与坐标轴之间需要找到一个基准线,这样更容易绘制,而且调试边距也很灵活

double startX, endX, startY, endY;//定义绘制区域的边界
static const double basePadding = 16; //默认的边距
double fixedHeight, fixedWidth; //去除padding后曲线的真实宽高
bool isShowXyRuler; //是否显示xy刻度
List<ChatBean> chatBeans;//数据源
 
class ChatBean {
  String x;
  double y;
  int millisSeconds;
  Color color;
 
  ChatBean({@required this.x, @required this.y, this.millisSeconds, this.color});
}

然后在paint()方法中拿到Size,确定绘制区域的坐标

///计算边界
  void initBorder(Size size) {
    print('size - - > $size');
    this.size = size;
    startX = yNum > 0 ? basePadding * 2.5 : basePadding * 2; //预留出y轴刻度值所占的空间
    endX = size.width - basePadding * 2;
    startY = size.height - (isShowXyRuler ? basePadding * 3 : basePadding);
    endY = basePadding * 2;
    fixedHeight = startY - endY;
    fixedWidth = endX - startX;
    maxMin = calculateMaxMin(chatBeans);
  }

maxMin是定义存储曲线中最大值和最小值的

///计算极值 最大值,最小值
  List<double> calculateMaxMin(List<ChatBean> chatBeans) {
    if (chatBeans == null || chatBeans.length == 0) return [0, 0];
    double max = 0.0, min = 0.0;
    for (ChatBean bean in chatBeans) {
      if (max < bean.y) {
        max = bean.y;
      }
      if (min > bean.y) {
        min = bean.y;
      }
    }
    return [max, min];
  }

初始化画笔 .. 是dart中的独特语法,代表使用对象的返回值调用属性或方法

var paint = Paint()
      ..isAntiAlias = true//抗锯齿
      ..strokeWidth = 2
      ..strokeCap = StrokeCap.round//折线连接处圆滑处理
      ..color = xyColor
      ..style = PaintingStyle.stroke;//描边

绘制坐标轴,这里在确定好的边界基础上再次xy轴横向和纵向各自增加一倍的padding,不然显得太紧凑

canvas.drawLine(Offset(startX, startY),Offset(endX + basePadding, startY), paint); //x轴

canvas.drawLine(Offset(startX, startY),Offset(startX, endY - basePadding), paint); //y轴

绘制 X 轴刻度,定义为最多绘制7组数据 ,rulerWidth就是刻度的长度定义为8

int length = chatBeans.length > 7 ? 7 : chatBeans.length; //最多绘制7个
double DW = fixedWidth / (length - 1); //两个点之间的x方向距离
double DH = fixedHeight / (length - 1); //两个点之间的y方向距离
for (int i = 0; i < length; i++) {
     ///绘制x轴文本
     TextPainter(
            textAlign: TextAlign.center,
            ellipsis: '.',
            text: TextSpan(
                text: chatBeans[i].x,
                style: TextStyle(color: fontColor, fontSize: fontSize)),
            textDirection: TextDirection.ltr)
          ..layout(minWidth: 40, maxWidth: 40)
          ..paint(canvas, Offset(startX + DW * i - 20, startY + basePadding));
 
      ///x轴刻度
      canvas.drawLine(Offset(startX + DW * i, startY),Offset(startX + DW * i, startY - rulerWidth), paint);
   }

这里要说明一点,Flutter绘制文本,并不能像android那样调用canvas.drawText(), 而是通过TextPainter来渲染的,

构造TextPainter 你必须指定文字的方向 textDirection 和 宽度 layout ,最后调用paint方法,指定坐标进行绘制

绘制 Y 轴刻度,y轴的刻度数量并不需要跟随数据源的长度,只需要按照一定数量(yNum )平分y轴最大值即可

      int yLength = yNum + 1; //包含原点,所以 +1
      double dValue = maxMin[0] / yNum; //一段对应的值
      double dV = fixedHeight / yNum; //一段对应的高度
      for (int i = 0; i < yLength; i++) {
        ///绘制y轴文本,保留1位小数
        var yValue = (dValue * i).toStringAsFixed(isShowFloat ? 1 : 0);
        TextPainter(
            textAlign: TextAlign.center,
            ellipsis: '.',
            maxLines: 1,
            text: TextSpan(  
                text: '$yValue',
                style: TextStyle(color: fontColor, fontSize: fontSize)),
            textDirection: TextDirection.rtl)
          ..layout(minWidth: 40, maxWidth: 40)
          ..paint(canvas, Offset(startX - 40, startY - dV * i - fontSize / 2));
 
        ///y轴刻度
        canvas.drawLine(Offset(startX, startY - dV * (i)),Offset(startX + rulerWidth, startY - dV * (i)), paint);
      }

现在坐标轴和刻度已经绘制完成了,基本上与原生一致,只是代码方式有些区别,接下来的曲线也是一模一样的,绘制贝塞尔曲线其实也不难,主要是找到起点和两个坐标之间的辅助点, 贝塞尔曲线的原理可以参考这里

path.cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
path = Path();
double preX, preY, currentX, currentY;
int length = chatBeans.length > 7 ? 7 : chatBeans.length;
double W = fixedWidth / (length - 1); //两个点之间的x方向距离

遍历数据源的第一个元素时,需要做个判断,index=0时,需要将path move到此处

if (i == 0) {
     path.moveTo(startX, (startY - chatBeans[i].y / maxMin[0] * fixedHeight));
     continue;
   }

添加后面的坐标时,需要找辅助点

currentX = startX + W * i;
preX = startX + W * (i - 1);
 
preY = (startY - chatBeans[i - 1].y / maxMin[0] * fixedHeight);
currentY = (startY - chatBeans[i].y / maxMin[0] * fixedHeight);
 
path.cubicTo(
              (preX + currentX) / 2, preY,
              (preX + currentX) / 2, currentY,
              currentX, currentY
            );

如果是要画折线而非曲线,第一步还是path.moveTo ,折线不需要找辅助点,所以后续可以直接添加坐标,path.lineTo

最后将path绘制出来

canvas.drawPath(newPath, paint);

虽然曲线已经成功绘制,但是这样显得很枯燥,如果可以看到绘制过程那就会更加有趣味性,这时候就需要通过动画来更新曲线的path的长度了,一般Android中我会用ValueAnimator.ofFloat(start ,end ) 来开启一个动画 ,在Flutter中,动画也是非常简单实用

_controller = AnimationController(vsync: this, duration: widget.duration);
      Tween(begin: 0.0, end: widget.duration.inMilliseconds.toDouble())
          .animate(_controller)
            ..addStatusListener((status) {
              if (status == AnimationStatus.completed) {
                print('绘制完成');
              }
            })
            ..addListener(() {
              _value = _controller.value;//当前动画值
              setState(() {});
            });
      _controller.forward();

动画执行过程中,我们会及时获取到当前的动画进度 _value, 此时就需要一段完整的path跟随动画值等比绘制了,之前在Android中我们可以用 PathMeasure 来测量path ,然后根据动画进度不断地截取,就实现了像贪吃蛇一样的效果, 但是在Flutter中,我并没有找到PathMeasure 这个类,相反的,PathMeasure 在Flutter竟然是个私有的类 _PathMeasure ,经过一通百度 和 Google,也没有找到类似的案例。难道没有人给造轮子,就必须要停止我前进的步伐了嘛,不急,显然Path这个类里面有很多方法,就这样我走上了一条反复测试的不归路...

幸运的是,在翻阅了Google 官方Flutter Api 后,终于找到了突破口

哈哈,藏得还挺深呐,就是这个 PathMetrics 类,path.computeMetrics() 的返回值 ,是用来将path解析成矩阵的一个工具

var pathMetrics = path.computeMetrics(forceClosed: false);

有个参数 forceClosed , 表示是否要连接path的起始点 ,我们这里当然不要啦 ,computeMetrics方法返回的是PathMetrics对象,调用 toList (),可以获取到 多个path组成的 List<PathMetric> ; 集合中的每个元素代表一段path的矩阵 , 奇怪,为什么是多个path 呢 ???

当时我也是懵着猜测的,历史总是惊人的相似,被我给猜对了,不晓得你们有没有发现,Path有个方法可以添加多个Path ,

path.addPath(path, offset);

当我每调用一次 addPath() 或者 moveTo(),lsit . length就增加1,所以上面提到的多个path的集合 就不难理解了 ,因为我们这里只有一个path,所以我们的 list 中只有一个元素 , 元素中包含一段path, 现在我们获取到了描述path的矩阵PathMetric

PathMetric.length 就是这段path的长度了,唉,为了找到你 ,我容易吗 !

另外还有个关键的方法,可以将pathMetric按照给定的位置区间截取,最后返回这段path, 这就跟Android中的PathMeasure.getSegment()是一样

extractPath(double start, double end,{ bool startWithMoveTo:true }) → Path
给定起始和停止距离,返回中间段。

现在是时候将前面获取到的当前动画值 value 用起来了,找到当前path的length乘以value即是当前path的最新长度

var pathMetrics = path.computeMetrics(forceClosed: true);
var list = pathMetrics.toList();
var length = value * list.length.toInt();
Path newPath = new Path();
for (int i = 0; i < length; i++) {
     var extractPath = list[i].extractPath(0, list[i].length * value, startWithMoveTo: true);
      newPath.addPath(extractPath, Offset(0, 0));
    }
 canvas.drawPath(newPath, paint);

走到这里,好像跨过了山和大海,得了,困死了,睡了、睡了...

现在曲线和折线都已经绘制完成了,不过刚开始的demo里还有个渐变色的部分没有完成,貌似有了渐变色以后,显得不那么单调了,其实,我们绘图所用到的Paint还有一个属性shader,可以绘制线条或区域的渐变色,LinearGradient可实现线性渐变的效果,默认为从左到右绘制,你可以通过begin和end属性自定义绘制的方向,我们这里需要指定为从上至下,并且颜色类型为数组的形式,所以你可以传入多个颜色值来绘制

var shader = LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              tileMode: TileMode.clamp,
              colors: shaderColors)
          .createShader(Rect.fromLTRB(startX, endY, startX, startY));

值得注意的是,通过 createShader的方式创建shader,你需要指定绘制区域的边界,我们这里要实现的是从上至下,所以就以y轴为基准,指定从上至下的绘制方向

既然是绘制渐变色,所以画笔的样式必须设置为填充状态

Paint shadowPaint = new Paint();
      shadowPaint
        ..shader = shader
        ..isAntiAlias = true
        ..style = PaintingStyle.fill;

另外,渐变色的区域我们是通过path来指定上面的边界的,所以我们还需要指定path下面部分的起点和终点,这样形成一个闭环,才能确定出完整的区域

///从path的最后一个点连接起始点,形成一个闭环
      shadowPath
        ..lineTo(startX + fixedWidth * value, startY)
        ..lineTo(startX, startY)
        ..close();
      canvas..drawPath(shadowPath, shadowPaint);

至此,即可实现带有渐变色的曲线或者折线,也许你有个疑问,画折线为什么也要用path呢,不是可以直接drawLine吗 ?机智如我,添加到path以后,可以更方便的绘制,添加动画也很方便

另附上最终的实现效果,至于触摸操作就不打算阐述了,可以参考以下代码

代码已发布到 Dart社区 https://pub.dev/flutter/packages?q=flutter_chart

GitHub仓库链接 https://github.com/good-good-study/flutter_chart

参考链接


Sqflite数据存储模型&无必要使用Isolate操作数据库

Sqflite存储模型如下:

依据上图Sqflite插件的DB存储模型会有2个等待队列,一个是Flutter层同步执行队列,一个是Native层的线程执行队列

  • Android实现机制是HandlerThread,底层使用同一个静态变量,因此Query/Save读写在会同一线程队列中,不同数据库的操作也需要在同一个线程队列中排队。具体参考代码 SqflitePlugin.java

  • 其iOS的实现是通过 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),...),把任务调度到系统的后台线程排队顺序执行,原理类似于HandlerThread。只是这样会造成一个问题,如果其他功能也使用系统队列,会造成不必要的延迟。iOS定义一个数据库专用线程会更好一些。具体参考代码 SqflitePlugin.m

我们在系统层调用 Sqlite 的时候,需要注意 Sqlite 调用的耗时阻塞问题,一般都是在子线程中操作数据库,而由于 Sqflite 系统层后台线程队列的存在,在 Flutter 层,其实没必要使用 isolate 对耗时操作进行规避,这点与原生开发不同。

注意

如果确实需要在 isolate 中执行 Sqflite 相关的调用,一般建议使用 FlutterIsolate 创建 isolate ,主要原因还是 Sqlite 相关的调用都是 Platform Channel 的调用。默认情况下,在 isolate 是不允许调用这些函数的。

FlutterIsolate allows creation of an Isolate in flutter that is able to use flutter plugins. It creates the necessary platform specific bits (FlutterBackgroundView on android & FlutterEngine on iOS) to enable the platform channels to work inside an isolate.

参考链接


Dart语言构造函数中存在异步方法时的处理方法

dart语言构造函数中如果存在异步方法,编程时如果不注意特殊处理的话,很容易使代码出现未定义行为。例如下面的代码:

class MyComponent{
  MyComponent() {
    init();
  }
  init() async{
    var ret = await _init();
    print('$ret');
  }
 
   _init(){
    return Future.delayed(new Duration(seconds: 2), () {
      return "hello";
    });
  }
}
 
void main() {
  var c = new MyComponent();
  print("done");
}

本意是想先输出“hello“ 然后再输出”done",然而输出顺序刚好相反。原因在于init是异步方法,本应该在调用前使用await等待异步方法返回。然而在构造函数中是无法使用await的。那么该如何处理呢?下面是一种方法,使用工厂函数产生类的对象。而工厂函数是异步的,需要配合await使用。虽然能够解决异步构造的问题,但总觉得不够优雅。

import 'dart:async';
import 'dart:io';
 
class MyComponent{
  /// Private constructor
  MyComponent._create() {
    print("_create() (private constructor)");
 
    // Do most of your initialization here, that's what a constructor is for
    //...
  }
 
  _complexAsyncInit(){
    return Future.delayed(new Duration(seconds: 2), () {
      return "hello";
    });
  }
 
  /// Public factory
  static Future<MyComponent> create() async {
    print("create() (public factory)");
 
    // Call the private constructor
    var component = MyComponent._create();
 
    // Do initialization that requires async
    var ret = await component._complexAsyncInit();
    print("async return value: $ret");
 
    // Return the fully initialized object
    return component;
  }
}
 
void main() async {
  var c = await MyComponent.create();
  print("done");
}

输出:

I/flutter (14941): create() (public factory)
I/flutter (14941): _create() (private constructor)
I/flutter (14941): async return value: hello
I/flutter (14941): done

参考链接


Flutter(able) 的单例模式

一个类只允许创建一个实例,那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

作为最简单的一种设计模式之一,对于单例本身的概念,大家一看就能明白,但在某些情况下也很容易使用不恰当。相比其他语言,Dart 和 Flutter 中的单例模式也不尽相同,本篇文章我们就一起探究看看它在 Dart 和 Flutter 中的应用。

继续阅读Flutter(able) 的单例模式