FutureBuilder 和 MaterialApp 的导航问题
Navigation problem with FutureBuilder and MaterialApp
我的应用有一个计算为 Future 的状态。
例如,它包含主题颜色,因为我想在导航时更改颜色。
我尝试在等待数据时显示进度指示器。
但我做不到。
Navigator.push 无法正常工作并且应用栏丢失,或者我没有进度指示器和路线错误...
这是一个代码片段。
import 'package:flutter/material.dart';
void main() => runApp(Test());
class Test extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<Color> color = Model.getColor();
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
if (snapshot.hasError) throw snapshot.error;
if (snapshot.connectionState != ConnectionState.done) {
if (false) {
// Navigation not working. App bar missing.
return Material(child: Center(child: CircularProgressIndicator()));
} else {
// Progress not working. Screen flickering.
return MaterialApp(home: _buildWait());
}
}
var app = MaterialApp(
theme: ThemeData(primaryColor: snapshot.data),
home: _buildPage(),
// ERROR: The builder for route "/" returned null.
// routes: {'/': (_) => _buildPage()},
);
return app;
},
);
}
Widget _buildPage() {
return Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
setState(() {
color = Model.getColor();
});
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Scaffold(appBar: AppBar());
}));
},
),
),
);
},
);
}
}
Widget _buildWait() {
return Scaffold(
appBar: AppBar(title: Text('Wait...')),
body: Center(child: CircularProgressIndicator()),
);
}
class Model {
static final _colors = [Colors.red, Colors.green, Colors.amber];
static int _index = 0;
static Future<Color> getColor() {
return Future.delayed(Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
}
}
预期结果:当我按下按钮导航到新路线时,它应该显示一个进度指示器,然后是具有不同主题颜色的新屏幕。
我删除了 _buildPage
小部件中的 return Builder
部分,它似乎可以正常工作。它还在 AppBar
.
中显示 CircularProgressIndıcator
和 Wait...
文本
这是编辑后的代码:
Widget _buildPage() {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
setState(() {
color = Model.getColor();
});
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Scaffold(appBar: AppBar());
}));
},
),
),
);
}
所以我想我发现了错误,实际上你必须在 runApp()
里面有一个 MaterialApp
作为 root。
所以 FutureBuilder
里面不能有 MaterialApp
您可以做的是使 MaterialApp
成为根小部件并具有默认的主屏幕,并且在其构建方法中您可以拥有 FutureBuilder
但同样不要在其中包含 materialApp
只需使用脚手架直接。
编辑:
回答关于应用主题的问题
您可以通过使用切换主题
theme
和 darkTheme
在 materialApp 和控制 themeMode
从 Provider
或任何其他状态管理方法。
MaterialApp(
title: 'Flutter Tutorials',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: appState.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
home: ThemeDemo(),
);
有几种方法可以做到这一点,这是我找到的另一种方法 custom theme app
尝试一下它会起作用,如果不告诉我
现在尝试以下操作。尝试单独制作一个根小部件,因为根小部件始终存在。您不希望完整的 UI 路线保留在内存中。还将下一条路线作为单独的小部件。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Test(),
);
}
}
class Test extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<Color> color = Model.getColor();
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
if (snapshot.hasError) return Center(child: Text("An Error Occurred"));
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildWait();
}
var app = Theme(
data: ThemeData(primaryColor: snapshot.data),
child: _buildPage(),
);
return app;
},
);
}
Widget _buildPage() {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return NextRoute();
}));
},
),
),
);
}
}
Widget _buildWait() {
return Scaffold(
appBar: AppBar(title: Text('Wait...')),
body: Center(child: CircularProgressIndicator()),
);
}
class Model {
static final _colors = [Colors.red, Colors.green, Colors.amber];
static int _index = 0;
static Future<Color> getColor() {
return Future.delayed(
Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
}
}
class NextRoute extends StatefulWidget {
NextRoute({Key key}) : super(key: key);
@override
_NextRouteState createState() => _NextRouteState();
}
class _NextRouteState extends State<NextRoute> {
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: Model.getColor(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text("An Error Occurred"),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildWait();
}
return Theme(
data: ThemeData(primaryColor: snapshot.data),
child: Scaffold(
appBar: AppBar(),
),
);
});
}
}
我想你可以这样做:
将所有 Future data/FutureBuilder 移动到不同的 Stateless/Stateful 小部件中并使用 Theme
class
覆盖主题颜色
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var color = Theme.of(context).primaryColor;
return FutureBuilder(
future: Model.getColor(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Theme(
data: ThemeData(primaryColor: color),
child: _buildWait(),
);
} else {
color = snapshot.data;
return Theme(
data: ThemeData(primaryColor: color),
child: Scaffold(
appBar: AppBar(),
),
);
}
},
);
}
}
首页使用局部变量存储颜色
...
Color _primaryColor;
@override
void initState() {
_primaryColor = Theme.of(context).primaryColor;
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: _primaryColor),
home: _buildPage(),
);
}
...
如果你想让第一页同时更新主题,你应该使用一些方法在widget之间共享数据(例如Provider)。我使用简单的方法来捕获自定义 return 值
// First Page
// use "then" can get the return value from the other route
...
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondPage();
})).then((color) {
setState(() {
_primaryColor = color;
});
});
},
...
// Second Page
// WillPopScope can catch navigator pop event
...
return WillPopScope(
onWillPop: () async {
Navigator.of(context).pop(color);
return Future.value(false);
},
child: FutureBuilder(
...
如果您尝试更改主题时没有必要使用Routing
,
我可以提供一个简单的解决方案,通过 Theme
class
更改主题数据
ThemeData currentTheme;
@override
void initState() {
super.initState();
currentTheme = Theme.of(context);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
Widget child;
if (snapshot.hasError) throw snapshot.error;
if (snapshot.connectionState != ConnectionState.done) {
child = Scaffold(
appBar: AppBar(),
body: const Center(child: CircularProgressIndicator()),
);
}else{
currentTheme = currentTheme.copyWith(primaryColor: snapshot.data);
child = _buildPage();
}
return Theme(
data: currentTheme,
child: child,
);
},
),
);
}
的文档
我的应用有一个计算为 Future 的状态。 例如,它包含主题颜色,因为我想在导航时更改颜色。 我尝试在等待数据时显示进度指示器。
但我做不到。 Navigator.push 无法正常工作并且应用栏丢失,或者我没有进度指示器和路线错误...
这是一个代码片段。
import 'package:flutter/material.dart';
void main() => runApp(Test());
class Test extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<Color> color = Model.getColor();
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
if (snapshot.hasError) throw snapshot.error;
if (snapshot.connectionState != ConnectionState.done) {
if (false) {
// Navigation not working. App bar missing.
return Material(child: Center(child: CircularProgressIndicator()));
} else {
// Progress not working. Screen flickering.
return MaterialApp(home: _buildWait());
}
}
var app = MaterialApp(
theme: ThemeData(primaryColor: snapshot.data),
home: _buildPage(),
// ERROR: The builder for route "/" returned null.
// routes: {'/': (_) => _buildPage()},
);
return app;
},
);
}
Widget _buildPage() {
return Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
setState(() {
color = Model.getColor();
});
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Scaffold(appBar: AppBar());
}));
},
),
),
);
},
);
}
}
Widget _buildWait() {
return Scaffold(
appBar: AppBar(title: Text('Wait...')),
body: Center(child: CircularProgressIndicator()),
);
}
class Model {
static final _colors = [Colors.red, Colors.green, Colors.amber];
static int _index = 0;
static Future<Color> getColor() {
return Future.delayed(Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
}
}
预期结果:当我按下按钮导航到新路线时,它应该显示一个进度指示器,然后是具有不同主题颜色的新屏幕。
我删除了 _buildPage
小部件中的 return Builder
部分,它似乎可以正常工作。它还在 AppBar
.
CircularProgressIndıcator
和 Wait...
文本
这是编辑后的代码:
Widget _buildPage() {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
setState(() {
color = Model.getColor();
});
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Scaffold(appBar: AppBar());
}));
},
),
),
);
}
所以我想我发现了错误,实际上你必须在 runApp()
里面有一个 MaterialApp
作为 root。
所以 FutureBuilder
里面不能有 MaterialApp
您可以做的是使 MaterialApp
成为根小部件并具有默认的主屏幕,并且在其构建方法中您可以拥有 FutureBuilder
但同样不要在其中包含 materialApp
只需使用脚手架直接。
编辑: 回答关于应用主题的问题
您可以通过使用切换主题
theme
和 darkTheme
在 materialApp 和控制 themeMode
从 Provider
或任何其他状态管理方法。
MaterialApp(
title: 'Flutter Tutorials',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: appState.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
home: ThemeDemo(),
);
有几种方法可以做到这一点,这是我找到的另一种方法 custom theme app
尝试一下它会起作用,如果不告诉我
现在尝试以下操作。尝试单独制作一个根小部件,因为根小部件始终存在。您不希望完整的 UI 路线保留在内存中。还将下一条路线作为单独的小部件。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Test(),
);
}
}
class Test extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<Color> color = Model.getColor();
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
if (snapshot.hasError) return Center(child: Text("An Error Occurred"));
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildWait();
}
var app = Theme(
data: ThemeData(primaryColor: snapshot.data),
child: _buildPage(),
);
return app;
},
);
}
Widget _buildPage() {
return Scaffold(
appBar: AppBar(),
body: Center(
child: RaisedButton(
child: Text('Push'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return NextRoute();
}));
},
),
),
);
}
}
Widget _buildWait() {
return Scaffold(
appBar: AppBar(title: Text('Wait...')),
body: Center(child: CircularProgressIndicator()),
);
}
class Model {
static final _colors = [Colors.red, Colors.green, Colors.amber];
static int _index = 0;
static Future<Color> getColor() {
return Future.delayed(
Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
}
}
class NextRoute extends StatefulWidget {
NextRoute({Key key}) : super(key: key);
@override
_NextRouteState createState() => _NextRouteState();
}
class _NextRouteState extends State<NextRoute> {
@override
Widget build(BuildContext context) {
return FutureBuilder<Color>(
future: Model.getColor(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text("An Error Occurred"),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildWait();
}
return Theme(
data: ThemeData(primaryColor: snapshot.data),
child: Scaffold(
appBar: AppBar(),
),
);
});
}
}
我想你可以这样做:
将所有 Future data/FutureBuilder 移动到不同的 Stateless/Stateful 小部件中并使用
覆盖主题颜色Theme
classclass SecondPage extends StatelessWidget { @override Widget build(BuildContext context) { var color = Theme.of(context).primaryColor; return FutureBuilder( future: Model.getColor(), builder: (context, snapshot) { if (!snapshot.hasData) { return Theme( data: ThemeData(primaryColor: color), child: _buildWait(), ); } else { color = snapshot.data; return Theme( data: ThemeData(primaryColor: color), child: Scaffold( appBar: AppBar(), ), ); } }, ); } }
首页使用局部变量存储颜色
... Color _primaryColor; @override void initState() { _primaryColor = Theme.of(context).primaryColor; super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: _primaryColor), home: _buildPage(), ); } ...
如果你想让第一页同时更新主题,你应该使用一些方法在widget之间共享数据(例如Provider)。我使用简单的方法来捕获自定义 return 值
// First Page // use "then" can get the return value from the other route ... onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return SecondPage(); })).then((color) { setState(() { _primaryColor = color; }); }); }, ... // Second Page // WillPopScope can catch navigator pop event ... return WillPopScope( onWillPop: () async { Navigator.of(context).pop(color); return Future.value(false); }, child: FutureBuilder( ...
如果您尝试更改主题时没有必要使用Routing
,
我可以提供一个简单的解决方案,通过 Theme
class
ThemeData currentTheme;
@override
void initState() {
super.initState();
currentTheme = Theme.of(context);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder<Color>(
future: color,
builder: (context, snapshot) {
Widget child;
if (snapshot.hasError) throw snapshot.error;
if (snapshot.connectionState != ConnectionState.done) {
child = Scaffold(
appBar: AppBar(),
body: const Center(child: CircularProgressIndicator()),
);
}else{
currentTheme = currentTheme.copyWith(primaryColor: snapshot.data);
child = _buildPage();
}
return Theme(
data: currentTheme,
child: child,
);
},
),
);
}
的文档