dart/flutter 学习笔记
flutter项目迁移
flutter页面之间传值
调用代码: Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) { return GatewayPage(type: TTGatewayType.g2, wifi: wifiName); })); 传入页面: class GatewayPage extends StatefulWidget { GatewayPage({required this.type, this.wifi}) : super(); final String? wifi; final TTGatewayType type; @override _GatewayPageState createState() => _GatewayPageState(type, wifi); } class _GatewayPageState extends State<GatewayPage> { BuildContext? _context; String? _wifi; String? _wifiPassword; TTGatewayType? _type; _GatewayPageState(TTGatewayType type, String? wifi) { super.initState(); _wifi = wifi; _type = type; }
map变量迭代
edit[selectedCommand]['fields'].forEach((key, value) { PARAM[key] = enItem[key]; }); edit[selectedCommand]['fields'].forEach((key, value) => PARAM[key] = enItem[key]); PARAM..addAll(edit[selectedCommand]['fields'].map((key, value) => MapEntry(key, enItem[key])));
慎重使用退出清除函数dispose,重置变量后容易忘记
@override void dispose() { // todo 清除页面树,退出页面时执行 page = {}; super.dispose(); }
值拷贝
// 列表值拷贝 valuesByOriginalKey = List.from(values); // Map的addEntries方法进行值拷贝 Map<String, dynamic> copiedMap = Map.fromEntries(originalMap.entries);
类型转换
setState(() { api(context, title, 'GET', url).then((res) { settings = (res?[resultKey] as List<dynamic>).cast<Map<String, dynamic>>(); setState(() {}); // 在读取完值后调用setState更新UI }); }); child: FindDropdown( items: sMap['keys'].cast<String>(),
一、StreamBuildUtil
1、class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver { // TODO 绑定 StreamBuildUtil
2、WidgetsBinding.instance.addObserver(this); // TODO 初始化 StreamBuildUtil.instance.getStream
3、定义控件
child: StreamBuildUtil.instance.getStream("password").addObserver((password) { return Text( '$password', style: TextStyle( color: Colors.red, fontSize: 16.0, ), ); }, initialData: ''),
4、传值 StreamBuildUtil.instance.getStream("password").changeData(getFilterTitle());
函数参数传值
在 Dart 语言中,所有数据传递给函数时都是通过传递值。这意味着,当你把一个变量传递给函数时,你传递的是这个变量当前所持有的值。然而,基于数据类型的不同,这种 "传值" 的行为可能会给人 "传引用" 的错觉。
对于基本数据类型(如 int
、double
、bool
和 String
),这些是不可变的值,因此看起来就像是 "传值"。对于复合数据类型(如 List
、Map
和自定义的对象),虽然变量持有的是一个指向数据的引用,并且这个引用值被传递给函数,但从技术上讲,这仍然被视为 "传值",因为实际上传递的还是引用值本身,而不是对象。如果函数修改了传入的对象,那么外部的原始对象也会受到影响,因为它们共享同一个引用。
下面是一个示例,展示了传递不同类型数据给函数时的行为:
void main() { int myInt = 10; List<int> myIntList = [1, 2, 3]; // 传递基本数据类型的值 updateValue(myInt); print(myInt); // 输出: 10,因为基本数据类型在传递时本质上是拷贝值 // 传递复合数据类型的引用 updateList(myIntList); print(myIntList); // 输出: [4, 2, 3],因为引用类型在传递时是拷贝引用 } void updateValue(int value) { value = 20; // 这里只是修改了函数内部的局部变量 } void updateList(List<int> list) { list[0] = 4; // 这里修改了通过引用传递的列表的第一个元素 }
在上述代码中,函数 updateValue
尝试修改 myInt
变量,但这个修改仅限于函数内部的局部变量,外部的变量 myInt
不会被改变。另一方面,函数 updateList
修改了列表的第一个元素,由于 myIntList
是引用类型,所以这个修改也反映在了原始的列表对象上。
总结来说,不管是基本类型还是引用类型,在 Dart 中都是通过传递值来进行函数调用的。引用类型变量的值本质上是指向对象的引用本身,因此当这个引用值被传递给函数时,函数内对对象的操作会影响到原始对象。
在Flutter(以及Dart语言)中,double
类型同样没有 isEmpty
属性。正如我之前提及的,isEmpty
是一些集合类型和 String
类型的属性,它用来表示一个集合或字符串是否没有元素或字符。对于 double
这样的基本数值类型,它们总是包含一个数值,即使是 0.0
,也不能认为是“空的”。
如果你尝试对一个 double
类型的变量使用 isEmpty
属性,你会得到一个编译时错误,因为这个属性对于 double
类型并不存在。
如果你要检查一个 int、
double
是否未被赋值,你可以使用可空类型 double?
,然后检查它是否为 null
:
double? number;
// 检查number是否为null
bool isNull = number == null;
这样你可以判断一个 double
类型的变量是否未初始化或者没有分配值。注意,这与 "empty" 是不同的概念,因为数值类型本身没有 "empty" 的状态。
Aqueduct
如果你想了解Aqueduct,先瞥一眼它的Core Concepts。我简单地列一下要点:
资源视角:Aqueduct是Restful 的忠实拥趸。所有的http请求都被视为一个资源请求。缺省的http content type 是Json格式。这和传统的web框架(比如SpringMVC或者Django)比,是不是省心了很多。
路由/控制器:Aqueduct通过显式编程模式,将url映射做了简单明了的处理。整个应该的所有请求都通过Entry Point里的路由定义实现。一个路由对应一个URL路径。而控制器包括资源控制器和路由中间件。中间件实现请求过滤,比如安全(认证/授权),日志,异常处理。而资源控制器实现服务请求的业务实现代码。
数据绑定:请求和返回值的数据序列话,如Json的编码/解码,都可以通过Bind标签自动完成。这虽然是很多web框架的标配,但是通过语言的标注绑定,还是实现的非常优雅,值得一试。
ORM:不仅能够将Dart的model class直接映射成数据库的表,而且还提供的数据迁移的版本控制。通过“aqueduct db generate”和“aqueduct db upgrade”,aqueduct可以自动识别每个版本之间的数据模型的差异,并自动生产升级/降级的脚本。
OAuth2.0:Auqeduct原生支持Oauth2.0框架,当然不喜欢的话,也可以自己通过中间件实现。但是,如果采用OAuth的话,几乎是不写一行代码的,拥有认证和授权能力,是不是很赞?
文档化:Auqeduct原生支持Open API 3.0(前身是Swagger Doc)。敲一句:aqueduct document,就可以得到标准的html格式的Open API文档,是不是很爽?
转换为特定类型
Map<String, Command> map = commandList[index].cast<String, Command>();
完成异步函数A、B、C,才能完成开始做D
// 模拟异步函数A、B、C Future<void> asyncFunctionA() async { await Future.delayed(Duration(seconds: 1)); print('异步函数A完成'); } Future<void> asyncFunctionB() async { await Future.delayed(Duration(seconds: 2)); print('异步函数B完成'); } Future<void> asyncFunctionC() async { await Future.delayed(Duration(seconds: 3)); print('异步函数C完成'); } // 等待所有异步函数完成 Future<void> waitForAsyncFunctions() async { await Future.wait([asyncFunctionA(), asyncFunctionB(), asyncFunctionC()]); }
异步函数的同步初始化方法
_load() { currentTest = GetStorage().read('test') as String; print('object'); print(currentTest); setState(() {}); // 在读取完值后调用setState更新UI } @override void initState (){ super.initState(); Future.delayed(Duration.zero, () => setState(() { _load(); }));
异步函数的阐释
@override
void initState (){
super.initState();
// todo 异步函数初始化方式1,注意:_center() async。
// todo _center是一个异步函数,它返回一个Future。当你调用then方法时,你需要传递一个接受单个参数的函数,
// todo 这个参数是_center函数异步执行完成后返回的结果。但是,在你的代码中,你传递的是一个不接受任何参数的匿名函数。
// todo 为了修复这个问题,你需要确保传递给then的函数接受一个参数。
// todo 注意,我在.then((_) {中添加了一个下划线_作为参数,这是Dart中一个常见的约定,表示这个参数在函数体内不会被使用。
// todo 同时,我也将companysName.length > 0替换为companysName.isNotEmpty,这是Dart中检查列表是否非空的更简洁的方式。
center(context).then((_) { // todo 添加不需要使用的返回参数(下划线 _ ),即使你不打算在函数体内使用它 ***center
setState(() {
companysNameState = companyNameList.isNotEmpty ? companyNameList : companysNameState;
locksNameState = lockNameList.isNotEmpty ? lockNameList : locksNameState;
// todo ************************************初始化列表后选择列表第一个为当前***********************************
currentCompany = companysNameState.length > 0 ? companysNameState[0] : currentCompany;
currentLock = locksNameState.length > 0 ? locksNameState[0] : currentLock;
});
});
// todo 异步函数初始化方式2,注意:_center() async
//Future.delayed(Duration.zero, () => setState(() {
//_center();
//}));
}
类型转换:
values = (data?['lock_list'] as List<dynamic>).cast<Map<String, dynamic>>();
解决下拉列表选择后显示正确选项问题:非空同时非null
String? selectedLock = (currentLock == null || currentLock!.isEmpty) ? '' : currentLock;
String? selectedCompany = (currentCompany == null || currentCompany!.isEmpty) ? '' : currentCompany; // todo 解决显示选择
在flutter中空字符串('')和空值(null)是不同概念
异步函数初始化中使用的2种方式
onPressed: () { setState(() { /// todo 假设_getCompanys是一个异步函数,用于获取公司名称列表 _center().then(() { /// todo 确保在获取到数据后再更新companysNameState setState(() { companysNameState = companysName.length > 0 ? companysName : companysNameState; locksNameState = locksName.length > 0 ? locksName : locksNameState; }); }); }); },
flutter项目文件重点:
1、page文件:导航;
2、new、edit、fix文件:屏幕界面显示逻辑、变量定义、变量和界面元素即时刷新;
3、fn文件:界面相关的业务逻辑实现;
4、common文件:公用函数
数据即时更新和页面刷新:
1、要保证数据源更新后,控件立即更新,即数据源必须在在setState中更新;
2、在更新数据源时,注意要在异步函数执行完毕后进行。
这两段代码的主要区别在于"onFieldSubmitted"方法中是否调用了"setState"函数。
在第一段代码中,当用户在文本字段中提交内容时,"onFieldSubmitted"方法会调用"setState"函数,这将触发Flutter框架重新构建UI,以反映新的状态。在这种情况下,"TOKEN"的值被更新,并且如果"TOKEN"不为空,"labelTextCompany"的值将被清空,否则,它将被设置为'查找公司'。
在第二段代码中,"onFieldSubmitted"方法没有调用"setState"函数。这意味着即使"TOKEN"和"labelTextCompany"的值发生了变化,UI也不会立即更新以反映这些变化。只有当其他的"setState"被调用时,UI才会更新。
总的来说,如果你希望在用户提交文本字段内容后立即更新UI,你应该在"onFieldSubmitted"方法中调用"setState"函数。
flutter中数据传递的3种方式
1、通过全局变量赋值,用于一般情况;
2、需要实时更新的数据传递,例如收到mqtt时,同时保存到数据库的情况:
verificationCodeCtr.sink.add(iot1)
verificationCodeCtr.stream.listen((mobile) => isAuth(mobile));
3、处理完数据后,实时刷新到界面的情况
StreamBuildUtil.instance.getStream("text1").changeData('mqtt连接出错: $e');
StreamBuildUtil.instance.getStream("state").addObserver((state) {return Text("$now\n$state\n");
***通过控件中的setstate方法,改变最上层类的变量时,该变量赋值的控件属性会随之改变。***
flutter项目目录结构
对于最新权限修改、删除在 issue表 中有update_time字段标记,设备时间节点以update_time字段为准。
一、库
1、import 'dart:xxx'; 引入Dart标准库
2、import 'xxx/xxx.dart'; 引入绝对路径的Dart文件
3、import 'package:xxx/xxx.dart'; 引入Pub仓库中的第三方库
4、import 'package:项目名/xxx.dart'; 引入自定义的dart packages文件
5、例如有两个dart项目a和b ,在同级目录下
,b需要引用a中的dart文件,则在b的pubspec.yaml文件中引用a的包
在b的dart文件中要引用a的dart文件则在B的dart文件中引入需要引入的文件
import 'package:a/aaaaaa.dart';