flutter解析多层嵌套的Json数据(Dio)_s-010101的博客-CSDN博客

mikel阅读(773)

来源: flutter解析多层嵌套的Json数据(Dio)_s-010101的博客-CSDN博客

flutter解析简单的Json数据在前面flutter网络请求(Dio)中已经讲过,这里解析的是多层嵌套的Json数据

这里我们要解析的数据接口来自玩安卓网站https://www.wanAndroid.com/article/list/0/json

由图可以看出,这里的数据是Map嵌套Map再嵌套List

建立数据模型
数据模型类的建立我们可以利用网上的一些工具进行实现,这里给出两个
https://autocode.icu/jsontodart
https://javiercbk.github.io/json_to_dart/

把Json语句复制在左边,点击CONVERT TO DART即可在右边生成相应的模型类

使用
这里网络请求依然是使用的Dio,依赖添加等操作不在进行描述直接上代码

_getDatas() async {
var api = ‘https://www.wanAndroid.com/article/list/0/json’;
var result = await Dio().get(api);
var list = AutoGenerate.fromJson(result.data);
print(result);
print(result.runtimeType);
print(result.data);
print(result.data is Map);
print(list is AutoGenerate);
List itemList = list.data.datas;
itemList.forEach((value) {
print(value.link);
});
}

运行可知var result = await Dio().get(api);获取到的result是一个Response<dynamic>类型,result.data是一个Map类型(代表Json嵌套数据最外层的Map),var list = AutoGenerate.fromJson(result.data)得到的是一个AutoGenerate的对象,List itemList = list.data.datas得到最内层的Datas对象的集合,然后在通过遍历集合对象得到每个对象属性的值
————————————————
版权声明:本文为CSDN博主「s-010101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_46527751/article/details/123318500

Flutter入门进阶之旅(十二)Flutter 数据存储 - 简书

mikel阅读(716)

来源: Flutter入门进阶之旅(十二)Flutter 数据存储 – 简书

关于Flutter中的数据存储

相信做过原生Android开发的读者对数据存储并不陌生,在原生Android中我们会把一些轻量级的数据(如用户信息、APP配置信息等)写入SharedPreferences做存储,把需要长期存储的数据写入本地文件或者SQLite3,当然Flutter中也同样用一套完整的本地数据存储体系,下面我们就一直来了解下上述提到的这3中本地存储方式在Flutter中使用。

1.SharedPreferences

在Flutter中本身并没有内置SharedPreferences存储,但是官方给我们提供了第三方的组件来实现这一存储方式。我们可以通过pubspec.yaml文件引入,关于pubspec.yaml的使用我们在Flutter入门进阶之旅(五)Image Widget,这一章节提到过,只不过在Image使用中我们引入的是assets文件依赖。

如下我们在dependencies节点下引入SharedPreferences的依赖,读者在pubspec.yaml引入依赖时一定要注意代码缩进格式,否则在在执行flutter packages get时很可能会报错


dependencies:
  flutter:
    sdk: flutter
    
  # 添加sharedPreference依赖
  shared_preferences: ^0.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
    
  # 引入本地资源图片
  assets:
     - images/a.png
     - images/aaa.png

然后命令行执行flutter packages get把远程依赖同步到本地,在此笔者写文章的时候sharedPreference的最新版本是0.5.0,读者可自行去https://pub.dartlang.org/flutter上获取最新版本,同时也可以在上面找到其他需要引入的资源依赖包。

笔者的话

啰里啰嗦的准备工作总算是讲完了,主要是今天的课程涉及到了包依赖管理,可能对于有些初学者有点懵,所以我就借助sharedPreference把依赖引入废话扯了一大通,如果读者已经掌握了上述操作,可跳过准备工作直接到下面的部分。

继续上面的内容,我们先来体验一下sharedPreference,贴个图大家放松一下。

sharedPreference

从上图中我们看到我们使用sharedPreference做了简单存储跟获取的操作,其实sharedPreference好像也就这么点左右,不是存就是取。读者在自行操作时一定不要忘记导入sharedPreference的包

import 'package:shared_preferences/shared_preferences.dart';

存数据

跟原生Android一样,Flutter中操作sp也是通过key-value的方式存取数据

