Uri对象的使用
直接使用字符串来拼接 URI 地址需要关注地址中拼接的每个部分的合法性,并且在处理复杂逻辑时需要更冗长的处理,如果变量包含非法字符(如中文),整个地址非法。
如:在路由跳转中使用
方式一:使用Uri对象(推荐)
Uri address = Uri(path: path, queryParameters: queryParameters);
NavigatorUtils.push(context, address.toString());方式二:参数处理,不推荐
NavigatorUtils.push(context,'${Routes.webViewPage}?title=${Uri.encodeFull(title)}&url=${Uri.encodeComponent(url)}');
类型转换
建议使用 is
而不是 as
来进行类型转换。 is
运算符允许更安全地进行类型检查,如果转换失败,也不会抛出异常。as
进行类型失败会抛出异常。如:
if (animal is Bird) {animal.fly();} else {print('转换失败');}(animal as Animal).eat('meat'); // 强制类型转换一旦失败就会抛异常
ChangeNotifier 使用
1.ChangeNotifier 的属性访问或方法调用:
ChangeNotifier
及其子类在 dispose
之后将不可使用,dispose
后访问其属性(hasListener
)或方法(notifyListeners
)时均不合法,在 dispose
后访问属性或调用方法通常出现在异步调用的场景下,由其是在网络请求之后刷新界面。典型场景如下:
class PageNotifier extends ChangeNotifier { dynamic pageData;Future<voud> beginRefresh() async {final response = await API.getPageContent();if (!response.success) return;pageData = response.data;// 接口返回之后此实例可能被 dispose,从而导致异常notifyListeners();}
}
解决:
// 统一定义如下 mixin
mixin Disposed on ChangeNotifier {bool _disposed = false;bool get hasListeners {if (_disposed) return false;return super.hasListeners;}@overridevoid notifyListeners() {if (_disposed) return;super.notifyListeners();}@overridevoid dispose() {_disposed = true;super.dispose();}
}// 在必要的 ChangeNotifier 子类混入 Disposed
class PageNotifier extends ChangeNotifier with Disposed { Future<voud> beginRefresh() async {final response = await API.getPageContent();if (!response.success) return;pageData = response.data;// 异步调用不会异常notifyListeners(); }}
2.ChangeNotifier 禁止实例复用:
单个 ChangeNotifier
实例在多个独立的组件或页面中使用会造成潜在的问题:复用的实例一旦在某个组件中被意外 dispose
之后就无法使用,从而影响其它组件展示逻辑并且这种影响是全局的
@override
void initState() {super.initState();// 添加监听ShoppingCart.instance.addListener(_update);
}@override
void dispose() {// 正确移除监听ShoppingCart.instance.removeListener(_update);// 在组件中这样移除监听,将产生致命影响// ShoppingCart.instance.dispose();super.dispose();
}
解决:因此在 Flutter 开发中应禁止 ChangeNotifier
实例对外跨组件直接复用,如需跨组件复用应借助provider
、get_it
等框架将 ChangeNotifer
子类实例对象置于顶层;
void main() {runApp(MultiProvider(providers: [Provider<Something>.value(ShoppingCart.instance),],child: const MyApp(),));
}
如果你非得要 「「单例化」」 自定义 ChangeNotifier
子类实例,记得一定要重新 dispose
函数。
Controller 使用
在 Flutter 中大多数 Controller
都直接或间接继承自 ChangeNotifier
。为使代码逻辑更加严谨,增强整个代码的健状性,建议:所有 Controller
需要显式调用 dispose
方法,所有自定义 Controller
需要重写或者添加 dispose
方法。
// ScrollController 源码
class ScrollController extends ChangeNotifier {
//...
}// 自定义 Controller 需要添加 dispose 方法
class MyScrollController {ScrollController scroll = ScrollController();// 添加 dispose 方法void dispose() {scroll.dispose();}
}
避免资源释放遗忘
在 Flutter 中有很多需要主动进行资源释放的类型,包含但不限于:Timer
、StreamSubscription
、ScrollController
、TextEditingController
等,另外很多第三方库存在需要进行资源释放的类型。
如此多的资源释放类型管理起来是非常麻烦的,一旦忘记某个类型的释放很会造成整个页面的内存泄漏。而资源的创建一般都位于 initState
内,资源释放都位于 dispose
内。
「为了减小忘记资源释放的可能性,dispose
应为 State
内的第一个函数并尽可能的将 initsate
紧跟在 dispose
后」
示例:
final _controller = TextEditingController();
late Timer _timer;// 属性后第一个函数应为 dispose
void dispose() {_controller.dispose();_timer.cancell();super.dispose();
}
// 中间不要插入其它函数,紧跟着写 initState
void initState() {super.initState();_timer = Timer(...);
}
创建一个通用的mixin来处理
// 创建下面的 Mixin
mixin AutomaticDisposeMixin<T extends StatefulWidget> on State<T> {Set<VoidCallback> _disposeSet = Set<VoidCallback>();void autoDispose(VoidCallback callabck) {_disposeSet.add(callabck);}void dispose() {_disposeSet.forEach((f) => f());_disposeSet.removeAll();super.dispose();}
}
1.局部变量场景下使用:
//使用前处理方式
late CancelToken _token;Future<void> _refreshPage() async {// _token 只在页面刷新的函数中使用,却不得不加一个变量来引用它_token = CancelToken();Dio dio = Dio();Response response = await dio.get(url, cancelToken: _token);int code = response.statusCode;// ...
}void dispose() {super.dispose();_token.cancel();
}
//使用后处理方式
class _PageState extends State<Page> with AutomaticDisposeMixin {Future<void> _refreshPage() async {final token = CancelToken();// 添加到自动释放队列autoDispose(() => token.cancel());Dio dio = Dio();Response response = await dio.get(url, cancelToken: token);int code = response.statusCode;// ...}
}
2.在 initState
内进行资源声明的同时进行资源释放,这种写法相对来讲更加直观,更不易遗漏资源释放
final _controller = TextEditingController();void initState() {super.initState();_timer = Timer(...);autoDispose(() => _timer.cancel());autoDispose(() => _controller.dispose());
}
State 中存在异步刷新
1.如下单独处理方式
Future<void> _refreshPage() async {// 异步可能是接口、文件读取、状态获取等final response = await API.getPageDetaile();if (!response.success) return;// 当前 Widget 存在于渲染树中才刷新if (!mounted) return; setState((){_data = response.data;});
}
2.统一处理方式
// 统一定义如下 mixin
mixin Stateable<T extends StatefulWidget> on State<T> {@overridevoid setState(VoidCallback fn) {if (!mounted) return;super.setState(fn);}
}// 在存在异步刷新的 State 中 with 如上 mixin
class SomPageState extends State<SomePageWidget> with Stateable { //...
}