Flutter - 当 Provider 变量改变时触发导航
Flutter - trigger navigation when Provider variable changes
我试图在应用程序初始启动时显示启动画面,直到我正确检索到所有数据。检索由名为 "ProductData" 的 class 完成。准备就绪后,我想从启动页面导航到应用程序的主屏幕。
不幸的是,我找不到触发 运行 那种导航并侦听提供商的方法的好方法。
这是我用来测试这个想法的代码。具体来说,我想 运行 命令 Navigator.pushNamed(context, 'home');当变量 shouldProceed 变为真时。不幸的是,下面的代码给了我错误,"setState() or markNeedsBuild() called during build."
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';=
import 'package:provider/provider.dart';
class RouteSplash extends StatefulWidget {
@override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
bool shouldProceed = false;
@override
Widget build(BuildContext context) {
shouldProceed =
Provider.of<ProductData>(context, listen: true).shouldProceed;
if (shouldProceed) {
Navigator.pushNamed(context, 'home'); <-- The error occurs when this line is hit.
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
}
是否有更好的方法来根据提供商的结果导航到页面?
如果您仍在等待数据,您应该做的不是尝试导航到新视图,而是显示加载初始屏幕,一旦更改显示您的主主页视图,如下所示:
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Main extends StatefulWidget {
@override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
bool shouldProceed = Provider.of<ProductData>(context, listen: true).shouldProceed;
@override
Widget build(BuildContext context) {
if(shouldProceed){
return Home();
}else{
return RouteSplash();
}
}
}
在这个例子中使用 BlocListener:
BlocListener(
bloc: BlocProvider.of<DataBloc>(context),
listener: (BuildContext context, DataState state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: BlocBuilder(
bloc: BlocProvider.of<DataBloc>(context),
builder: (BuildContext context, DataState state) {
if (state is Initial) {
return Text('Press the Button');
}
if (state is Loading) {
return CircularProgressIndicator();
}
if (state is Success) {
return Text('Success');
}
if (state is Failure) {
return Text('Failure');
}
},
}
)
我想我有一个解决方案可以满足 OP 的要求。如果您将启动画面设置为 Stateful
,那么您可以添加一个 PostFrameCallback
。这避免了在 build
为 运行 时调用 Navigator
的任何问题。然后,您的回调可以调用 Provider
读取数据所需的任何例程。此读取数据例程可以传递包含 Navigator
命令的进一步回调。
在我的解决方案中,我添加了一个进一步的回调,以便启动画面至少显示一秒钟(您可以在此处选择您认为合理的持续时间)。不幸的是,这会造成竞争条件,因此我需要导入 synchronized
包以避免出现问题。
import 'package:flutter/material.dart';
import 'package:reflect/utils/constants.dart';
import 'category_screen.dart';
import 'package:provider/provider.dart';
import 'package:reflect/data_models/app_prefs.dart';
import 'dart:async';
import 'dart:core';
import 'package:synchronized/synchronized.dart';
class LoadingScreen extends StatefulWidget {
static const id = 'LoadingScreen';
@override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
bool readPrefsDone = false;
bool timeFinished = false;
Lock _lock = Lock();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<AppPrefs>(context, listen: false).readPrefs(readDone);
Timer(Duration(seconds: 1), () {
timerDone();
});
});
}
void timerDone() async {
_lock.synchronized(() {
if (readPrefsDone) {
pushMainScreen();
}
timeFinished = true;
});
}
void readDone() {
_lock.synchronized(() {
if (timeFinished) {
pushMainScreen();
}
readPrefsDone = true;
});
}
void pushMainScreen() {
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, animation, animation2) => CategoryScreen(),
transitionDuration: Duration(seconds: 1),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Hero(
tag: kFishLogoTag,
child: Image(
image: AssetImage('assets/fish_logo.png'),
),
),
SizedBox(
height: 30,
),
Text(
'Reflect',
style: TextStyle(
fontSize: 30,
color: Color(0xFF0000cc),
fontWeight: FontWeight.bold,
),
),
],
),
),
));
}
}
遇到此问题的任何其他人都可以使用此代码
Future.delayed(Duration.zero, () => Navigate.toView(context));
这会导航到另一个屏幕而不会出现构建错误
我试图在应用程序初始启动时显示启动画面,直到我正确检索到所有数据。检索由名为 "ProductData" 的 class 完成。准备就绪后,我想从启动页面导航到应用程序的主屏幕。
不幸的是,我找不到触发 运行 那种导航并侦听提供商的方法的好方法。
这是我用来测试这个想法的代码。具体来说,我想 运行 命令 Navigator.pushNamed(context, 'home');当变量 shouldProceed 变为真时。不幸的是,下面的代码给了我错误,"setState() or markNeedsBuild() called during build."
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';=
import 'package:provider/provider.dart';
class RouteSplash extends StatefulWidget {
@override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
bool shouldProceed = false;
@override
Widget build(BuildContext context) {
shouldProceed =
Provider.of<ProductData>(context, listen: true).shouldProceed;
if (shouldProceed) {
Navigator.pushNamed(context, 'home'); <-- The error occurs when this line is hit.
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
}
是否有更好的方法来根据提供商的结果导航到页面?
如果您仍在等待数据,您应该做的不是尝试导航到新视图,而是显示加载初始屏幕,一旦更改显示您的主主页视图,如下所示:
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Main extends StatefulWidget {
@override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
bool shouldProceed = Provider.of<ProductData>(context, listen: true).shouldProceed;
@override
Widget build(BuildContext context) {
if(shouldProceed){
return Home();
}else{
return RouteSplash();
}
}
}
在这个例子中使用 BlocListener:
BlocListener(
bloc: BlocProvider.of<DataBloc>(context),
listener: (BuildContext context, DataState state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: BlocBuilder(
bloc: BlocProvider.of<DataBloc>(context),
builder: (BuildContext context, DataState state) {
if (state is Initial) {
return Text('Press the Button');
}
if (state is Loading) {
return CircularProgressIndicator();
}
if (state is Success) {
return Text('Success');
}
if (state is Failure) {
return Text('Failure');
}
},
}
)
我想我有一个解决方案可以满足 OP 的要求。如果您将启动画面设置为 Stateful
,那么您可以添加一个 PostFrameCallback
。这避免了在 build
为 运行 时调用 Navigator
的任何问题。然后,您的回调可以调用 Provider
读取数据所需的任何例程。此读取数据例程可以传递包含 Navigator
命令的进一步回调。
在我的解决方案中,我添加了一个进一步的回调,以便启动画面至少显示一秒钟(您可以在此处选择您认为合理的持续时间)。不幸的是,这会造成竞争条件,因此我需要导入 synchronized
包以避免出现问题。
import 'package:flutter/material.dart';
import 'package:reflect/utils/constants.dart';
import 'category_screen.dart';
import 'package:provider/provider.dart';
import 'package:reflect/data_models/app_prefs.dart';
import 'dart:async';
import 'dart:core';
import 'package:synchronized/synchronized.dart';
class LoadingScreen extends StatefulWidget {
static const id = 'LoadingScreen';
@override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
bool readPrefsDone = false;
bool timeFinished = false;
Lock _lock = Lock();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<AppPrefs>(context, listen: false).readPrefs(readDone);
Timer(Duration(seconds: 1), () {
timerDone();
});
});
}
void timerDone() async {
_lock.synchronized(() {
if (readPrefsDone) {
pushMainScreen();
}
timeFinished = true;
});
}
void readDone() {
_lock.synchronized(() {
if (timeFinished) {
pushMainScreen();
}
readPrefsDone = true;
});
}
void pushMainScreen() {
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, animation, animation2) => CategoryScreen(),
transitionDuration: Duration(seconds: 1),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Hero(
tag: kFishLogoTag,
child: Image(
image: AssetImage('assets/fish_logo.png'),
),
),
SizedBox(
height: 30,
),
Text(
'Reflect',
style: TextStyle(
fontSize: 30,
color: Color(0xFF0000cc),
fontWeight: FontWeight.bold,
),
),
],
),
),
));
}
}
遇到此问题的任何其他人都可以使用此代码
Future.delayed(Duration.zero, () => Navigate.toView(context));
这会导航到另一个屏幕而不会出现构建错误