【发布时间】:2020-09-05 19:23:24
【问题描述】:
每次页面在屏幕上可见时,是否有任何回调可用?在 ios 中有一些委托方法,例如 viewWillAppear、viewDidAppear、viewDidload。
我想在特定页面出现在屏幕上时调用 API。
注意:我不是在询问应用程序的状态,例如前台、后台、暂停、恢复。
谢谢!
【问题讨论】:
标签: flutter dart flutter-layout
每次页面在屏幕上可见时,是否有任何回调可用?在 ios 中有一些委托方法,例如 viewWillAppear、viewDidAppear、viewDidload。
我想在特定页面出现在屏幕上时调用 API。
注意:我不是在询问应用程序的状态,例如前台、后台、暂停、恢复。
谢谢!
【问题讨论】:
标签: flutter dart flutter-layout
特别针对您的问题:
使用initState,但请注意,您不能在initState 中使用async 调用,因为它会在初始化小部件之前调用,就像名称的意思一样。如果您想在创建 UI 后做某事didChangeDependencies 很棒。但千万不要在不使用FutureBuilder或StreamBuilder的情况下使用build()
演示的简单示例:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
@override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
@override
void initState() {
super.initState();
fetchData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
StatefulWidget的State类的一些生命周期方法:
initState():
描述此小部件所代表的用户界面部分。
框架在多种不同情况下调用此方法:
After calling initState. After calling didUpdateWidget. After receiving a call to setState. After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes). After calling deactivate and then reinserting the State object into the tree at another location.框架将这个小部件下面的子树替换为小部件 此方法返回,通过更新现有子树或通过 移除子树并膨胀一个新的子树,这取决于是否 此方法返回的小部件可以更新现有的根 子树,通过调用 Widget.canUpdate 确定。 Read more
didChangeDependencies():
在此 State 对象的依赖项发生更改时调用。
例如,如果之前的 build 调用引用了一个 InheritedWidget 后来改变了,框架会调用这个 通知此对象有关更改的方法。
在
initState之后也立即调用此方法。这是安全的 从此方法调用BuildContext.dependOnInheritedWidgetOfExactType。 Read more
build()(无状态小部件)
描述此小部件所代表的用户界面部分。
当这个小部件被插入到 树在给定的 BuildContext 中以及当这个小部件的依赖关系 更改(例如,此小部件引用的
InheritedWidget更改)。 Read more
didUpdateWidget(Widget oldWidget):
在小部件配置更改时调用。
如果父小部件重建并请求该位置在 树更新以显示具有相同 runtimeType 的新小部件和
Widget.key,框架会更新这个的widget属性State对象引用新的小部件,然后调用此方法 前一个小部件作为参数。 Read more
【讨论】:
initState,但请注意您不能在initState 中使用异步调用。如果你想在创建 UI 之后做某事didChangeDependencies 就可以了
initState 本身不是async,您就可以在initState 中使用async 调用,因此您可以使用FutureBuilder 重绘该async 的用户界面打电话。
有些小部件是无状态的,有些是有状态的。如果它是无状态小部件,则只有值可以更改,但 UI 更改不会呈现。
有状态小部件的方式相同,它的值和 UI 都会发生变化。
现在,将研究方法。
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState(() {});
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
【讨论】:
如果您想进行 API 调用,那么您必须(或确实应该)使用 StatefulWidget。
遍历它,假设您的有状态小部件收到了一些它需要进行 API 调用的 id。
每次您的小部件收到一个新 ID(包括第一次)时,您都需要使用该 ID 进行新的 API 调用。
所以使用didUpdateWidget 来检查id 是否发生变化,如果发生变化(就像小部件出现时所做的那样,因为旧的id 将是null)然后进行新的API 调用(也设置适当的加载和错误状态!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
@override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
@override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
@override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
我是 Flutter 的新手(从字面上看,这个周末才开始使用它),但它本质上复制了 React 范例,如果这对你有帮助的话。
个人喜好,我非常喜欢这种方法而不是使用FutureBuilder(现在,就像我说的,我是全新的)。逻辑更容易推理(对我来说)。
【讨论】:
FutureBuilder 逻辑。起初理解它可能会很棘手。您还可以将setState 调用减少为:try{ resp=await apiCall(); data=resp;}catch(e){err = e;} setState(()=>loading = false;); 保存的每个小部件重绘都将确保 UI 流畅。
您不需要StatefulWidget 来在每次显示屏幕时调用 api。
在以下示例代码中,按浮动操作按钮导航到 api 调用屏幕,使用返回箭头返回,再次按下浮动操作按钮导航到 api 页面。
每次访问该页面时都会自动调用api。
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
这是零样板的简单代码。
【讨论】:
最简单的方法是使用need_resume
1.将此添加到您的包的 pubspec.yaml 文件中:
dependencies:
need_resume: ^1.0.4
2.使用ResumableState 类型而不是State 为有状态小部件创建状态类
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
@override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
@override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
@override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
【讨论】: