Flutter web set a white screen at height less than 500px - flutter

I would like to set a white screen when someone resizes the height of their tab to a small size
It works for the initial route however, I would like to do this for all my screens
Here is my code
class _ExcelitState extends State<Excelit> {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
key: navigatorKey,
debugShowCheckedModeBanner: false,
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) =>
const Scaffold(body: Center(child: Text('Page Not Found!!!'))),
);
},
// navigatorObservers: [observer],
routes: {
'/': (context) => (kIsWeb && setHeight(context, 0.01) <= 5)
? const EmptyScreen()
: const LoginScreen(),
},
);
}
}
class EmptyScreen extends StatelessWidget {
const EmptyScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: kBackgroundColor,);
}
}

I did the following:
IndexedStack would save the state and once the screen height on the web is below ~500px I change to the empty screen
yet I had to change all my Scaffolds to CustomScaffold
import 'package:excelit/constants/constants.dart';
import 'package:excelit/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class CustomScaffold extends StatelessWidget {
const CustomScaffold({Key? key, required this.widget}) : super(key: key);
final Widget widget;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kBackgroundColor,
resizeToAvoidBottomInset: true,
body: IndexedStack(
index: (kIsWeb && setHeight(context, 0.01) <= 5) ? 1 : 0,
children: [
widget,
const EmptyScreen(), //if height is reduced below ~500px show white screen
],
),
);
}
}
class EmptyScreen extends StatelessWidget {
const EmptyScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: kBackgroundColor,);
}
}

Related

Flutter app resizing on different devices

I installed my app on 2 different devices, the problem is both devices show different widget sizes. Both devices have different screen sizes.
The expected result ->
image1 from Moto One Fusion+
image2 from OnePlus 6pro
main.dart code ->
void main() {
runApp(BmiApp());
}
class BmiApp extends StatelessWidget {
const BmiApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/input': (context) => InputPage(),
'/calculate': (context) => CalculatedResult(),
},
theme: ThemeData.dark().copyWith(
appBarTheme: AppBarTheme(
color: Color(0xFF0A1234),
),
scaffoldBackgroundColor: Color(0xFF0A1234),
),
initialRoute: '/input',
);
}
}
input_page.dart code ->
class InputPage extends StatefulWidget {
const InputPage({Key? key}) : super(key: key);
#override
State<InputPage> createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('BMI Calculator'),
centerTitle: true,
),
body:Column(),
),
);
}
}
Inside column I have stacked all the widgets.
Github link for complete code

How to set splash screen time out on flutter