/**
   * 利用SharedPreferences存储数据
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }

SharedPreferences中为我们提供了String、bool、Double、Int、StringList数据类型的存取。

SharedPreferences

 

取数据

/**
   * 获取存在SharedPreferences中的数据
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }

上述操作逻辑中我们通过_textFieldController获取在TextField中的值,在按下存储按钮的同时我们把数据写入sp中,当按下获取值的时候我们通过setState把从sp中获取的值同步更新到下面的Text上显示。

完整代码:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';
  final STORAGE_KEY = 'storage_key';

  /**
   * 利用SharedPreferences存储数据
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }

  /**
   * 获取存在SharedPreferences中的数据
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        children: <Widget>[
          Text("shared_preferences存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.pink,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.lightGreen,
          ),
          Text('shared_preferences存储的值为  $_storageString'),


        ],
      ),
    );
  }
}

2.文件存储

虽然我们今天内容是Flutter的数据存储,尴尬的是Flutter本身都没有内置提到的这三种存储方式,不过好在官方给我们提供了三方的支持库,不知道后续的Flutter版本中会不会对此做改进。操作文件同样我们也需要像SharedPreferences一样,需要在pubspec.yaml引入。在 Flutter 里实现文件读写,需要使用 path_provider 和 dart 的 io 模块。path_provider 负责查找 iOS/Android 的目录文件,IO 模块负责对文件进行读写

  # 添加文件依赖
  path_provider: ^0.5.0

笔者在此引入的最新版本是0.5.0,读者可自行去https://pub.dartlang.org/flutter上获取最新版本。

由于整个操作演示逻辑跟SharedPreferences一样,我就不详细讲解文件存储中关于存取数据的具体操作了,稍微我贴上源代码,读者自行查阅代码对比即可,关于文件存储的三个获取文件路径的方法我这里说明一下,做过原生Android开发的读者可能对此不陌生,但是ios或者初学者可能并不了解这个概念,所以我想提出来说明一下。

在path_provider中有三个获取文件路径的方法:

  • getTemporaryDirectory()//获取应用缓存目录,等同IOS的NSTemporaryDirectory()和Android的getCacheDir() 方法
  • getApplicationDocumentsDirectory()获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
  • getExternalStorageDirectory()//这个是存储卡,仅仅在Android平台可以使用

来看下操作文件的效果图

文件存储

 

借用了SharedPreferences存储的逻辑,只是把存储的代码放在了file.text中,代码里有详尽的注释,我就不多做解释说明了,读者可自行尝试对比跟SharedPreferences的差别

样例代码

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用文件存储数据
   */
  saveString() async {
    final file = await getFile('file.text');
    //写入字符串
    file.writeAsString(_textFieldController.value.text.toString());
  }

  /**
   * 获取存在文件中的数据
   */
  Future getString() async {
    final file = await getFile('file.text');
    var filePath  = file.path;
    setState(() {
      file.readAsString().then((String value) {
        _storageString = value +'\n文件存储路径:'+filePath;
      });
    });
  }

  /**
   * 初始化文件路径
   */
  Future<File> getFile(String fileName) async {
    //获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
    final fileDirectory = await getApplicationDocumentsDirectory();

    //获取存储路径
    final filePath = fileDirectory.path;

    //或者file对象(操作文件记得导入import 'dart:io')
    return new File(filePath + "/"+fileName);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("文件存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.deepOrange,
          ),
          Text('从文件存储中获取的值为  $_storageString'),
        ],
      ),
    );
  }
}

3.Sqflite

在Flutter中的数据库叫Sqflite跟原生安卓的SQLite叫法不一样。我们来看下Sqflite官方对它的解释说明:

SQLite plugin for Flutter. Supports both iOS and Android.

 Support transactions and batches
 Automatic version managment during open
 Helpers for insert/query/update/delete queries
 DB operation executed in a background thread on iOS and Android

通过上面的描述,我们了解到Sqflite是一个同时支持Android跟Ios平台的数据库,并且支持标准的CURD操作,下面我们还是用上面操作文件跟sp的代码逻辑是一块体验一下Sqflite

同样需要引入依赖:

  #添加Sqflite依赖
  sqflite: ^1.0.0

模拟场景:

利用Sqflite创建一张user表,其中user表中id设置为主键id,且为自增长,name字段为text类型,用户按下存储按钮后,把TextFile输入框里的内容插入到user表中,当按下获取按钮时,取出数据库中最后一条数据显示在下方Text上,并且显示出当前数据库中一共有多少条数据,以及数据库的存储路径。

效果图

Sqflite

上述描述样式代码 

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:sqflite/sqflite.dart';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用Sqflite数据库存储数据
   */
  saveString() async {
    final db = await getDataBase('my_db.db');
    //写入字符串
    db.transaction((trx) {
      trx.rawInsert(
          'INSERT INTO user(name) VALUES("${_textFieldController.value.text.toString()}")');
    });
  }

  /**
   * 获取存在Sqflite数据库中的数据
   */
  Future getString() async {
    final db = await getDataBase('my_db.db');
    var dbPath = db.path;
    setState(() {
      db.rawQuery('SELECT * FROM user').then((List<Map> lists) {
        print('----------------$lists');
        var listSize = lists.length;
        //获取数据库中的最后一条数据
        _storageString = lists[listSize - 1]['name'] +
            "\n现在数据库中一共有${listSize}条数据" +
            "\n数据库的存储路径为${dbPath}";
      });
    });
  }

  /**
   * 初始化数据库存储路径
   */
  Future<Database> getDataBase(String dbName) async {
    //获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
    final fileDirectory = await getApplicationDocumentsDirectory();

    //获取存储路径
    final dbPath = fileDirectory.path;

    //构建数据库对象
    Database database = await openDatabase(dbPath + "/" + dbName, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
    });

    return database;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Sqflite数据库存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.deepOrange,
          ),
          Text('从Sqflite数据库中获取的值为  $_storageString'),
        ],
      ),
    );
  }
}

至此,关于Flutter的本地存储相关的内容就全部讲解完了,在本文章中,我为了清晰代码结构跟业务逻辑,复用的都是同一个存储业务逻辑跟UI便于大家结合代码做对比,读者可结合代码自行对比三种存储方式的细节差别。

作者:谢栋
链接:https://www.jianshu.com/p/1fb8b61a85d8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

升级了flutter版本后,运行flutter后提示错误 Because every version of flutter from sdk depends on characters 1.2.1 which doesn't match any versions, flutter from sdk is forbidden.

mikel阅读(828)

升级了flutter版本后,运行flutter后提示错误

Because every version of flutter from sdk depends on characters 1.2.1 which doesn’t match any versions, flutter from sdk is forbidden.

解决方法:

增加了:

dependency_overrides:

  characters: ">=1.0.0 <=1.2.1"
相关文档:
https://www.cnblogs.com/mengqd/p/13928830.html
依赖覆盖
想象如下场景:项目依赖一个库(比如 path_provider)的版本为 1.6.22,而另一个依赖库也依赖这个 path_provider, 但版本为 0.5.0,那么我最终到底依赖哪个版本,此时执行 flutter pub get 则会出现如下错误:
Running "flutter pub get" in flutter_app...                     

Because every version of flutter_plugin from path depends on path_provider ^0.5.0 and flutter_app depends on path_provider ^1.6.22, flutter_plugin from path is forbidden.
So, because flutter_app depends on flutter_plugin from path, version solving failed.
pub get failed (1; So, because flutter_app depends on flutter_plugin from path, version solving failed.)
Process finished with exit code 1
 
