接着上篇 Flutter开发中的一些Tips,今天再分享一些我遇到的问题,这篇较上一篇,细节方面更多,但愿“引觉得戒”,毕竟细节决定成败。本篇的全部例子,都在我开源的flutter_deer中。但愿Star、Fork支持,有问题能够Issue。附上连接: https://github.com/simplezhli...
这个是我偶然在控制台发现的,完整的错误信息以下:java
Unhandled Exception: setState() called after dispose(): _AboutState#9c33a(lifecycle state: defunct, not mounted)
固然flutter在错误信息以后还有给出问题缘由及解决方法:android
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
大体的意思是,widget已经在dispose
方法时销毁了,但在这以后却调用了setState
方法,那么会发生此错误。好比定时器或动画回调调用setState()
,但此时页面已关闭时,就会发生此错误。这个错误通常并不会程序崩溃,只是会形成内存的泄露。git
那么解决的办法分为两部分:github
Timer _countdownTimer; @override void dispose() { _countdownTimer?.cancel(); _countdownTimer = null; super.dispose(); }
setState()
前判断当前页面是否存在:_countdownTimer = Timer.periodic(Duration(seconds: 2), (timer) { if (mounted){ setState(() { }); } });
咱们能够看看 mounted
在源码中是什么json
BuildContext get context => _element; StatefulElement _element; /// Whether this [State] object is currently in a tree. /// /// After creating a [State] object and before calling [initState], the /// framework "mounts" the [State] object by associating it with a /// [BuildContext]. The [State] object remains mounted until the framework /// calls [dispose], after which time the framework will never ask the [State] /// object to [build] again. /// /// It is an error to call [setState] unless [mounted] is true. bool get mounted => _element != null;
BuildContext
是Element
的抽象类,你能够认为mounted
就是 context
是否存在。那么一样在回调中用到 context
时,也须要判断一下mounted
。好比咱们要弹出一个 Dialog
时,或者在请求接口成功时退出当前页面。BuildContext
的概念是比较重要,须要掌握它,错误使用通常虽不会崩溃,可是会使得代码无效。app
本问题详细的代码见:点击查看less
问题描述:我在每次的接口请求前都会弹出一个Dialog
作loading提示,当接口请求成功或者失败时关闭它。但是若是在请求中,咱们点击了返回键人为的关闭了它,那么当真正请求成功或者失败关闭它时,因为咱们调用了Navigator.pop(context)
致使咱们错误的关闭了当前页面。async
那么解决问题的突破口就是知道什么时候Dialog的关闭,那么就可使用 WillPopScope
拦截到返回键的输入,同时记录到Dialog的关闭。ide
bool _isShowDialog = false; void closeDialog() { if (mounted && _isShowDialog){ _isShowDialog = false; Navigator.pop(context); } } void showDialog() { /// 避免重复弹出 if (mounted && !_isShowDialog){ _isShowDialog = true; showDialog( context: context, barrierDismissible: false, builder:(_) { return WillPopScope( onWillPop: () async { // 拦截到返回键,证实dialog被手动关闭 _isShowDialog = false; return Future.value(true); }, child: ProgressDialog(hintText: "正在加载..."), ); } ); } }
本问题详细的代码见:点击查看动画
addPostFrameCallback
回调方法在Widget渲染完成时触发,因此通常咱们在获取页面中的Widget大小、位置时使用到。
前面第二点我有说到我会在接口请求前弹出loading。若是我将请求方法放在了initState
方法中,异常以下:
inheritFromWidgetOfExactType(_InheritedTheme) or inheritFromElement() was called before initState() completed.
When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
缘由:弹出一个DIalog的showDialog
方法会调用Theme.of(context, shadowThemeOnly: true)
,而这个方法会经过inheritFromWidgetOfExactType
来跨组件获取Theme对象。
inheritFromWidgetOfExactType
方法调用inheritFromElement
:
可是在_StateLifecycle
为created
和 defunct
时是没法跨组件拿到数据的,也就是initState()
时和dispose()
后。因此错误信息提示咱们在 didChangeDependencies
调用。
然而放在didChangeDependencies
后,新的异常:
setState() or markNeedsBuild() called during build.
This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
提示咱们必须在页面build时,才能够去建立这个新的组件(这里就是Dialog)。
因此解决方法就是使用addPostFrameCallback
回调方法,等待页面build完成后在请求数据:
@override void initState() { WidgetsBinding.instance.addPostFrameCallback((_){ /// 接口请求 }); }
致使这类问题的场景不少,可是大致解决思路就是上述的办法。
本问题详细的代码见:点击查看
很少哔哔,直接看图:
简单说就是删除一个emoji表情,通常须要点击删除两次。碰到个别的emoji,须要删除11次!!其实这问题,也别吐槽Flutter,基本emoji在各个平台上都或多或少有点问题。
缘由就是:
这个问题我发如今Flutter 的1.5.4+hotfix.2
版本,解决方法能够参考:https://github.com/flutter/en... 虽然只适用于长度为2位的emoji。
幸运的是在最新的稳定版1.7.8+hotfix.3
中修复了这个问题。不幸的是我发现了其余的问题,好比在我小米MIX 2s上删除文字时,有时会程序崩溃,其余一些机型正常。异常以下图:
我也在Flutter上发现了一样的问题Issue,具体状况能够关注这个Issue :https://github.com/flutter/fl... ,据Flutter团队的人员的回复,这个问题修复后不太可能进入1.7的稳定版本。。
因此建议你们谨慎升级,尤为是用于生产环境。那么这个问题暂时只能搁置下来了,等待更稳定的版本。。。
MediaQuery.of(context).viewInsets.bottom > 0
viewInsets.bottom
就是键盘的顶部距离底部的高度,也就是弹起的键盘高度。若是你想实时过去键盘的弹出状态,配合使用didChangeMetrics
。完整以下:
import 'package:flutter/material.dart'; typedef KeyboardShowCallback = void Function(bool isKeyboardShowing); class KeyboardDetector extends StatefulWidget { KeyboardShowCallback keyboardShowCallback; Widget content; KeyboardDetector({this.keyboardShowCallback, @required this.content}); @override _KeyboardDetectorState createState() => _KeyboardDetectorState(); } class _KeyboardDetectorState extends State<KeyboardDetector> with WidgetsBindingObserver { @override void initState() { WidgetsBinding.instance.addObserver(this); super.initState(); } @override void didChangeMetrics() { super.didChangeMetrics(); WidgetsBinding.instance.addPostFrameCallback((_) { print(MediaQuery.of(context).viewInsets.bottom); setState(() { widget.keyboardShowCallback ?.call(MediaQuery.of(context).viewInsets.bottom > 0); }); }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { return widget.content; } } 代码来自项目GSYFlutterDemo:https://github.com/CarGuo/GSYFlutterDemo
if (MediaQuery.of(context).viewInsets.bottom == 0){ final focusScope = FocusScope.of(context); focusScope.requestFocus(FocusNode()); Future.delayed(Duration.zero, () => focusScope.requestFocus(_focusNode)); }
其中_focusNode
是对应的TextField
的focusNode
属性。
FocusScope.of(context).requestFocus(FocusNode());
这里提一下关闭,通常来讲即便键盘弹出,点击返回页面关闭,键盘就会自动收起。可是顺序是:
页面关闭 --> 键盘关闭
这样会致使键盘短暂的出如今你的上一页面,也就会出现短暂的部件溢出(关于溢出可见上篇)。
因此这时你就须要在页面关闭前手动调用关闭键盘的代码了。按道理是要放到deactivate
或者dispose
中处理的,可谁让context
已经为null了,因此,老办法,拦截返回键:
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { // 拦截返回键 FocusScope.of(context).requestFocus(FocusNode()); return Future.value(true); }, child: Container() ); }
本问题详细的代码见:点击查看
话说如今新建的Flutter项目,Android的 targetSdkVersion
默认都是28。因此不可避免的就是Android 9.0的适配甚至6,7,8的适配,那我碰到的一个问题是接入的高德2D地图在9.0的机子上显示不出来。
问题的主要缘由是Android 9.0 要求默认使用加密链接,简单地说就是不容许使用http请求,要求使用https。高德的2D地图sdk怀疑是使用了http请求,因此会加载不出。
解决方法两个:
targetSdkVersion
改成28如下(长远看来不推荐)network_security_config.xml
文件:<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
AndroidManifest.xml
中的application
添加:
android:networkSecurityConfig="@xml/network_security_config"
这个问题只是Android适配中的一小部分,相应的iOS中也有适配问题。好比经常使用的权限适配等。
不得不说作Flutter的开发须要对原生开发有必定了解。尤为是以前在写Flutter的地图插件时感觉深入,那么我原本就是作Android开发的,因此Android端的部分很快就完成了。iOS部分就很吃力,首先OC的语法就不会,其次说实话写完了内心也没底,仍是须要向iOS的同事请教确保一下。因此跨平台方案的出现并不会对原生开发形成冲击,反而是对原生开发提出了更高的要求。
本问题详细的代码见:点击查看
FlutterJsonBeanFactory
。关于它的一系列使用能够参看:https://www.jianshu.com/nb/33... fluttertoast
这个插件,而我推荐oktoast这类使用Flutter的解决方案 。由于fluttertoast
是调用了Android原生的Toast,首先在各个系统上的样式就不统一,同时部分系统机型上受限通知权限,会致使Toast没法弹出。篇幅有限,那么先分享以上几条Tips,若是本篇对你有所帮助,能够点赞支持!其实收藏起来不是之后遇到问题时查找更方便吗🤔。
最后再次奉上Github地址:https://github.com/simplezhli...