Navigation problem with FutureBuilder and MaterialApp - flutter

My app has a state which is computed as a Future.
For example it includes a theme color, because I want to change the color when I navigate.
I try to display a progress indicator while waiting for the data.
But I can't make it work.
Either Navigator.push is not working and the app bar is missing, or I have no progress indicator and a route error...
Here is a code snippet.
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]);
}
}
Expected result: when I push the button to navigate to the new route, it should display a progress indicator, and then the new screen with a different theme color.

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 using
theme 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 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(),
),
);
}
},
);
}
}
The first page use the local variable to store color
...
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(),
);
}
...
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;
#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,
);
},
),
);
}
Here is the document of Themes for part of an application

I have removed the return Builder section in the _buildPage widget and it seems to work. It also shows the CircularProgressIndıcator and Wait... text in AppBar.
Here is the edited code:
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());
}));
},
),
),
);
}

Related

Could not find the correct Provider<TokenBloc> above this App Widget

I using BLoC. How to create it correctly, what would not arise due to the lack of widgets down the widget tree. Now I usually like this:
Widget build(BuildContext context) {
return MaterialApp(
// debugShowCheckedModeBanner: false,
theme: Styles.appTheme,
home: BlocProvider<TokenBloc>(
create: (context) => di.sl<TokenBloc>(),
child: _childTokenBloc,
),
);
}

Widget get _childTokenBloc {
return BlocBuilder<TokenBloc, TokenState>(builder: (context, state) {
if (state is TokenInitialState) {
context.read<TokenBloc>().add(TokenCheckEvent());
return const LogoImage();
}
if (state is TokenCheckState) {
return const LogoImage();
}
if (state is TokenOkState) {
return MainPageWidget();
}
if (state is TokenNoAuthorizationState) {
return const AuthorizationPageWidget();
}
return const LogoImage();
}
);
}
In AuthorizationPageWidget I do:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ConfirmAuthorizationPage()),
);
And from ConfirmAuthorizationPage I try to turn to TokenBloc:
context.read<TokenBloc>().add(TokenAddEvent());
but I get Error: Could not find the correct Provider above this App Widget
I thought that TokenBloc would be found in the widget tree, but is it not? And how to fix this problem? Need to use MultiBlocProvider in the build method of the ConfirmAuthorizationPage widget? It will be re-initialized, and the previous one will not be used.
Update 1:
Code AuthorizationPageWidget:
class AuthorizationPageWidget extends StatefulWidget {
const AuthorizationPageWidget({Key? key}) : super(key: key);
#override
_AuthorizationPageWidgetState createState() =>
_AuthorizationPageWidgetState();
}
class _AuthorizationPageWidgetState extends State<AuthorizationPageWidget> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<AuthorizationBloc>(
create: (context) => sl<AuthorizationBloc>(),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_title,
_description,
Expanded(child: Align(alignment: FractionalOffset.bottomCenter, child: _bottomButton))
],
),
),
),
);
}
//......
void pushConfirmPage(String number) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ConfirmAuthorizationPage(number: number,)),
);
}
}
If you want to provide your Bloc in all your application, you have to write it in your MaterialApp like this, not in the body ;
return
BlocProvider<TokenBloc>( // like this
create: (context) => TokenBloc(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: _TokenHome(),
),
),
);
class _TokenHome extends StatelessWidget { // use a class instead of function
const _TokenHome({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<TokenBloc, TokenState>(
builder: (context, state) {
if (state is TokenInitialState) {
context.read<TokenBloc>().add(TokenCheckEvent());
return const LogoImage();
}
if (state is TokenCheckState) {
return const LogoImage();
}
if (state is TokenOkState) {
return MainPageWidget();
}
if (state is TokenNoAuthorizationState) {
return const AuthorizationPageWidget();
}
return Container(
width: 50,
height: 50,
color: Colors.red,
); // use this if there is not a state
}
);
}
}
If for some reason it doesn't show anything anymore, then it's because some of your classes like AuthorizationPageWidget or LogoImage are wrong, check that.
-------- EDIT
Using BlocProvider on each page can be useful, but keep in mind that for example AuthorizationBloc will only work for its children, if you call it on another side of the screen it will not work, so it is highly recommended to use a MultiBlocProvider in MaterialApp to avoid future problems;
return MultiBlocProvider( // like this
providers: [
BlocProvider<TokenBloc>(
create: (context) => TokenBloc(),
),
BlocProvider<AuthorizationBloc>(
create: (context) => AuthorizationBloc(),
),
],
child: BlocBuilder<LanguageCubit, Locale?>(
builder: (context, lang) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
);
},
),
);
So all the other BlocProvider that you use to create, delete them, you do not need them, now if you use a BlocBuilder, BlocListeners of any Bloc, you would not have any inconvenience.

How can I await for data to be returned from a screen or dialog using a FutureBuilder?

