Navigation problem with FutureBuilder and MaterialApp
Now try the following. Try to make a root widget separately, because root widget is always there. you don't want a complete UI route to persist in the memory. Also make next route as a separate widget.
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(), ), ); }); }}
so I think I have found the error, actually you must have an MaterialApp
inside runApp()
as root.
so you can't have MaterialApp
inside FutureBuilder
what you can do is make MaterialApp
the root widget and have a default Home Screen and inside its build method you can have your FutureBuilder
but again don't include materialApp
inside it just use Scaffold directly.
EDIT :To answer the question regarding app theme
You can have switching themes by usingtheme
and darkTheme
in materialApp And control themeMode
from Provider
or any other state management approach.
MaterialApp( title: 'Flutter Tutorials', debugShowCheckedModeBanner: false, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: appState.isDarkModeOn ? ThemeMode.dark : ThemeMode.light, home: ThemeDemo(), );
There are several ways to do it here is one more that I found custom theme app
Try this out it will work, if doesn't let me know
I think you can do as follow:
Move all Future data/FutureBuilder into different Stateless/Stateful Widget and override the theme color with
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(), ), ); } }, ); }}
The first page use the local variable to store color
...Color _primaryColor;@overridevoid initState() { _primaryColor = Theme.of(context).primaryColor; super.initState();}@overrideWidget build(BuildContext context) { return MaterialApp( theme: ThemeData(primaryColor: _primaryColor), home: _buildPage(), );}...
If you want the first page update the theme on the same time, you should use some method to share data between widget (e.g. Provider). I use the simple method to catch the custom return value
// 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( ...
If it is not necessary for you to use Routing
when you try to change Theme,I can provide a simple solution that change the theme data by Theme
class
ThemeData currentTheme;@overridevoid initState() { super.initState(); currentTheme = Theme.of(context);}@overrideWidget 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, ); }, ), );}
Here is the document of Themes for part of an application