I am new to flutter and am kinda lost on how to set up a time to my splash screen so after this time it goes to the main screen. am using rive for the splash screen
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
void main() {
runApp(const MaterialApp(home: SimpleAnimation()));
}
class SimpleAnimation extends StatelessWidget {
const SimpleAnimation({Key? key, this.loading}) : super(key: key);
final bool? loading;
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: RiveAnimation.asset('assets/splash/splash.riv',
fit: BoxFit.cover)
),
);
}
}
You can set 3 second time in initstate after navigate to which screen you want
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
// after 3 second it will navigate
Future.delayed(const Duration(seconds: 3)).then((val) {
// Navigation Here
});
}
#override
Widget build(BuildContext context) {
return const Scaffold(
// your code
);
}
}
#override
void initState() {
//set timer for splash screen
Timer(const Duration(seconds: 4), () async {
//add your logic here
Navigator.pushNamedAndRemoveUntil(
context, ScreenRoute.mainScreen, (route) => false);
super.initState();
}
This SimpleAnimation widget shows after the splash screen. While this is StatelessWidget widget, you can define method inside build method. Change Duration(seconds: 2) based on your need.
class SimpleAnimation extends StatelessWidget {
const SimpleAnimation({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Future.delayed(const Duration(seconds: 2)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NextScreen(),
));
});
return const Scaffold(
body: Center(
As folks already mentioned the straighforward way would be to add a delay and do navigation after it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
}
});
}
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.green);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.yellow);
}
}
Though, with this implementation, you'll have to depend on the animation length. So when you'll update animation - you'll have not to forget to update it inside the splash screen. A more reliable (and complex) solution would be to listen to the animation status and do the navigation after the animation finishes. Like this (warning, change ):
class PlayOneShotAnimation extends StatefulWidget {
const PlayOneShotAnimation({Key? key}) : super(key: key);
#override
_PlayOneShotAnimationState createState() => _PlayOneShotAnimationState();
}
class _PlayOneShotAnimationState extends State<PlayOneShotAnimation> {
late RiveAnimationController _controller;
#override
void initState() {
super.initState();
_controller = OneShotAnimation(
'bounce',
autoplay: true,
onStop: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const MainScreen(),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
animations: const ['idle', 'curves'],
controllers: [_controller],
),
),
);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.yellow);
}
}
This is my approach for splash screen, the advantage of this approach is to make sure that the splash screen launch only once when the app starting.
First define a static bool in app home class to indicate the app launch.
static bool launch = true;
Then at the home attribute in your MaterialApp widget at app home class, check if (launch) is true use a FutureBuilder to launch the splash screen, if (launch) is false set home to your second screen. With FutureBuilder you can set a timer for your splash screen, when it done your second screen will start (credit to https://stackoverflow.com/a/68699447/11619215).
home: launch? FutureBuilder(
future: Future.delayed(const Duration(seconds: 3)),
builder: (ctx, timer) =>
timer.connectionState == ConnectionState.done
? const SecondScreen(title: 'Flutter Demo Home Page')
: appSplashScreen(),
): const SecondScreen(title: 'Flutter Demo Home Page'),
In the Second screen, check if (launch) is true then set it to false. This will make sure that the splash screen will only launch once each time your application start.
if(AppHome.launch) {
AppHome.launch = false;
}
Below is the full code with appSplashScreen widget at the bottom:
import 'package:flutter/material.dart';
void main() {
runApp(const AppHome());
}
class AppHome extends StatelessWidget {
const AppHome({Key? key}) : super(key: key);
//static bool to indicate the launching of the app
static bool launch = true;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: launch? FutureBuilder(
future: Future.delayed(const Duration(seconds: 3)),
builder: (ctx, timer) =>
timer.connectionState == ConnectionState.done
? const SecondScreen(title: 'Flutter Demo Home Page')
: appSplashScreen(),
): const SecondScreen(title: 'Flutter Demo Home Page'),
);
}
}
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
Widget build(BuildContext context) {
//mack sure your splash screen only launch once at your app starting
if(AppHome.launch) {
AppHome.launch = false;
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const Center(
child: Text(
'My Second screen',
),
),
);
}
}
Widget appSplashScreen() {
return Container(
decoration: const BoxDecoration(
////you can add background image/color to your splash screen
// image: DecorationImage(
// image: AssetImage('assets/background.png'),
// fit: BoxFit.cover,
// ),
color: Colors.white,
),
child: Center(
child: SizedBox(
//get MediaQuery from instance of window to get height and width (no need of context)
height: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.height*0.5,
width: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.width*0.5,
child: Column(
children: const [
////you can add image to your splash screen
// Image(
// image: AssetImage('assets/splashscreen_image.png'),
// ),
FittedBox(
child: Text(
'Loading',
textAlign: TextAlign.center,
style: TextStyle(
decoration: TextDecoration.none,
),
)
),
CircularProgressIndicator(),
],
),
),
),
);
}

Flutter Provider nested navigation

