1、jq –arg传递的变量select()没有硬编码值就不能工作吗?
1 | Product_Personnel_Array=$(cat ${Product_Personnel_FILE_PATH} | ${JQ_EXEC} -r '.[]') # -r 去除字符串引号 |
1 | 0、基础用法(验证通过✅) |
1 | jq '.test_group_1[] | select(.name == test_1-1).checksum = 1' $FILE_PATH |
1、jq –arg传递的变量select()没有硬编码值就不能工作吗?
1 | Product_Personnel_Array=$(cat ${Product_Personnel_FILE_PATH} | ${JQ_EXEC} -r '.[]') # -r 去除字符串引号 |
1 | 0、基础用法(验证通过✅) |
1 | jq '.test_group_1[] | select(.name == test_1-1).checksum = 1' $FILE_PATH |
使用三方库的时候,如果该库与原生有关。如webview插件、百度地图插件等。如果直接使用,不对工程设置的话,则会报trying to create a view with an unregistered type, unregistered view type:
解决方案:在info.plist加入
1 | <key>io.flutter.embedded_views_preview</key> |
原因:
启动的时候使用rootBundle.loadString 加载本地配置json,但是直接加载会报错:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Null check operator used on a null value
解决办法:加载本地资源以前增加一行代码:
WidgetsFlutterBinding.ensureInitialized(); //解决加载json错误
例子:
1 | void main() async { |
1)iOS权限问题可参照iOS定位SDK手动部署说明。
2)iOS头文件错误:


解决办法:Xcode-TARGETS-build settings-Allow Non-modular Includes In Famework Modules设置为YES。
Allow Non-modular Includes In Famework Modules
从页面进入弹窗(全局WebView)。弹窗和页面不在同一路由栈上,从而使得当页面返回的时候,需要自己判断返回到哪。
1 | 页面APage --> 页面BPage --> 弹窗CView --> 页面DPage |
必选处理的事:
①自定义返回按钮事件。
②传递进入时候的标记。
处理事项的主要代码
1 | // 进入页面 APage.swift |
fromRoutePath的传入与使用进入时候的标记赋值方案,主要有以下两种。
进入页面的代码处标记。缺点:每个需要的地方的都需要写相应的代码。
页面进行路由跳转的时候,将前一页的页面命名路由名传递给下一页。
1、添加页面跳转监听:进行对原本参数的添加,达到新值的传入目的。
2、跳转的目的页面将所有参数接收,待后续内部独立处理。
3、页面内部独立分解所有参数,并在返回时候充分使用。
1 | // 页面跳转的监听 |
| 序号 | 操作类型标识 | 操作类型描述 | 场景举例 | ||
|---|---|---|---|---|---|
| 1 | openPage | 页面跳转 | 发布内容按钮 | ||
| 2 | popupWindow | 弹出弹窗 | 查看物流按钮 | ||
| 3 | popupToast | 弹出Toast | 催一催按钮 | ||
路由操作,常与按钮点击结合在一起,所以我们顺便约定【点击按钮的属性规范】。
1 | // 页面跳转 |
1 | // 基类 |
路由解析
使用WebUrl解析的方法
1 | // 解析方法如下: |
解析方法详细内容如下:
1 | import 'dart:convert'; |
构造方法详细内容如下:
1 | import 'dart:convert'; |
背景:埋点为避免去从所有埋点里查找进入此页面前的上一个进入页面,希望直接知道此页面的上页面路径。
路由规范
| 字段 | 描述 | 示例 | |
|---|---|---|---|
| 1 | sourceFrom | 从哪个环境过来 (如h5跳转到app) |
appType/h5Type |
| 2 | |||
背景:从页面进入弹窗(全局WebView)。弹窗和页面不在同一路由栈上,从而使得当页面返回的时候,需要自己判断返回到哪。
详见:优雅的自定义返回
背景:一个基于WebView的Cocos2d游戏,因为启动加载引擎会有3-5秒的加载时长。为了更好的游戏体验,希望启动后重新进入不用重新加载。
常见:web游戏,极小,游戏页独立存在
特殊:web游戏可以调到app页时候
关键策略:
1、调研游戏内容多网页共享方案
2、建立游戏测试页,提供后续方案验证基础
3、开发方案demo,进行游戏内容多网页实验
4、如果方案有效,后续进行含业务的完整方案梳理;如果无效则进行其他方案调研
调研过程:
1、完成Flutter游戏测试页的建立,并通过尝试共享网页视图或控制器两种方式,进行游戏内容在多页面的共享测试。得出共享时候视图树会重绘,即网页会重新加载,从而无法达到整个跳转过程在同一路由栈下的正常跳转交互。
2、完成实现原生游戏测试页,并在原生上通过共享单例视图方式,进行游戏内容在多页面的共享测试。仍会出现在共享时候视图重绘操作。
3、后续预想方案
方案①游戏页面与其他网页一样常规的正常重新开辟。
优点:能使得整个跳转过程在同一路由栈下的正常跳转交互,享有系统的各种进出交互。
优化点:页面开辟时候,游戏加载慢。
方案②游戏使用唯一独立窗口,形如支付宝蚂蚁森林。需要从游戏内去app的其他页面时候,使用先缩小游戏窗口,到一个悬浮按钮,再弹出目标页面。
存在现象:从目标页再进游戏的时候需要从悬浮按钮进入。
路由解析详见 路由规范(含点击属性规范)
使用WebUrl解析的方法
1 | // 解析方法如下: |
解析方法详细内容如下:
1 | import 'dart:convert'; |
构造方法详细内容如下:
1 | import 'dart:convert'; |
1、home 是应用程序默认的路由小部件,如果指定了 home,那么 route 中就不能包含 /,home 会取代 /。
2、除非指定了 initialRoute(会先执行home,再执行initialRoute,在initialRoute页返回会回到home页),否则应用程序会首先显示 home 对应的小部件,如果 initialRoute 不能正常显示,也会显示 home。
使用场景:
若APP启动时需要根据是否登录来决定首先显示的页面,可在路由监听中判断 token,若未登录,则 initialRoute 设置登录页对应的别名,则会打开登录页。
典型错误信息:NoSuchMethodError: The method 'markNeedsBuild' was called on null.
这个错误常出现在异步任务(Future)处理,比如某个页面请求一个网络API数据,根据数据刷新 Widget State。
异步任务结束在页面被pop之后,但没有检查State 是否还是 mounted,继续调用 setState 就会出现这个错误。
一段很常见的获取网络数据的代码,调用 requestApi(),等待Future从中获取response,进而setState刷新 Widget:
1 | class AWidgetState extends State<AWidget> { |
response 的获取为async-await异步任务,完全有可能在AWidgetState被 dispose之后才等到返回,那时候和该State 绑定的 Element 已经不在了。故而在setState时需要容错。
setState之前检查是否 mounted1 | class AWidgetState extends State { |
这个mounted检查很重要,其实只要涉及到异步还有各种回调(callback),都不要忘了检查该值。
比如,在 FrameCallback里执行一个动画(AnimationController):
1 | @override |
AnimationController有可能随着 State 一起 dispose了,但是FrameCallback仍然会被执行,进而导致异常。
又比如,在动画监听的回调里搞点事:
1 | @override |
同样的在_handleAnimationTick被回调前,State 也有可能已经被dispose了。
如果你还不理解为什么,请仔细回味一下Event loop 还有复习一下 Dart 的线程模型。
典型错误信息:NoSuchMethodError: The method 'pop' was called on null.
常在 showDialog 后处理 dialog 的 pop() 出现。
在某个方法里获取网络数据,为了更好的提示用户,会先弹一个 loading 窗,之后再根据数据执行别的操作…
1 | // show loading dialog on request data |
出错的原因在于—— Android 原生的返回键:虽然代码指定了barrierDismissible: false,用户不可以点半透明区域关闭弹窗,但当用户点击返回键时,Flutter 引擎代码会调用 NavigationChannel.popRoute(),最终这个 loading dialog 甚至包括页面也被关掉,进而导致Navigator.of(context)返回的是null,因为该context已经被unmount,从一个已经凋零的树叶上是找不到它的根的,于是错误出现。
另外,代码里的Navigator.of(context) 所用的context也不是很正确,它其实是属于showDialog调用者的而非 dialog 所有,理论上应该用builder里传过来的context,沿着错误的树干虽然也能找到根,但实际上不是那么回事,特别是当你的APP里有Navigator嵌套时更应该注意。
首先,确保 Navigator.of(context) 的 context 是 dialog 的context;其次,检查 null,以应对被手动关闭的情况。
showDialog 时传入 GlobalKey,通过 GlobalKey去获取正确的context。
1 | GlobalKey key = GlobalKey(); |
key.currentContext 为null意为着该 dialog 已经被dispose,亦即已经从 WidgetTree 中unmount。
其实,类似的XXX.of(context)方法在 Flutter 代码里很常见,比如 MediaQuery.of(context)、Theme.of(context)、DefaultTextStyle.of(context),DefaultAssetBundle.of(context)等等,都要注意传入的context是来自正确节点的,否则会有惊喜在等你。
写 Flutter 代码时,脑海里一定要对context的树干脉络有清晰的认知,如果你还不是很理解context,可以看看 《深入理解BuildContext》 - Vadaski。
在获取ScrollController的position、offset,或者调用jumpTo()等方法时,常出现StateError错误。
错误信息:StateError Bad state: Too many elements,StateError Bad state: No element
在某个按钮点击后,通过ScrollController 控制ListView滚动到开头:
1 | final ScrollController _primaryScrollController = ScrollController(); |
先看ScrollController的源码:
1 | class ScrollController extends ChangeNotifier { |
很明显,ScrollController 的 offest 是从 position 中获得,而position 则是来自变量 _positions。
StateError错误,就是_positions.single 这一行抛出:
1 | abstract class Iterable<E> { |
那么问题来了,这个_positions 为什么忽而一滴不剩,忽而却嫌它给的太多了呢?ˊ_>ˋ
还是要回到 ScrollController 的源码里找找。
1 | class ScrollController extends ChangeNotifier { |
为什么没有数据(No element): ScrollController还没有 attach 一个 position。原因有两个:一个可能是还没被 mount 到树上(没有被Scrollable使用到);另外一个就是已经被 detach了。
为什么多了(Too many elements): ScrollController还没来得及 detach旧的 position,就又attach了一个新的。原因多半是因为ScrollController的用法不对,同一时间被多个 Scrollable关注到了。
针对 No element 错误,只需判断一下 _positions是不是空的就行了,即hasClients。
1 | final ScrollController _primaryScrollController = ScrollController(); |
针对 Too many elements 错误,确保ScrollController只会被一个 Scrollable绑定,别让它劈腿了,且被正确 dispose():
1 | class WidgetState extends State { |
Dart 这个语言可静可动,类型系统也独树一帜。万物都可以赋值null,就导致写惯了 Java 代码的同志们常常因为bool int double这种看起来是”primitive”的类型被null附体而头晕。
典型错误信息:
Failed assertion: boolean expression must not be nullNoSuchMethodError: The method '>' was called on null.NoSuchMethodError: The method '+' was called on null.NoSuchMethodError: The method '*' was called on null.这种错误,较常发生在使用服务端返回的数据model时。
1 | class StyleItem { |
StyleItem.fromJson() 对数据没有容错处理,应当认为 map 里的value都有可能是 null。
1 | class StyleItem { |
一定要习惯 Dart 的类型系统,什么都有可能是null,比如下面一段代码,你细品有几处可能报错:
1 | class Test { |
小提示,onDone()也可以是null >﹏<。
在和原生用 MethodChannel传数据时更要特别注意,小心驶得万年船。
典型错误信息:
type 'List<dynamic>' is not a subtype of type 'List<int>'type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, String>'常发生在给某个List、Map 变量赋值时。
这种错误,也较常发生在使用服务端返回的数据model时。
1 | class Model { |
jsonDecode()这个方法转换出来的map的泛型是Map<String, dynamic>,意为 value 可能是任何类型(dynamic),当 value 是容器类型时,它其实是List<dynamic>或者Map<dynamic, dynamic>等等。
而 Dart 的类型系统中,虽然dynamic可以代表所有类型,在赋值时,如果数据类型事实上匹配(运行时类型相等)是可以被自动转换,但泛型里 dynamic 是不可以自动转换的。可以认为 List<dynamic> 和 List<int>是两种运行时类型。
1 | class Model { |
ios中一个常见的交互是:点击顶部栏时,自动将当前的滚动区滚到顶部。在flutter中,大部分时候这件事是“自然完成”的,但是也有时候会遇到这个行为失效的情况。要解决这个问题首先自然是要看这个feature是如何实现的。
其实大部分都是Scaffold里面干的事:
Scaffold里有这样一段代码:
1 | switch (themeData.platform) { |
这里命名很舒服,可以直接看出来在干什么:如果是ios的话,那就给Scaffold加一个在状态栏上的点击区,点击的话就会触发一个函数,这个函数干的事情如下:
1 | final ScrollController _primaryScrollController = ScrollController(); |
也就是,Scaffold会提供一个默认的ScrollController,而点击顶部栏会使得这个controller滚到顶部,在ScrollView的build函数中则会取这个controller:
1 |
|
如果指定controller的话,就不会使用PrimaryScrollview,如果不指定的话,则在primary为true时使用PrimaryController,而默认情况下controller为null,primary为true,因此一个裸体的ListView是会相应屏幕点击的。
知道了原理,就很容易分析自己代码里出的问题是什么,常见的可能就是:
1、没加Scaffold,这个其实并不常见(自相矛盾草),不过可以检查一下,一般总是会有Scaffold的
2、真正常见的:指定了controller,如果自己创建了一个Controller丢给ScrollView,那必然是会失效的。但是使用controller又是一个很常见且重要的需求,怎么办呢?也很简单,就是不要自己创建新的ScrollController,而是直接取PrimaryScrollController.of(context)这个controller,对其进行自己要做的操作。
3、相对不太常见且需要分析具体代码的:多个Scaffold导致的冲突。
注意到其实flutter里的这个点击状态栏并不是真的点击了状态栏,而是点击了“Scaffold提供的位于状态栏的可点击区域”,也就是说,如果有多个Scaffold就会有多个这样的区域。实际情况是,只有最内部的Scaffold的状态栏会有响应,而如果ScrollView所处位置取到的和点击的Scaffold不一致,自然也就不会有滚动到顶部的feature
今天遇到ios点击状态栏无法回到顶部(原理在文章后)的问题。研究后发现,Scaffold组件虽然会自带这个功能。但使用时候,必须遵循指定规则才行。
我们以点击 TapStatusNormalPage 上的状态栏返回顶部来举例。
1 | // tap_status_normal_page.dart |
失效示例1:
1 | import 'package:flutter/material.dart'; |
这种情况,点击状态栏便不会回到顶部,我们需要保证的就是每个页面仅有一个Scaffold。
失效示例2:
虽然home里只有一个 Scaffold ,但app的builder的Widget生成,如果child再多余包Scaffold会导致点击状态栏无法回到顶部
1 | return ScreenUtilInit( |
点击页面上的某个按钮,让页面上的列表滚动到顶部(不需要自定义controller)。
1 | PrimaryScrollController.of(context).jumpTo(0); |
如果还要监听滚动
1 |
|
问题:
flutter_swiper:Another exception was thrown: ScrollController attached to multiple scroll views
翻译一下:引发了另一个异常:ScrollController连接到多个滚动视图。
原因:
Flutter Swiper是一个轮播图组件,内部包含一个Widget List,当这个Widget List数量大于1,就可能会有这种情况
解决方案:给Swiper加一个Key即可解决
1 | return Container( |
参考文章:

remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.
remote: Please see https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information.
意思就是你原先的密码凭证从2021年8月13日开始就不能用了,要使用个人访问令牌(personal access token),就是把你的密码替换成token。
1、登录github官网进入setting
2.页面拉到底 找到 Developer Setting
3.右侧菜单栏找到Personal access tokens 点击后 找到右上角的Generate new token

4、token的使用
在终端上cd到要项目中,然后执行git pull。

此时弹出
Username for ‘https://github.com‘: 此处输入你github 用户名
Password for ‘https://用户名@github.com’:把复制的token粘贴到此处(这里原先是输入密码的现在改成token)回车就好了

解决方式,终端输入如下命令:
git config –global http.proxy

解决:

创建的时候下面的权限和是否设置过期时间(我是吧所有权限都勾选了) 根据自己情况选择 最后创建完后 吧token复制下来

fatal: unable to access ‘https://github.com/dvlproad/001-UIKit-CQDemo-Flutter.git/‘: LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443

解决:终端输入
git config http.sslVerify “false”
git config –global http.sslVerify “false”
其他参考文章:访问GitHub遇到SSL_ERROR_SYSCALL错误解决方法
检查1:电脑上是否有curl-openssl(一般都有)
openssl version

如果没有,则安装curl-openssl。安装方法如下:
1 | brew install curl-openssl |

原来是一个依赖包下载不成功(harfbuzz-3.1.1.arm64_monterey)
用brew单独下载依赖包,就避免了找不到依赖版本的错误
1 | brew install zstd |

之后再返回继续执行之前的
1 | brew install curl-openssl |
