November 19, 2022

Flutter 使用报告

Flutter 介绍

FlutterGoogle 推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart 语言开发 App,一套代码同时运行在 iOSAndroid 平台。Flutter 提供了丰富的组件、接口,开发者可以很快地为 Flutter 添加 Native 扩展。

在此记录些常用资料:

文档文章
官方网站Flutter 最佳实践和编码准则
官方英文文档iPad 大屏 & Flutter 多引擎适配之路
官方中文文档详解 android:elevation 的使用
官方 Package 站Flutter elevation 属性名称的含义
Flutter 实战Android 中 elevation 的设置方法
模式匹配
工具
Json to Dart图片缓存
BasedWidget
QWeatherIcons

使用 index.dart 文件简化导入

规定文件目录结构如下:

lib
│  index.dart
│  main.dart

├─pages
│      home_page.dart
│      index.dart
│      setting_page.dart

└─widgets
    │  index.dart

    ├─dialog
    │      dialog_confirm_widget.dart
    │      dialog_from_json_widget.dart
    │      index.dart

    └─diary
       │  index.dart

       ├─list
       │      diary_list_item_place_holder_widget.dart
       │      diary_list_item_widget.dart
       │      diary_list_view_widget.dart
       │      index.dart

       └─page
              diary_page_item_share_button_widget.dart
              diary_page_item_widget.dart
              diary_page_view_widget.dart
              index.dart

则在 lib/index.dartexport 所有子文件夹下的 index.dart 文件:

export 'main.dart';
export 'pages/index.dart';
export 'widgets/index.dart';

pages/index.dartexport 所有该文件夹下的 *.dart 文件:

export 'home_page.dart';
export 'setting_page.dart';

在所有 *.dart 文件(包括 main.dart 文件)内尽绝大可能 import 'package:<项目名>/index.dart'; 即可简化导入。

同时当要引入外部包时,只要在 lib/index.dart 文件内导入即可,当然,有时会出现不同包之间的类名冲突,此时在需要使用到该包的地方单独 import 即可,或者使用 hide/show 语法限制,具体例子如下:

/// 各路由下的 index.dart
export 'main.dart';
export 'pages/index.dart';
export 'widgets/index.dart';
 
/// flutter 相关
/// [RefreshCallback]`export 'package:flutter/material.dart'` 冲突,两者近似
export 'package:flutter/cupertino.dart' hide RefreshCallback;
export 'package:flutter/services.dart'
    show DeviceOrientation, SystemChrome; // 设备服务
/// [Badge]`export 'package:badges/badges.dart'; // 小红点提示` 冲突,我想用外部包
export 'package:flutter/material.dart' hide Badge;
export 'package:flutter/gestures.dart';
 
/// dart 相关
export 'dart:async' show Timer, StreamSubscription;
export 'dart:convert';
export 'dart:io';
export 'dart:ui' show ImageFilter;
 
/// 外部包相关
export 'package:badges/badges.dart'; // 小红点提示
/// [Interval]`package:flutter/src/animation/curves.dart` 冲突,两者结构完全不同,但外部包里的这个用不到
export 'package:dart_date/dart_date.dart' hide Interval; // 日期工具
/// [Text]`export 'package:flutter/material.dart` 冲突,两者结构完全不同,但外部包里的这个用不到
export 'package:flutter_quill/flutter_quill.dart' hide Text; // 富文本

版本号构建问题

Flutter 使用 android/app/build.gradle 来打包 apk, 且其引入了 flutter.gradle 并指向 flutter.groovyD:\Flutter\packages\flutter_tools\gradle\src\main\groovy\flutter.groovy

约在 flutter.groovy993

if (shouldSplitPerAbi()) {
    variant.outputs.each { output ->
        def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))
        if (abiVersionCode != null) {
            output.versionCodeOverride =
                abiVersionCode * 1000 + variant.versionCode
        }
    }
}

我们知道 flutter 将判断是否使用了 'split-per-abi' 命令, 是则在 ABI_VERSION 选择一个版本 *1000 再加上构建号