I'd like a FutureBuilder to wait for the user to make a selection in a route or dialog (example below) and then return that data to the builder. However, the dialog never appears.
How can I await for data to be returned from a screen or dialog using a FutureBuilder?
DartPad
import 'package:flutter/material.dart';
void main() => runApp(HomeScreen());
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder(
future: launch(context),
builder: (context, snapshot) {
// use result from screen or dialog in snapshot.data
return Center(child: Text(snapshot.data));
}),
),
);
}
Future launch(BuildContext context) async {
return await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
actions: <Widget>[
FlatButton(
child: Text('Send Data'),
onPressed: () {
// return some data
Navigator.pop(context, 'Some data!');
},
),
],
content: Container(child: Text('')),
);
},
);
}
}
You can copy paste run full code below
Step 1: You need to use addPostFrameCallback
Step 2: move MaterialApp to upper level
Step 3: check ConnectionState
code snippet
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_future = launch(context);
});
});
super.initState();
}
Future<String> launch(BuildContext context) async {
var result = await showDialog(
...
print("result $result");
return Future.value(result);
}
working demo dartpad link
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<String> _future;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_future = launch(context);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: FutureBuilder(
future: _future,
builder: (context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snapshot.hasError) {
return Text(
'${snapshot.error}',
style: TextStyle(color: Colors.red),
);
} else {
return Center(child: Text("his this is ${snapshot.data}"));
}
}
}),
),
);
}
Future<String> launch(BuildContext context) async {
var result = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
actions: <Widget>[
FlatButton(
child: Text('Send Data'),
onPressed: () {
// return some data
Navigator.pop(context, 'Some data!');
},
),
],
content: Container(child: Text('')),
);
},
);
print("result $result");
return Future.value(result);
}
}
Thanks for reverting use the future Completer to get the data from Dialogue
create a Completer instance
var dialogueFuture = Completer();
feed the future of completer to Future builder
FutureBuilder{
future : dialogueFuture.future,
...
}
in show dialogue function complete the Future like this
var theData = await showDialogue(...)
dialogFuture.complete(theData);
see the dartpad here

Flutter screen(list) is reloading after coming back from another screen(detail)

I have created a Flutter application with a list. On tap of an item, I am opening detail of that item.
The problem is whenever I come back from the detail screen, the list screen is reloaded. I don't want to reload the list every time.
I have used BloC architecture in this.
Below are the code snippets. Please suggest.
Thank You.
Main
void main() {
final userRepository = UserRepository();
ApiClient apiClient = ApiClient(httpClient: http.Client());
runApp(BlocProvider<AuthenticationBloc>(
builder: (context) {
return AuthenticationBloc(
userRepository: userRepository, apiClient: apiClient)
..dispatch(AppStarted());
},
child: MyApp(userRepository: userRepository),
));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final UserRepository userRepository;
MyApp({Key key, #required this.userRepository}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
bloc: BlocProvider.of<AuthenticationBloc>(context),
builder: (context, state) {
if (state is AuthenticationUninitialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return HomePage(userRepository: userRepository);
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(userRepository: userRepository);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
return null;
},
),
);
}
}
List Screen
class HomePage extends StatelessWidget {
UserRepository userRepository;
HomePage({#required this.userRepository}) : super();
#override
Widget build(BuildContext context) {
ApiClient apiClient = ApiClient(httpClient: http.Client());
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
drawer: AppDrawer(userRepository),
body: BlocProvider(
builder: (context) {
return HomeBloc(apiClient);
},
child: _HomeContent(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.amberAccent,
),
);
}
}
class _HomeContent extends StatelessWidget {
#override
Widget build(BuildContext context) {
final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.dispatch(FetchMovieList());
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
if (state is MovieListLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is MovieListLoaded) {
List<Movie> topRatedMovies = state.movieList;
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: Card(
child: Row(
children: <Widget>[
Image.network(ApiClient.IMAGE_BASE_URL +
topRatedMovies[index].poster_path),
Column(
children: <Widget>[
Text(topRatedMovies[index].title),
],
)
],
),
),
onTap: () {
_onListItemTapped(topRatedMovies[index].id, context);
},
);
},
itemCount: topRatedMovies.length,
);
}
if (state is MovieListError) {
return Center(
child: Text('Error in calling API'),
);
}
return Center(child: Text('Employee data not found'));
},
);
}
void _onListItemTapped(int movieId, BuildContext context) {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => MovieDetailPage(
movieId: movieId,
)));
}
}
At anytime your build method needs to be ready for multiple build calls. If build calls are causing problem then something is probably wrong. It would be a better idea to fetch the data outside the build method to prevent unnecessary API calls.
For example you can create a Stateful Widget and in initState method you can fetch the data. After that, build method is called to prepare UI with the data. You can use a Future Builder to show progress and update UI when the data is fetched.
Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future _future;
Future getData() async {
// Fetch data
}
#override
void initState() {
super.initState();
_future = getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _future,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.data.hasErrors) {
return Text('Error: ${snapshot.data.errors}');
} else {
// Data is fetched, build UI
return ListTile();
}
}
});
}
}

Cannot find the 'arguments' in Navigator.pushNamed when pushing to next screen