I have a problem with provider and navigation.
I have a HomeScreen with a list of objects. When you click on one object I navigate to a DetailScreen with tab navigation. This DetailScreen is wrapped with a ChangenotifierProvider which provides a ViewModel
Now, when I navigate to another screen with Navigator.of(context).push(EditScreen) I can't access the ViewModel within the EditScreen
The following error is thrown
════════ Exception caught by gesture ═══════════════════════════════════════════
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider<ViewModel> above this EditScreen Widget
This is a simple overview of what I try to achieve
Home Screen
- Detail Screen (wrapped with ChangeNotifierProvider)
- Edit Screen
- access provider from here
I know what the problem is. I'm pushing a new screen on the stack and the change notifier is not available anymore.
I thought about creating a Detail Repository on top of my App which holds all of the ViewModels for the DetailView.
I know I could wrap the ChangeNotifier around my MaterialApp, but I don't want that, or can't do it because I don't know which Detail-ViewModel I need. I want a ViewModel for every item in the list
I really don't know what's the best way to solve this. Thanks everyone for the help
Here is a quick example app:
This is a picture of the image tree
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("DetailView"),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (_) => ViewModel(), child: DetailScreen()))),
)));
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => EditScreen())),
),
));
}
}
class EditScreen extends StatelessWidget {
const EditScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("Print"),
onPressed: () =>
Provider.of<ViewModel>(context, listen: false).printNumber()),
),
);
}
}
class ViewModel extends ChangeNotifier {
printNumber() {
print(2);
}
}
To be able to access providers accross navigations, you need to provide it before MaterialApp as follows
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ViewModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("DetailView"),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DetailScreen(),
),
),
)));
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => EditScreen())),
),
));
}
}
class EditScreen extends StatelessWidget {
const EditScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("Print"),
onPressed: () =>
Provider.of<ViewModel>(context, listen: false).printNumber()),
),
);
}
}
class ViewModel extends ChangeNotifier {
printNumber() {
print(2);
}
}
A bit late to the party, but I think this is the answer the question was looking for:
(Basically passing the ViewModel down to the next Navigator page.)
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final viewModel = Provider.of<ViewModel>(context); // Get current ViewModel
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context).push(
// Pass ViewModel down to EditScreen
MaterialPageRoute(builder: (context) {
return ChangeNotifierProvider.value(value: viewModel, child: EditScreen());
}),
),
),
));
}
}
I am a bit late but I found a solution on how to keep the value of a Provider alive after a Navigator.push() without having to put the Provider above the MaterialApp.
To do so, I have used the library custom_navigator. It allows you to create a Navigator wherever you want in the tree.
You will have to create 2 different GlobalKey<NavigatorState> that you will give to the MaterialApp and CustomNavigator widgets. These keys will allow you to control what Navigator you want to use.
Here is a small snippet to illustrate how to do
class App extends StatelessWidget {
GlobalKey<NavigatorState> _mainNavigatorKey = GlobalKey<NavigatorState>(); // You need to create this key for the MaterialApp too
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _mainNavigatorKey; // Give the main key to the MaterialApp
home: Provider<bool>.value(
value: myProviderFunction(),
child: Home(),
),
);
}
}
class Home extends StatelessWidget {
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>(); // You need to create this key to control what navigator you want to use
#override
Widget build(BuildContext context) {
final bool myBool = Provider.of<bool>(context);
return CustomNavigator (
// CustomNavigator is from the library 'custom_navigator'
navigatorKey: _navigatorKey, // Give the second key to your CustomNavigator
pageRoute: PageRoutes.materialPageRoute,
home: Scaffold(
body: FlatButton(
child: Text('Push'),
onPressed: () {
_navigatorKey.currentState.push( // <- Where the magic happens
MaterialPageRoute(
builder: (context) => SecondHome(),
),
},
),
),
),
);
}
}
class SecondHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bool myBool = Provider.of<bool>(context);
return Scaffold(
body: FlatButton(
child: Text('Pop'),
onPressed: () {
Novigator.pop(context);
},
),
);
}
}
Here you can read the value myBool from the Provider in the Home widget but also ine the SecondHome widget even after a Navigator.push().
However, the Android back button will trigger a Navigator.pop() from the Navigator of the MaterialApp. If you want to use the CustomNavigator's one, you can do this:
// In the Home Widget insert this
...
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_navigatorKey.currentState.canPop()) {
_navigatorKey.currentState.pop(); // Use the custom navigator when available
return false; // Don't pop the main navigator
} else {
return true; // There is nothing to pop in the custom navigator anymore, use the main one
}
},
child: CustomNavigator(...),
);
}
...

Flutter Quick Actions change selected Bottom Navigation Bar item