官方解释见 此链接

我们只需修改 ABI_VERSION map 如下

private static final Map ABI_VERSION = [
    (ARCH_ARM32)        : 0,
    (ARCH_ARM64)        : 0,
    (ARCH_X86)          : 0,
    (ARCH_X86_64)       : 0,
]

注意若进行了 Flutter 版本更新,应重新修改该 flutter.groovy 文件

vivo 系手机无法调试 Flutter 程序

vivo 系列手机升至 Origin3 后发现调试 Flutter 应用卡在启动页,并且没有任何报错,详见 github issue,简化自 此链接

答案是 vivo 系统发大病连日志都隐藏,我们需要提供 IMEI 1 码至 vivo 官方进行授权

  1. 拨号盘输入 *#06# 复制 IMEI 1
  2. 添加企业 QQ 号 3002261823(或通过 官方网站 联系)
  3. 提交相关问题和信息,要求一键授权自己的手机
  4. 等待授权成功后拨号盘输入 *#*#112#*#*“右上角按钮”->“更多”->“一键授权” 即可

AlertDialog content 传入 ListView 时在调试模式下报错

这是个怪问题,release 版本正常运行,解决方法如下:

AlertDialog(
  title: (...),
  content: SizedBox(
    width: double.minPositive, // 可选 double.maxFinite 但建议为 double.minPositive,
    child: ListView(
      shrinkWrap: true,
      children: (...),
    ),
  ),
  contentPadding: (...),
  actions: (...),
);

ard 语法

详见 此页面

代码规范

  1. 尽可能使用 '' 而不是 "" 来表示字符串

  2. 尽量不使用 StatefulWidget / ConsumerStatefulWidget 而是 StatelessWidget / ConsumerWidget

  3. 尽量不要使用 const MyWidget({Key? key}) : super(key: key); 而是 const MyWidget({super.key});,对于其他变量也是如此

  4. StatelessWidget / ConsumerWidget 组件,其结构如下

    class MyWidget extends StatelessWidget {
        const MyWidget({super.key});
     
        void _myFunction() {
            (...)
        }
     
        @override
        Widget build(BuildContext context) {
            return Container();
        }
     
        Future<void> _myFuture() {
            (...)
        }
    }
    class MyWidget extends ConsumerWidget {
        const MyWidget({super.key});
     
        void _myFunction() {
            (...)
        }
     
        @override
        Widget build(BuildContext context, WidgetRef ref) {
            return Container();
        }
     
        Future<void> _myFuture() {
            (...)
        }
    }
  5. StatefulWidget / ConsumerStatefulWidget 组件,其结构如下

    class MyWidget extends StatefulWidget {
        const MyWidget({super.key});
        (...)
     
        @override
        State<MyWidget> createState() => _MyWidgetState();
    }
     
    class _MyWidgetState extends State<MyWidget> {
        (...)
     
        @override
        void initState() {
            super.initSate();
            (...)
        }
     
        @override
        void dispose() {
            (...)
            super.dispose();
        }
     
        void _myFunction() {
            (...)
        }
     
        @override
        Widget build(BuildContext context) {
            return Container();
        }
     
        Future<void> _myFuture() {
            (...)
        }
    }
    class MyWidget extends ConsumerStatefulWidget {
        const MyWidget({super.key});
        (...)
     
        @override
        ConsumerState<MyWidget> createState() => _MyWidgetState();
    }
     
    class _MyWidgetState extends ConsumerState<MyWidget> {
        (...)
     
        @override
        void initState() {
            super.initSate();
            (...)
        }
     
        @override
        void dispose() {
            (...)
            super.dispose();
        }
     
        void _myFunction() {
            (...)
        }
     
        @override
        Widget build(BuildContext context) {
            return Container();
        }
     
        Future<void> _myFuture() {
            (...)
        }
    }

感想

  • Flutter 的使用非常简单,上手也快,非常有意思

  • 自己用 Flutter 写了很多项目,这里来个 Mercurius 日记软件的 仓库链接