I am trying to move signup screen to OTP screen. From Signup screen I need to pass email id to OTP screen. Now, I am using below, but cannot resolve the arguments: parameter in that.
Navigator.pushNamed(context, Routes.ROUTE_OTP,arguments:{"id": 'email'});
Searched so many tutorials they given there to use arguments:( but my bad I can't find:(
A simple example demonstrating your requirement follows:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (settings) {
WidgetBuilder builder;
Map arguments = settings.arguments;
switch (settings.name) {
case '/':
builder = (
BuildContext _,
) =>
SignUp();
break;
case '/otp':
builder = (
BuildContext _,
) =>
Otp(id: arguments["id"]);
break;
default:
return null;
}
return MaterialPageRoute(builder: builder, settings: settings);
},
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Signup"),
),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.of(context).pushNamed(
"/otp",
arguments: {
"id": "email#email.com",
},
);
},
child: Text("SEND OTP")),
),
);
}
}
class Otp extends StatelessWidget {
final String id;
Otp({this.id});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("OTP"),
),
body: Center(
child: Text(id),
),
);
}
}

How solve problem Appbar icon clicked to navigate

I try to develop flutter application. In this application I used Appbar with user icon to navigate another page.
Now I am clicked that icon(person icon) it shows error.
.
It has not proper documentation though internet. I couldn't found answer. My source code is
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Web Issue finder"),
actions: <Widget>[
new IconButton(
icon: Icon(Icons.person),
// tooltip: "Admin",
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (_) => AdminAuth()),
);
}
)
],
),
body: new FutureBuilder(
future: loadStates(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
itemBuilder: (context, index) {
if (index >= snapshot?.data?.length ?? 0) return null;
return new ListTile(
title: new Text("${snapshot.data[index]}"),
onTap: () {
debugPrint("${snapshot.data[index]} clicked");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
IssueAddScreen(state: snapshot.data[index]),
),
);
},
);
},
);
} else {
return new Center(child: new CircularProgressIndicator());
}
})));
}
this is navigated class
import 'package:flutter/material.dart';
class AdminAuth extends StatelessWidget{
// final String state;
// IssueAddScreen({Key key, #required this.state}) : super(key: key);
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "iWallet",
home: Scaffold(
appBar: AppBar(title: Text("admin auth"),),
body: Text("cvgbh"),
),
);
}
}
Still I can't fix that error I am followed some documentation and stack overflow questions.
flutter documenttation
Github question and answer
Stackoverflow question and answer
Try to use context in your builder
Navigator.push(context,MaterialPageRoute(builder: (BuildContext context){return AdminAuth();
});
The issue here is with Navigator not present in the parent context.
You are using a context for the MyApp which isn't under the navigator.
MyApp <------ context
--> MaterialApp
(--> Navigator built within MaterialApp)
--> Scaffold
--> App Bar
--> ...
to solve this - Define new class that contain MaterialApp then pass MyApp() in home: of MaterialApp.
Same for the AdminAuth.
class MyAppHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("Web Issue finder"),
actions: <Widget>[
new IconButton(
icon: Icon(Icons.person),
// tooltip: "Admin",
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => AdminAuth()),
);
})
],
),
body: new FutureBuilder(
future: loadStates(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
itemBuilder: (context, index) {
if (index >= snapshot?.data?.length ?? 0) return null;
return new ListTile(
title: new Text("${snapshot.data[index]}"),
onTap: () {
debugPrint("${snapshot.data[index]} clicked");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
IssueAddScreen(state: snapshot.data[index]),
),
);
},
);
},
);
} else {
return new Center(child: new CircularProgressIndicator());
}
}));
}
}
The problem is the one explained above.
In my own words:
The context from which you are calling "Navigator" does not contain a "Navigator".
I guess the problem is that in you code you call Scaffold before MaterialApp complete the build method and get a Navigator or something like that.
If you separate the MaterialApp and the Scaffold (like below) you solve the problem.
void main() => runApp(MaterialApp(
home: MyApp())
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Web Issue finder"),
actions: <Widget>[
new IconButton(
icon: Icon(Icons.person),
tooltip: "Admin",
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (_) => AdminAuth()),
);
}
)
],
),
body: new FutureBuilder(
future: loadStates(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
itemBuilder: (context, index) {
if (index >= snapshot?.data?.length ?? 0) return null;
return new ListTile(
title: new Text("${snapshot.data[index]}"),
onTap: () {
debugPrint("${snapshot.data[index]} clicked");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
IssueAddScreen(state: snapshot.data[index]),
),
);
},
);
},
);
} else {
return new Center(child: new CircularProgressIndicator());
}
})));
There is some issue with MateriaApp context in the library.
Your code will not work. Create a different MaterialApp and then use your widget in home: property of MaterialApp.
For example:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
home: MyAppState(), //This is your MyAppState
);
}
}
Now you can remove MaterialApp widget in your MyAppState keeping only Scaffold Widget
IconButton(onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(builder: (context)=> AdminAuth));
},)