flutter遇到问题及解决方案
目录
1、easy_refresh相关问题
2、 父子作用域关联问题
3. 刘海屏底部安全距离
4. 了解保证金弹窗 iOS端闪退 (待优化)
5. loading无法消失
6. dialog蒙版问题
7. 倒计时优化
8. scrollController.offset报错
9. 断点不走
10.我的出价报红
11. 竞拍大厅折叠效果与滚动冲突 & 加载完成状态无法上拉加载
12. 加载网络图片失败的页面返回报错(未解决)
13. flutter页面进入后快速返回 报错
14. list. first 或 firstWhere报错 List firstWhere Bad state: No element
15. 我的出价页面滑动太灵敏
16. 禁用侧滑
17. ListView底部 iOS有间距,但Android没有
1、easy_refresh相关问题
a. 仅有下拉刷新,viewModel可以不需要控制刷新状态。不用传controller
b. 传controller,在请求成功和失败后,都需要自行去管理刷新状态
问题一:我的出价多次快速切换按钮,调用下拉刷新,onRefresh方法不回调
解决方案:
controller.callRefresh(duration: const Duration(milliseconds: 1), force: true);
问题二:下拉刷新,添加筛选项,回到列表页,上划列表,请求被取消
原因:ClassicHeader和ClassicFooter 有 triggerOffset, ///触发器任务的偏移量 和 maxOverOffset, ///最大超限偏移,将不再滚动
解决方案:将两个值设置成相同,则会在滑到 “松开刷新” 的状态时,松手的一刻就回调 onRefresh 和 onLoad 方法。
triggerOffset: 100, ///触发器任务的偏移量
maxOverOffset: 100, ///最大超限偏移,将不再滚动
问题三:列表请求下一页,去重后数据少于1条,底部显示加载完成,且上拉加载无效,列表下滑一点再上拉才可以。
解决方案:关闭无限滚动 infiniteOffset设为null,默认值70
2、 父子作用域关联问题
问题:AScope创建BScope,BScope中调用A的ViewModel,与AScope的ViewModel,不是同一个。
现象:竞拍大厅使用Scope,筛选后竞拍大厅未同步筛选项
解决方案:
子作用域创建时需将父作用域的container传过来,并实现parent方法,即实现了父子作用域绑定关系
3. 刘海屏底部安全距离
a. ScreenUtil().bottomBarHeight 获取到的值为0,建议不再使用
b. 可以使用 MediaQuery.of(context).padding.bottom, 获取的值正常
c. AppUtil中提供了safeBottom方法
由于调用该方法会监听context,当下个页面存在输入框时,拉起关闭键盘都会重新rebuild页面,导致ui闪动。因此将bottom的获取放到app初始化时,且仅获取一次(判空逻辑改为copyWith应该也行)
4. 了解保证金弹窗 iOS端闪退 (待优化)
a. demo中debug、profile正常运行
b. 经销商app源码接入后,正常运行
c. 经销商app framework接入后,运行闪退
调用路径:webview_flutter => plugin webview_flutter_wkwebview => native wkwebview
暂未发现经销商app影响调用功能的代码。闪退的堆栈中,number类型调用字典取值方法导致的闪退,FLTWebController为三方的plugin方法实现。为不影响上线,iOS改用原生弹窗。
5. loading无法消失
搜索页加载中侧滑返回后,loading无法消失
原因:
@override
void dispose() {
disposed = true;
CommonRequest().remove(this);
AppUtil().dismissLoading(); // 优化后代码
super.dispose();
}
dispose时移除了请求监听,导致onDone,onError没有监听回调,loading dismiss方法也就不会触发
因此在移除请求监听时,手动dismiss loading即可。
6. dialog蒙版问题
a. flutter无法给底部tab添加蒙版
解决方案:增加plugin方法调用原生
@async void setTabBarMasker(bool hidden);
b. 弹窗后触发引导切换到其他tab, 蒙版无法消失
c. 多级flutter页面 iOS侧滑后,弹窗在前一个页面未dismiss
解决方案:增加flutter plugin :
void dismissSmartDialog();
/// tab切换 dismiss已弹出的dialog
@override
Future<void> dismissSmartDialog() async {
await SmartDialog.dismiss();
await SmartDialog.dismiss();
await SmartDialog.dismiss();
}
原生切换tab,或flutter页面侧滑时,移除dialog(一般不会超过三个,容错移除三次,简单粗暴)
遇到问题:首次进入app,快速切换底部tab,蒙层不消失
原因一 猜您喜欢请求有滞后,切换到其他tab后,请求才成功并触发原生弹出蒙层。
解决方案:添加pageshow provider,同时监听pageshow provider和引导显示的provider
visible: ref.watch(showGuideOfGuessLikeProvider) && ref.watch(isPageShowProvider),
原因二:生命周期未成对出现。
页面首次创建时并没有执行pageShow,pageShow方法是我们自己添加的渲染帧调度监听触发的。
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (!mounted) {
return;
}
if (needPageObserver() && BoostNavigator.instance.isTopPage(context)) {
onPageShow();
}
onFirstFrame();
});
super.initState();
}
方案二: 改用全局弹窗,router:custom_dialog,present一个半透明弹窗,可以遮盖tabbar
参考 放弃收购弹窗。
遇到问题一: 弹窗pop后,前一个页面loading卡帧,无法消失
解决方案:push.then 后移除loading
建议使用方案二,但不要认为方案二遇到的问题会比方案一少,需要注意flutter各种奇奇怪怪的问题
7. 倒计时优化
a. 列表使用的全局定时器,widget频繁监听,影响性能
解决方案:pageHide移除监听,列表倒计时结束移除监听。
b. 报错Bad state: Cannot use "ref" after the widget was disposed.
原因: 列表快速滚动,调用ref代码时widget已经被释放了
解决方案:onPageShow调用ref前先判断ref.context.mounted为true。(未从根源上解决问题)
mounted解释说明:
在Flutter中,`mounted`是一个布尔类型的变量,它表示当前State对象是否已经被插入到树中去了。当这个State对象被添加到树中时,`mounted`变量就会被设置为`true`。如果State对象被删除,那么`mounted`变量就会被设置为`false`。
开发者可以使用`mounted`变量来判断当前的State对象是否还有效,如果`mounted`为`true`,则可以安全地调用`setState()`方法进行状态更新。如果`mounted`为`false`,则不应该调用`setState()`,因为这个State对象已经被从树中删除了,状态更新将不会生效。
参考 BidHallItemChildCountDown
@override
void watchGenerateViewModel(WidgetRef ref) {
super.watchGenerateViewModel(ref);
remainSeconds = model.awayFromEnd! - DateTime.now().millisecondsSinceEpoch ~/ 1000;
/// 监听定时器刷新 PageHide不监听
if (ref.watch(shouldWatchTimerProvider) && remainSeconds > 0) {
ref.watch(commonTimer);
}
}
@override
void onPageHide() {
super.onPageHide();
ref.read(bidHallItemViewModelProvider).updateShouldWatchTimer(false);
}
@override
void onPageShow() {
super.onPageShow();
if (ref.context.mounted){
ref.read(bidHallItemViewModelProvider).updateShouldWatchTimer(true);
}
}
@override
bool needPageObserver() {
return true;
}
根本原因是生命周期未成对出现,渲染帧调度监听添加mounted判断后再触发onPageShow,同6-原因二,优化后onPageShow不需要再判断mounted
8. scrollController.offset报错
a.scrollController加到了easy_refresh的scrollController上,应该是widget的controller
b.列表加了scrollController,但是空视图页面没有加scrollController
9. 断点不走
检查下新加的代码,有可能在断点前面的代码抛异常了
10.我的出价报红
报错日志:如下图所示
flutter: The following assertion was thrown:
flutter: Tried to change the number of overrides. This is not allowed – overrides cannot be removed/added,
flutter: they can only be updated.
flutter: 'package:riverpod/src/framework/container.dart':
flutter: Failed assertion: line 373 pos 7: '_debugOverridesLength == overrides.length'
原因:
You didn't create two separate ProviderScope. The second one updated the first one.
If you want two different scope, give them a different Key
issue 链接: "Tried to change the number of overrides" when running golden tests on multiple widgets with different ProviderScopes · rrousselGit/riverpod · Discussion #2804 · GitHub
解决方案:给ProviderScope添加不同的key
ListView.builder(
controller: viewModel.scrollController,
itemCount: itemVMList.length,
itemBuilder: (BuildContext context, int position) {
return _buildChildItemProvider(itemVMList, position);
}),
DealerItemProvider? _buildChildItemProvider( List<DealerItemViewModel> myPriceList, int position) {
if (widget.myPriceType == Const.BIDING) { ///竞拍中
if (myPriceList[position] is ItemBiddingChildVM) {
return DealerItemProvider(
key: const Key('ItemBidding'), ///需要设置不同key
itemOverrides: overrideItemBiddingChildVM(
model: myPriceList[position] as ItemBiddingChildVM,
index: position),
child: ItemBiddingChild(),
);
} else if (myPriceList[position] is CommonRecommendTitleViewModel) {
return DealerItemProvider(
key: const Key('CommonRecommend'), ///需要设置不同key
itemOverrides: overrideCommonRecommendTitleViewModel(
model: myPriceList[position] as CommonRecommendTitleViewModel,
index: position),
child: CommonRecommendTitleWidget());
} else if (myPriceList[position] is BidHallItemViewModel) {
return DealerItemProvider(
key: const Key('CommonRecommend'), ///需要设置不同key
itemOverrides: overrideBidHallItemViewModel(
model: myPriceList[position] as BidHallItemViewModel,
index: position),
child: BidHallItem());
}
}
return null;
}
11. 竞拍大厅折叠效果与滚动冲突 & 加载完成状态无法上拉加载
a. 折叠效果通过NestedScrollView实现,列表刷新数据后滚动到顶部是在list添加scrollController实现,二者相冲突,只有一个生效
b. 列表请求多页数据,筛选第一页数据后,最下面显示加载完成,无法上拉加载。
解决方案: NestedScrollView添加key,触发请求前调用NestedScrollView滚动到顶部的方法
注意:调用innerController.jumpTo(0) 固定收起折叠样式。调用outerController.jumpTo(0); 固定展开折叠样式。因此需判断当前折叠状态。
final GlobalKey<NestedScrollViewState> scrollKey = GlobalKey<NestedScrollViewState>();
/// 触发下拉刷新 手动滚动到顶部
void refreshDataWithAnimate() {
if (pageState != ViewState.BUSY){
if (collapsedState){
scrollKey.currentState?.innerController.jumpTo(0);
}else{
scrollKey.currentState?.outerController.jumpTo(0);
}
}
refreshData();
}
c. 偶现 首次初始化列表 折叠区域(豆腐块),一直处于折叠状态,无法展开。
原因:静态常量声明使用了.w, 初始化时,scaleWidth有概率为0。原因是screenUtil初始化落后于静态常量初始化导致的
解决方案: 暂时这里不使用静态常量
根本解决方案:
I. 静态常量不使用.w
II. 更改定义方案,改为变量属性,总之让调用.w晚于screenUtil初始化
12. 加载网络图片失败的页面返回报错(未解决)
flutter: ══╡ EXCEPTION CAUGHT BY FLUTTER FRAMEWORK ╞═════════════════════════════════════════════════════════
flutter: The following StateError was thrown:
flutter: Bad state: Failed to load https://pic4.zhimg.com/100/v2-1c0788ea29fe3adaa98ff4ac0.jpg.
flutter:
flutter: When the exception was thrown, this was the stack
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: Bad state: Failed to load https://pic4.zhimg.com/100/v2-1c0788ea29fe3adaa98ff4ac0.jpg.
原因: 加载图片ExtendedImage.network,页面返回时,重新调用了load方法,报错,且会有堆栈上传。
13. flutter页面进入后快速返回 报错
[SmartDialog] 'package:flutter/src/widgets/routes.dart':
Failed assertion: line 1579 pos 12: '_scopeKey.currentState != null':
Tried to add a willPop callback to a route that is not currently in the tree.
原因:withContainer为false时,不新开container,页面创建太快,立刻点击返回,触发willpop,而此时树中还没有这个路由,因此报错(若解释不对请修正)
解决方案:push flutter页面withContainer改为true。
14. list. first 或 firstWhere报错 List firstWhere Bad state: No element
列表为空数组,调用first方法会崩溃。
列表调用firstWhere 找不到数据也会崩溃。
建议使用列表扩展中的safeFirst 和safeFirstWhere方法。
15. 我的出价页面滑动太灵敏
原因:Tabbar->TabBarView->pageController(PageView->controller) 滚动有动画,时间300ms,滑动切换tab后,动画还没停止,上滑的时候,动作依然被pageController接管,导致上滑触发了左右滑动。
解决方案: 使用ExtendedTabBarView
/// 构建我的出价 子页面
Widget _buildChildList() {
return Container(
color: themeColors.common_bg2_color,
padding: EdgeInsets.only(bottom: Platform.isAndroid ? 90.w : 0),
child: ExtendedTabBarView(
physics: const LessSpringClampingScrollPhysics(),
shouldIgnorePointerWhenScrolling:false,
link: false,
controller: viewModel.tabController,
children: _getTabKeepAliveList(),
),
);
}
16. 禁用侧滑
方式一:路由push增加参数 'disablePopGesture': true,
方式二:动态禁用侧滑
final PageInfo? pageInfo = RouteNavigator.getPageInfoByContext(ref.context);
BoostChannel.instance.disablePopGesture(containerId: pageInfo?.uniqueId ?? '');
17. ListView底部 iOS有间距,但Android没有
原因: iOS底部有安全距离,安卓没有,原因代码如下:
解决方案:ListView设置padding为zero