此时要解决这个冲突,可以添加 dependency_overrides:
dependency_overrides:
  path_provider: ^1.6.22
添加此属性后,所有 path_provider 插件都会使用同一个最新版本,使用此字段执行 flutter pub get 则会出现如下警告:
/Users/mengqingdong/project/flutter/bin/flutter --no-color pub get
Running "flutter pub get" in flutter_app...                     

Warning: You are using these overridden dependencies:
! path_provider 1.6.22
Running "flutter pub get" in flutter_app...                         0.5s
Process finished with exit code 0
 

【Flutter 实战】pubspec.yaml 配置文件详解 - 老孟Flutter - 博客园

mikel阅读(663)

来源: 【Flutter 实战】pubspec.yaml 配置文件详解 – 老孟Flutter – 博客园

老孟导读pubspec.yaml 文件是 Flutter 中非常重要的配置文件,下面就让我们看看里面各个配置的含义。

pubspec.yaml 是 Flutter 项目的配置文件,类似于 Android 中的 Gradle 配置文件,下面我们就看看 pubspec.yaml 中各个属性的配置。

创建一个新的项目(Flutter Application),pubspec.yaml 位于根目录,如图:

项目中默认配置,去掉注释部分,剩下如下:

name: flutter_app
description: A new Flutter application.

publish_to: 'none' 

version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

下面我们一个一个的详细介绍下。

name

此属性表示包名(package name),此属性是非常重要的,引入其他文件时需要使用此包名:

import 'package:flutter_app/home_page.dart';

如果你修改包名为 ,那么相应的引入也需要修改:

import 'package:flutter_app_demo/home_page.dart';

如果你创建了一个 Flutter 插件并发布到 pub.dev,那么此属性将会作为标题显示,同时其他人引用也需要使用此属性。

description

description 属性是一个可选配置属性,是对当前项目的介绍。如果作为插件发布到 pub.dev 上,此值显示在如下位置:

version

此属性应用程序的版本和内部版本号,格式为 x.x.x+x,例如:1.0.0+1,这个版本号称为 语义版本号(semantic versioning ),semantic versioning 相关知识请参考此处

版本号 + 前面到部分,叫做 version number,由 2 个小点隔开,后面的部分叫做 build number

Android 中 version number 对应 versionName,build number 对应 versionCode,在 Android/build.gradle 下有相关配置,

早期的版本 build.gradle 中 versionName 和 versionCode 是直接写死的数字,如下:

此时修改版本号只能通过修改 build.gradle 。而现在可以直接通过 pubspec.yaml 进行修改。

如果是插件,那么用户可以通过此版本号指定使用哪个版本,

path_provider: ^1.6.22

版本的指定有多种形式:

不指定或者 any

path_provider:

path_provider: any

此种格式默认加载 最新的版本,但强烈不推荐使用此方式,因为版本的变化会导致接口发生变化,项目出现编译异常。

x.y.z

明确指定版本

path_provider: 1.6.22

指定依赖的版本。

<=x.y.z 或者<x.y.z

小于或者小于等于此版本的包

path_provider: <=1.6.22

path_provider: <1.6.22

>=a.b.c <x.y.z

指定版本的区间

path_provider: '>=1.0.0 <1.6.22'

^x.y.z

此方式为最常见的方式,也是推荐的方式。