I'm trying to implement home screen quick actions / app shortcuts in my Flutter app. What I'm trying to achieve is when the user launches my app via a quick action, the app changes the selected tab inside the bottom navigation bar. Any help is appreciated.
main.dart:
runApp(
MaterialApp(
theme: Themes.appLightTheme,
darkTheme: Themes.appDarkTheme,
home: QuickActionsController(
child: HomeFrame(currentIndex: 0),
),
My QuickActionsController class:
import 'package:binfinder/screens/HomeFrame.dart';
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
class QuickActionsController extends StatefulWidget {
final HomeFrame child;
QuickActionsController({Key key, this.child}) : super(key: key);
#override
_QuickActionsControllerState createState() => _QuickActionsControllerState();
}
class _QuickActionsControllerState extends State<QuickActionsController> {
final QuickActions quickActions = QuickActions();
int _currentIndex = 0;
#override
void initState() {
super.initState();
_handleQuickActions();
_setupQuickActions();
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_map',
localizedTitle: 'Map',
),
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_map') {
setState(() {
_currentIndex = 1;
});
} else {
setState(() {
_currentIndex = 0;
});
}
});
}
#override
Widget build(BuildContext context) {
widget.child.currentIndex = _currentIndex;
return widget.child;
}
}
In the demo below, direct click app will enter First Page and In Quick Action choose Main view will enter Second Page
_handleQuickActions need to use
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
and use initial index to control page index
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
full code
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'dart:io';
class QuickActionsManager extends StatefulWidget {
final Widget child;
QuickActionsManager({Key key, this.child}) : super(key: key);
_QuickActionsManagerState createState() => _QuickActionsManagerState();
}
class _QuickActionsManagerState extends State<QuickActionsManager> {
final QuickActions quickActions = QuickActions();
#override
void initState() {
super.initState();
_setupQuickActions();
_handleQuickActions();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_main',
localizedTitle: 'Main view',
icon: Platform.isAndroid ? 'quick_box' : 'QuickBox'),
ShortcutItem(
type: 'action_help',
localizedTitle: 'Help',
icon: Platform.isAndroid ? 'quick_heart' : 'QuickHeart')
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_main') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
} else if (shortcutType == 'action_help') {
print('Show the help dialog!');
}
});
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QuickActions Demo',
home: QuickActionsManager(child: BottomNavigationBarController(initialIndex: 0,)));
}
}
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Home')));
}
}
class Login extends StatelessWidget {
const Login({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Login')));
}
}
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
final List<Widget> pages = [
FirstPage(
key: PageStorageKey('Page1'),
),
SecondPage(
key: PageStorageKey('Page2'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('First Page')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('Second Page')),
],
);
#override
void initState() {
_selectedIndex = widget.initialIndex;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
demo, emulator is a little slow when enter Second Page

Flutter : Navigator operation requested with a context that does not include a Navigator

I have a scenario wherein I check the value of SharePreferences based on the value it will redirect the user to HomePage or LandingPage. I am not sure where did I got wrong? but I am getting this error below: I guess its not getting the context right any idea how do I get it?.
Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
E/flutter (11533): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
Here is my code:
import 'package:credit/src/pages/landing.dart';
import 'package:flutter/material.dart';
import 'package:credit/src/pages/credit/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<MyApp> {
#override
void initState() {
super.initState();
getUserStatus().then((userStatus) {
if (userStatus == null) {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return LandingPage();
}));
} else {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return HomePage();
}));
}
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userstatus');
print("==On Load Check ==");
print(userStatus);
return userStatus;
}
When you call Navigator.of(context) framework goes up in widget tree attached to provided context and tries to find the closest Navigator.
The widget tree you showed does not have one, so you need to include Navigator in the widget tree.
Easiest option is to use MaterialApp with your widget passed as home. MaterialApp is creating navigator inside itself. (CupertinoApp does it too)
Updated code from original example:
import 'package:credit/src/pages/landing.dart';
import 'package:flutter/material.dart';
import 'package:credit/src/pages/credit/home.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoadingPage(),
);
}
}
class LoadingPage extends StatefulWidget {
LoadingPage({Key key}) : super(key: key);
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> { // note type update
#override
void initState() {
super.initState();
getUserStatus().then((userStatus) {
if (userStatus == null) {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return LandingPage();
}));
} else {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
return HomePage();
}));
}
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userstatus');
print("==On Load Check ==");
print(userStatus);
return userStatus;
}
I have changed my code from
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo App',
theme: ThemeData(
primarySwatch: white,
scaffoldBackgroundColor: Colors.white,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
HomeScreen(title: 'Demo Home')));
},
child: Text('Open Home Screen'))
],
),
),
),
);
}
To
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo App',
theme: ThemeData(
primarySwatch: white,
scaffoldBackgroundColor: Colors.white,
),
home: InitScreen());
}
}
class InitScreen extends StatelessWidget {
const InitScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
HomeScreen(title: 'Demo Home')));
},
child: Text('Open Home Screen'))
],
),
),
);
}
What changed?
Create a separate widget for home code in MyApp with InitScreen
What was the issue?
When we try to push Route by using Navigator.of(context), flutter will
try to find Navigator in the widget tree of the given context. In the
initial code, there was no widget that has Navigator. So, create a
separate widget for home code. And the MaterialApp widget in MyApp
will have Navigator.