此方式表示大版本不变,小版本使用最新的版本,例如^1.6.22 相当于‘>=1.6.22 <2.0.0’`

path_provider: ^1.6.22

author homepage issue_tracker repository

这四种属性在 Flutter Application 项目中默认是没有的,正常项目中也无需这几个属性,当我们开发插件并发布到 pub 时需要这几个属性。

当我们创建一个插件时,默认配置:

issue_tracker 和 repository 我们可以手动创建。这四个属性说明:

  • author:作者,填写自己的署名
  • homepage:主页。
  • issue_tracker:issue,一般写当前插件源代码的Github issue 地址。
  • repository:一般写当前插件源代码的Github地址。

这些属性会显示在 pub.dev 主页上:

Environment

Environment 属性下添加 Flutter 和 Dart 版本控制。

environment:
  sdk: ">=2.7.0 <3.0.0"

上面的版本规定此应用或库只能在高于或等于2.7.0,低于3.0.0的Dart SDK版本上运行。

我们也可以手动添加 Flutter 版本:

environment:
  sdk: ">=2.7.0 <3.0.0"
  flutter: "1.22.0"

也可以通过此属性使用实验性质的版本:

environment:
  sdk: ">=2.11.0-213.0.dev <2.12.0"

dependencies 和 dev_dependencies

dependencies 和 dev_dependencies 下包含应用程序所依赖的包,dependencies 和 dev_dependencies 就像其名字一样,dependencies 下的所有依赖会编译到项目中,而 dev_dependencies 仅仅是运行期间的包,比如自动生成代码的库。

我们可以通过四种方式依赖其包:

  • 依赖 pub.dev 上的第三方库
  • 依赖本地库
  • 依赖 git repository
  • 依赖我们自己的 pub仓库

依赖 pub.dev 上的第三方库

依赖 pub.dev 上的第三方库是最常用的一种方式

dependencies:
  path_provider: ^1.6.22

依赖本地库

如果你在本地创建了一个模块,依赖本地的库:

dependencies:
  flutter_package:
    path: ../flutter_package

依赖 git repository

依赖 Github 上的一个插件:

dependencies:
  bloc:
    git:
      url: https://github.com/felangel/bloc.git
      ref: bloc_fixes_issue_110
      path: packages/bloc
  • url:github 地址
  • ref:表示git引用,可以是 commit hash, tag 或者 branch
  • path:如果 git 仓库中有多个软件包,则可以使用此属性指定软件包

依赖我们自己的 pub 仓库。

一般大公司都会搭建自己的 pub 仓库,引用自己仓库的方式:

dependencies:
  bloc: 
    hosted:
      name: bloc
      url: http://your-package-server.com
    version: ^6.0.0

依赖覆盖

想象如下场景:项目依赖一个库(比如 path_provider)的版本为 1.6.22,而另一个依赖库也依赖这个 path_provider, 但版本为 0.5.0,那么我最终到底依赖哪个版本,此时执行 flutter pub get 则会出现如下错误:

Running "flutter pub get" in flutter_app...                     

Because every version of flutter_plugin from path depends on path_provider ^0.5.0 and flutter_app depends on path_provider ^1.6.22, flutter_plugin from path is forbidden.
So, because flutter_app depends on flutter_plugin from path, version solving failed.
pub get failed (1; So, because flutter_app depends on flutter_plugin from path, version solving failed.)
Process finished with exit code 1

此时要解决这个冲突,可以添加 dependency_overrides

dependency_overrides:
  path_provider: ^1.6.22

添加此属性后,所有 path_provider 插件都会使用同一个最新版本,使用此字段执行 flutter pub get 则会出现如下警告:

/Users/mengqingdong/project/flutter/bin/flutter --no-color pub get
Running "flutter pub get" in flutter_app...                     

Warning: You are using these overridden dependencies:
! path_provider 1.6.22
Running "flutter pub get" in flutter_app...                         0.5s
Process finished with exit code 0

Flutter

Flutter 下面的配置都是 Flutter 的相关配置。

uses-material-design

flutter:
  uses-material-design: true

确保您的应用程序中包含Material Icons字体,以便您可以使用material Icons类中的图标。

assets

assets 是对当前资源的配置,比如 图片、字体等。

配置本地图片,使用Image.asset() 加载。

assets:
  - images/a_dot_burr.jpeg
  - images/a_dot_ham.jpeg

配置字体:

fonts:
  - family: Schyler
    fonts:
      - asset: fonts/Schyler-Regular.ttf
      - asset: fonts/Schyler-Italic.ttf
        style: italic
  - family: Trajan Pro
    fonts:
      - asset: fonts/TrajanPro.ttf
      - asset: fonts/TrajanPro_Bold.ttf
        weight: 700

plugin

plugin 配置只存在与插件项目中,package 和 pluginClass 一般是不需要修改的,

flutter:
  plugin:
    platforms:
      android:
        package: com.flutter.app_market
        pluginClass: AppMarketPlugin
      ios:
        pluginClass: AppMarketPlugin

此配置正常情况下不需要修改,当需要添加新平台适配时,直接添加:

flutter:
  plugin:
    platforms:
      android:
        package: com.flutter.app_market
        pluginClass: AppMarketPlugin
      ios:
        pluginClass: AppMarketPlugin
      macos:
        default_package: app_market_macos
      web:
        default_package: app_market_web
        

pubspec.yaml 包含应用程序和依赖的软件包,规定Dart和Flutter SDK的版本约束,管理依赖关系并设置Flutter特定的配置。更详细的信息可以转到pubspec的官方文档查看

flutter version solving failed. - 简书

mikel阅读(570)

来源: flutter version solving failed. – 简书

记录一次运行flutter时碰到的问题已经解决办法,希望对碰到类似问题的伙伴有所帮助。

1. 使用flutter run运行flutter的hello_world的example遇到了依赖版本解析问题(Because hello_world depends on flutter_test any from sdk which depends on test_api 0.2.1, test_api 0.2.1 is required.So, because hello_world depends on test_api 0.2.2, version solving failed.),具体如下错误如下图,报错内容只说了依赖有问题,但是没有提供解决方案,只能自行查找。

2. 遂google一下,在flutter的github issue发现有人建议使用flutter doctor -v检查一下flutter的安装是否有问题,在跑flutter doctor -v之后没有发现影响的问题。

3. 继续看,发现使用flutter run -v可以打印运行时的log,有了清晰的log,问题的解决方案就显现了出来。

4. 按照提示执行flutter packages get安装完成后再次运行flutter run可以正常运行。

5. 虽然程序run起来了,但是由于adb版本问题,hot reload功能无法正常使用,(adb did not report forwarded port)。这个通过升级adb即可解决,flutter github的issue也有提到。

小结:

flutter尽管已经发布了1.0版(我使用的是1.1.8)但是有些细节方面的工作还是有点欠缺,需要使用者更熟悉flutter才更好的驾驭。刚运行flutter就遇到不能google快速解决问题确实还是有些挫折的,社区还需要大家一起多多贡献慢慢成熟。

遇到问题:

1.flutter doctor -v

2. 在运行的命令后加-v,输出log定位问题(比如 flutter run -v)

作者:emmonsthehound
链接:https://www.jianshu.com/p/3059c7c13355
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解决flutter 依赖版本冲突的n种方法_飞仔12345的博客-CSDN博客_flutter 版本冲突

mikel阅读(661)

来源: 解决flutter 依赖版本冲突的n种方法_飞仔12345的博客-CSDN博客_flutter 版本冲突

背景
接到一个需求,需要展示一个环状图和一个柱状图,去pub上逛了一圈,选择了功能比较全的MPFlutterChart 库。引入项目时,发现MPFlutterChart 和国际化包flutter_localizations依赖不同的intl版本。为了能使用MPFlutterChart 库,尝试了以下几种解决冲突的方法。
网上查找方法时,看到了这篇文章:https://www.cnblogs.com/pjl43/p/9943882.html,以下方案一到方案三都是这篇文章了的。

方案一,手动尝试历史版本
在pub上查看库的历史版本,往前尝试,找到一个不冲突的版本。方法太笨,不建议采用。

方案二,版本设置为any
设置版本号为any,则AS会自己查找不冲突的版本,引入依赖。相当于机器帮做了手动尝试的过程。如果能找到不冲突的版本,则可以使用方案三来优化。

方案三,从pubspec.lock找到具体版本号
方案二生效后,我们在pubspec.yaml里面看不到使用的具体版本号。这是可以去pubspec.lock中查看项目所依赖的具体版本,可把pubspec.yaml中的any替换成pubspec.lock的具体版本号。(该文章说是在pubspec.lock.yaml,不知是笔误还是因为flutter版本与我的不一样)。

以下是方案二无用情况下的处理

方案四,引入本地库,手动改版本
上面方案二很好,遗憾的是,对我的情况无用。我项目flutter_localizations依赖的intl是0.15.8,而MPFlutterChart 依赖的是0.16.0;而且,MPFlutterChart 是从第一个版本就是依赖0.16.0的,即使设置为any,也找不到依赖0.15.8的版本号。
pub上找不到对应版本,那可以从github上clone代码,修改依赖版本号后,通过本地引入的方式引入。

缺点:
1.下次升级MPFlutterChart 版本,需要重新colone代码,修改依赖版本,不易维护
2.降低intl版本,可能会出现一些在0.16.0版本已被修复的问题,甚至有的新API调不到导致无法运行。

方案五,修改flutter_localizations的依赖版本
降低版本存在风险,那能不能给flutter_localizations升级版本呢?想去pub上查看flutter_localizations依赖的intl版本号,发现无法搜索到。再看下pubspec.yaml文件里的引入方式,它和其他库的引入明显不同。

dependencies:
flutter:
sdk: flutter
#国际化支持
flutter_localizations:
sdk: flutter

看这引入方式,猜测它应该来自flutter的sdk。打开.packages文件,发现它的路径是%flutter_sdk%packages/flutter_localizations/lib/。打开里面的pubspec.yaml,果然找到了依赖的intl版本是0.15.8,将其改为0.16.0,从新获取依赖,可正常运行。

缺点:
1.此修改只能改自己本地的代码,如果别人拉取项目,而他电脑的依赖版本没修改,依然无法运行,不易于团队协作。
2.修改sdk包的依赖版本,可能会导致API不兼容问题。

方案六,升级flutter_sdk版本
单独修改sdk的依赖版本可能会出现问题,但是整体升级sdk则安全得多,因为每个sdk发布前必然经过大量的测试,尤其是稳定版的sdk。
由于MPFlutterChart 一开始就支持intl 0.16.0,因此猜测它发布的时候,flutter的sdk已经支持到0.16.0了。查看了MPFlutterChart 第一个版本的发布时间,19年9月。查看那段时间的flutter代码,发现果然已经支持0.16.0。所以,如果flutter_升级到1.12.13及以上的稳定版本,就不再有这个问题。

最后
这个问题的原因是我的flutter_sdk版本过低,没跟上官方最新版。但是如果以后还遇到其他第三方库和sdk库版本冲突,可以参考上面思路。

————————————————
版权声明:本文为CSDN博主「飞仔12345」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luozaifei1997/article/details/104895969

高效理解 FreeSql WhereDynamicFilter,深入了解设计初衷[.NET ORM] - FreeSql - 博客园

mikel阅读(461)

来源: 高效理解 FreeSql WhereDynamicFilter,深入了解设计初衷[.NET ORM] – FreeSql – 博客园

💻 前言

最近几乎每天40度,越热越不想面对电脑,还好开源项目都比较稳定没那么多待解决问题,趁着暑假带着女儿学习游泳已略有小成。游泳好处太多了,建议有孩子的都去学学,我是在岸边指导大约一周左右就学会了,目前可游200米。

FreeSQL 有一个用户很迷的功能 WhereDynamicFilter 动态表格查询,本文讲解它的设计初衷,如何高效理解,从此不再迷惑。

小时候学习编程,老师经常教导我们,程序 = 数据结构 + 算法,今天就以我自身的认知讲解该功能的完整设计过程,其中包含数据结构和算法。

自从编写了.NET ORM 开源项目,每次写文章必有黑子,上次发表的文章评论:

《[开源精品] .NET Redis Client 又多了一个选择》

@China-Mr-zhong说:写的项目 都很烂 怎么好意思 说是精品

@FreeSQL回复:

黑子,快回去先学下做人,黑完一次又一次,你代码写得真好。

原来他也是 ORM 作者,开源作品:Fast.Framework

而我上次发表的文章并非 ORM 啊,面对这样的社区圈子,有时候真的无语,这段被黑的内容是事后编辑的,因为现在这篇文章发表没多久已经有黑子开始点反对了。


🌳 ORM概念

Object Relational Mapping 是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

FreeSql .NET ORM 支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及还有说不出来的运行平台,因为代码绿色无依赖,支持新平台非常简单。目前单元测试数量:8500+,Nuget下载数量:900K+。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

FreeSql 使用最宽松的开源协议 MIT https://github.com/dotnetcore/FreeSql ,完全可以商用,文档齐全,甚至拿去卖钱也可以。

FreeSql 主要优势在于易用性上,基本是开箱即用,在不同数据库之间切换兼容性比较好,整体的功能特性如下:

  • 支持 CodeFirst 对比结构变化迁移;
  • 支持 DbFirst 从数据库导入实体类;
  • 支持 丰富的表达式函数,自定义解析;
  • 支持 批量添加、批量更新、BulkCopy;
  • 支持 导航属性,贪婪加载、延时加载、级联保存;
  • 支持 读写分离、分表分库,租户设计;
  • 支持 MySql/SQLServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/Clickhouse/MsAccess Ado.net 实现包,以及 Odbc 的专门实现包;

8500+个单元测试作为基调,支持10多数数据库,我们提供了通用Odbc理论上支持所有数据库,目前已知有群友使用 FreeSql 操作华为高斯、mycat、tidb 等数据库。安装时只需要选择对应的数据库实现包:

dotnet add packages FreeSql.Provider.MySql


🎣 需求矛盾

虽然 ORM 有理论定义支撑,但实际开发过程中,难免遇到动态查询的需求,常见的有后台管理系统用户自定义过滤查询,如:

鉴于实际与理论的矛盾,导致很多非常实用的功能类库让一些人诟病,指这是 SqlHelper,并非 ORM,在此不便理论,功过自在人心。


📰 数据结构

数据结构的定义,决定了功能的使用深度,这块也参考了一些竟品类似的功能,实际在 .NET ORM 领域很少有完美并简单的现实,要么使用太复杂,要么不支持深层级,甚至有 SQL 注入漏洞。

类似的功能其实市面产品应用挺广泛,几乎已经形成了一套成熟的产品规则。如果不是亲身经历过类似产品,是很难定义出完美的数据结构的,作为一个公众开源项目,API 一旦确定再改是非常痛苦的决定,用户升级不兼容的情况不仅会影响 FreeSql 口碑,还会让使用者进退两难,到底要不要升级?好在 FreeSql 从 2018 年最初理念保持至今,关于前后破坏性升级几乎没有。

最终根据对 SQL 逻辑表达式的理解,加上参考 JAVA 一个知名的后台开源框架,取长补短确定了最终数据结构。

说这么多无外乎三个重点:

1、自己不熟悉的,多方面学习,接纳更成熟的方案;

2、自己要是没想好怎么做,多观察再做;

3、多思考用户场景;

我们需要考虑的场景有以下几种:

1、WHERE id = 1

{
    "Field": "id",
    "Operator": "Equals",
    "Value": 1
}

2、WHERE id = 1 AND id = 2

{
    "Logic": "And",
    "Filters":
    [
        {
            "Field": "id",
            "Operator": "Equals",
            "Value": 1
        },
        {
            "Field": "id",
            "Operator": "Equals",
            "Value": 2
        }
    ]
}

3、WHERE id IN (1,2)

{
    "Field": "id",
    "Operator": "Contains",
    "Value": [1,2] //或者 "1,2"
}

4、WHERE id = 1 OR id = 2

{
    "Logic": "Or",
    "Filters":
    [
        {
            "Field": "id",
            "Operator": "Equals",
            "Value": 1
        },
        {
            "Field": "id",
            "Operator": "Equals",
            "Value": 2
        }
    ]
}

5、WHERE id = 1 AND (id = 2 OR id = 3)

注意优先级,它不是 id = 1 AND id = 2 OR id = 3

{
    "Logic": "And",
    "Filters":
    [
        {
            "Field": "id",
            "Operator": "Equals",
            "Value": 1
        },
        {
            "Logic": "Or",
            "Filters":
            [
                {
                    "Field": "id",
                    "Operator": "Equals",
                    "Value": 2
                },
                {
                    "Field": "id",
                    "Operator": "Equals",
                    "Value": 3
                }
            ]
        }
    ]
}

第5个例子最特别,这也是为什么 WhereDynamicFilter 数据结构定义成树型的主要原因。

关于 Operator 我们需要以下使用场景:

  • Contains/StartsWith/EndsWith/NotContains/NotStartsWith/NotEndsWith:包含/不包含,like ‘%xx%’,或者 like ‘xx%’,或者 like ‘%xx’
  • Equal/NotEqual:等于/不等于
  • GreaterThan/GreaterThanOrEqual:大于/大于等于
  • LessThan/LessThanOrEqual:小于/小于等于
  • Range:范围查询
  • DateRange:日期范围,有特殊处理 value[1] + 1
  • Any/NotAny:是否符合 value 中任何一项(直白的说是 SQL IN)
  • Custom:自定义解析

最终完整的 C# 数据结构类定义如下:

/// <summary>
/// 动态过滤条件
/// </summary>
[Serializable]
public class DynamicFilterInfo
{
    /// <summary>
    /// 属性名:Name
    /// 导航属性:Parent.Name
    /// 多表:b.Name
    /// </summary>
    public string Field { get; set; }
    /// <summary>
    /// 操作符
    /// </summary>
    public DynamicFilterOperator Operator { get; set; }
    /// <summary>
    /// 值
    /// </summary>
    public object Value { get; set; }

    /// <summary>
    /// Filters 下的逻辑运算符
    /// </summary>
    public DynamicFilterLogic Logic { get; set; }
    /// <summary>
    /// 子过滤条件,它与当前的逻辑关系是 And
    /// 注意:当前 Field 可以留空
    /// </summary>
    public List<DynamicFilterInfo> Filters { get; set; }
}

public enum DynamicFilterLogic { And, Or }
public enum DynamicFilterOperator
{
    /// <summary>
    /// like
    /// </summary>
    Contains,
    StartsWith,
    EndsWith,
    NotContains,
    NotStartsWith,
    NotEndsWith,

    /// <summary>
    /// =
    /// Equal/Equals/Eq 效果相同
    /// </summary>
    Equal,
    /// <summary>
    /// =
    /// Equal/Equals/Eq 效果相同
    /// </summary>
    Equals,
    /// <summary>
    /// =
    /// Equal/Equals/Eq 效果相同
    /// </summary>
    Eq,
    /// <summary>
    /// <>
    /// </summary>
    NotEqual,

    /// <summary>
    /// >
    /// </summary>
    GreaterThan,
    /// <summary>
    /// >=
    /// </summary>
    GreaterThanOrEqual,
    /// <summary>
    /// <
    /// </summary>
    LessThan,
    /// <summary>
    /// <=
    /// </summary>
    LessThanOrEqual,

    /// <summary>
    /// >= and <
    /// 此时 Value 的值格式为逗号分割:value1,value2 或者数组
    /// </summary>
    Range,

    /// <summary>
    /// >= and <
    /// 此时 Value 的值格式为逗号分割:date1,date2 或者数组
    /// 这是专门为日期范围查询定制的操作符,它会处理 date2 + 1,比如:
    /// 当 date2 选择的是 2020-05-30,那查询的时候是 < 2020-05-31
    /// 当 date2 选择的是 2020-05,那查询的时候是 < 2020-06
    /// 当 date2 选择的是 2020,那查询的时候是 < 2021
    /// 当 date2 选择的是 2020-05-30 12,那查询的时候是 < 2020-05-30 13
    /// 当 date2 选择的是 2020-05-30 12:30,那查询的时候是 < 2020-05-30 12:31
    /// 并且 date2 只支持以上 5 种格式 (date1 没有限制)
    /// </summary>
    DateRange,

    /// <summary>
    /// in (1,2,3)
    /// 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组
    /// </summary>
    Any,
    /// <summary>
    /// not in (1,2,3)
    /// 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组
    /// </summary>
    NotAny,

    /// <summary>
    /// 自定义解析,此时 Field 为反射信息,Value 为静态方法的参数(string)
    /// 示范:{ Operator: "Custom", Field: "RawSql webapp1.DynamicFilterCustom,webapp1", Value: "(id,name) in ((1,'k'),(2,'m'))" }
    /// 注意:使用者自己承担【注入风险】
    /// 静态方法定义示范:
    /// namespace webapp1
    /// {
    /// public class DynamicFilterCustom
    /// {
    /// [DynamicFilterCustom]
    /// public static string RawSql(object sender, string value) => value;
    /// }
    /// }
    /// </summary>
    Custom
}

/// <summary>
/// 授权 DynamicFilter 支持 Custom 自定义解析
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class DynamicFilterCustomAttribute : Attribute { }

📡 安全考虑

由于 ISelect.WhereDynamicFilter 方法实现动态过滤条件(与前端交互),在 SQL 注入安全防御这块一定要进行到底,主要思考如下:

1、Field 只允许传递 C# 实体属性名(不支持使用数据库字段名,甚至直接使用 SQL 内容片段);

2、Operator 只允许规定的枚举操作类型;

3、Value 必须根据 Operator 进行强制类型检查,比如 “1,2” + Any 检索出来的数据是 int[] { 1,2 };

4、Operator Custom 类型支持用户自行扩展,可现实更自由的查询;


⚡ 算法

如果把数据结构定义成灵魂,那算法就是驱壳,实现 WhereDynamicFilter 的核心算法是递归树结构。

感兴趣的朋友可以直接去源码查看实现:https://github.com/dotnetcore/FreeSql


🌌 难理解

WhereDynamicFilter 功能2020年上线到现在,我个人都觉得其实蛮难理解的,更不要提很多使用者反馈。主要原因是数据结构为树结构,通常80%的人只是简单的一层 AND/OR 需求,他们很少会遇到深层级的自定义查询。

但是作为功能性 ORM 类库,应该满足更多适用范围,而不是妥协为求简单来实现功能。

其实便于理解也不难,只要掌握以下方法:

1、Logic 是设置 Filters 数组下的逻辑关系(这很重要,一定要理解正确)

为了解决 WHERE id = 1 AND (id = 2 OR id = 3) 优先级问题,Filters 更像一对括号

{
    "Logic": "And",
    "Filters":
    [
        { "Field": "id", "Operator": "Equals", "Value": 1 },
        {
            "Logic": "Or",
            "Filters":
            [
                { "Field": "id", "Operator": "Equals", "Value": 2 },
                { "Field": "id", "Operator": "Equals", "Value": 3 }
            ]
        }
    ]
}

2、Field/Operator/Value 与 Logic/Filters 不要同时设置(避免理解困难)

3、删除 JSON 中不必要的内容

这个病不好治,因为强类型对象产生的默认 json 内容,即使无用的属性也序列化了。

{
    "Field": null,
    "Operator": "Equals",
    "Value": null,
    "Logic": "Or",
    "Filters":
    [
        {
            "Field": "Name-1",
            "Operator": "Equals",
            "Value": "ye-01",
            "Logic": "And",
            "Fitlers": null
        },
        {
            "Field": "Name-1",
            "Operator": "Equals",
            "Value": "ye-02",
            "Logic": "And",
            "Fitlers": null
        }
    ]
}

以上类型改成如下,是不是更好理解?

{
    "Logic": "Or",
    "Filters":
    [
        {
            "Field": "Name-1",
            "Operator": "Equals",
            "Value": "ye-01"
        },
        {
            "Field": "Name-1",
            "Operator": "Equals",
            "Value": "ye-02"
        }
    ]
}

🚀 最终功能

一个任意定制的高级查询功能预览如下:

前端只需要按要求组装好 DynamicFilterInfo 对应的 JSON 数据内容,后台就可轻易完成高级过滤查询,有多轻易呢?

var dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(jsonText);

var list = fsql.Select<T>().WhereDynamicFilter(dyfilter).ToList();

⛳ 结束语

希望这篇文章能帮助大家从 WhereDynamicFilter 的设计初衷,轻松理解并熟练掌握它,为企业的项目研发贡献力量。

开源地址:https://github.com/dotnetcore/FreeSql


作者是什么人?

作者是一个入行 18年的老批,他目前写的.net 开源项目有:

开源项目 描述 开源地址 开源协议
ImCore 架构最简单,扩展性最强的聊天系统架构 https://github.com/2881099/im 最宽松的 MIT 协议,可商用
FreeRedis 最简单的 RediscClient https://github.com/2881099/FreeRedis 最宽松的 MIT 协议,可商用
csredis https://github.com/2881099/csredis 最宽松的 MIT 协议,可商用
FightLandlord 斗地主单机或网络版 https://github.com/2881099/FightLandlord 最宽松的 MIT 协议,学习用途
IdleScheduler 定时任务 https://github.com/2881099/IdleBus/tree/master/IdleScheduler 最宽松的 MIT 协议,可商用
IdleBus 空闲容器 https://github.com/2881099/IdleBus 最宽松的 MIT 协议,可商用
FreeSql 国产最好用的 ORM https://github.com/dotnetcore/FreeSql 最宽松的 MIT 协议,可商用
FreeSql.Cloud 分布式事务tcc/saga https://github.com/2881099/FreeSql.Cloud 最宽松的 MIT 协议,可商用
FreeSql.AdminLTE 低代码后台管理项目生成 https://github.com/2881099/FreeSql.AdminLTE 最宽松的 MIT 协议,可商用
FreeSql.DynamicProxy 动态代理 https://github.com/2881099/FreeSql.DynamicProxy 最宽松的 MIT 协议,学习用途

需要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。

QQ群:4336577(已满)、8578575(在线)、52508226(在线)

flutter 表单Form使用示例 - 简书

mikel阅读(750)

来源: flutter 表单Form使用示例 – 简书

介绍

flutter提供一套表单校验框架Form,可以通过Form框架一步校验所有表单,非常方便,比较常用的用法是Form+TextFormField。

Form

class Form extends StatefulWidget {
  final Widget child;
  ...
  const Form({
    Key key,
    @required this.child,
    this.autovalidate = false,
    this.onWillPop,
    this.onChanged,
  })
  ...

Form继承StatefulWidget,有一个widget 类型的child参数,证明Form是一个容器。
Form里还有一个validate方法,如下

bool validate() {
  ... 
 }

一般通过GlobalKey来访问Form中validate方法,Form的validate方法用来校验所有Form里的FormField表单,validate方法返回值是bool类型,返回true表示所有表单校验成功;返回false表示有校验失败的表单;

TextFormField

class TextFormField extends FormField<String> {
...
 TextFormField({
   ...
    FormFieldValidator<String> validator,
   ...
  })

TextFormField继承FormField,FormField后面源码分析会讲,所有Form可统一校验的表单都必须继承FormField,可以通过FormField自定义各种各样可校验表单,TextFormField只是FormField自定义表单中的一种。
表单校验必须实现的方法为validator,定义如下:

typedef FormFieldValidator<T> = String Function(T value);

每个表单的校验规则都在validator里实现,通过返回值来判断是否校验成功。

  • 返回null,表示表单校验成功
  • 返回非null,表示表单校验失败,其实返回的非null还可以用来表示校验失败的错误提示信息,TextFormField内部实现就将返回的非null当成错误信息提示出来。

使用步骤

1、用Form包在所有需要校验的表单最外层,如下:

  Form(
            key:xxx,
            child:xxx

2、将GlobalKey传给Form,用于调用Form里方法,如下:

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  Form(
            key:_formKey,
            child:xxx

3、将TextFormField传给Form容器,如下:

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  Form(
            key:_formKey,
            child:
                    ...
                    TextFormField
                    ...

4、实现对应TextFormField的校验规则 ,如下:

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  Form(
            key:_formKey,
            child:
                    ...
                    TextFormField( 
                        validator: (value) {
                              ...
                              return xxx; 
                        },
                    )
                    ...

5、最后调用Form校验所有表单方法validate,如下:

_formKey.currentState.validate()

使用示例

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('系统表单'),
          backgroundColor: Colors.blue,
        ),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatefulWidget {
  HomeContent({Key key}) : super(key: key);

  _HomeContentState createState() => _HomeContentState();
}

class _HomeContentState extends State<HomeContent> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  String phoneNum;

  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          Form(
            key: _formKey,
            child: Column(
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(
                    hintText: '手机号码',
                  ),
                  validator: (value) {
                    RegExp reg = new RegExp(r'^\d{11}$');
                    if (!reg.hasMatch(value)) {
                      return '请输入11位手机号码';
                    }
                    return null;
                  },
                ),
                TextFormField(
                  decoration: InputDecoration(
                    hintText: '用户名',
                  ),
                  validator: (value) {
                    if (value.isEmpty) {
                      return '请输入用户名';
                    }
                    return null;
                  },
                ),
              ],
            ),
          ),
          SizedBox(height: 10),
          Row(
            children: <Widget>[
              Expanded(
                child: RaisedButton(
                  child: Text('校验'),
                  onPressed: () {
                    //一步校验全部表单
                    if (_formKey.currentState.validate()) {
                      Scaffold.of(context).showSnackBar(SnackBar(
                        content: Text('校验成功...'),
                      ));
                    }
                  },
                ),
              )
            ],
          ),
        ],
      ),
    );
  }
}

使用效果

效果.gif

作者:HawkFlying
链接:https://www.jianshu.com/p/b5bc338b260b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Flutter Error: Cannot run with sound null safety, because the following dependencies don‘t support_吴庆森的博客-CSDN博客

mikel阅读(645)

来源: Flutter Error: Cannot run with sound null safety, because the following dependencies don‘t support_吴庆森的博客-CSDN博客

版本运行的时候可以有下面的报错:

说明包不安全,第一种简单方法是,打开终端进入项目所在的文件夹,输入命令:

flutter run –no-sound-null-safety
这样需要每次都用命令打包,另一种方法,找到 main.dart ,然后右键点击,找到 Edit ‘main.dart’… 如下图所示:

找到第二行 flutter additional run args ,添加:–no-sound-null-safety,点击 OK 即可,如下图:

这样再运行就不会报错了。
————————————————
版权声明:本文为CSDN博主「吴庆森」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wuqingsen1/article/details/121030584