Flutter Context Error in Navigator pushReplacement - flutter

I am creating a Splash Screen and I am getting Context error in Navigator push replacement
Following is the code for splash screen in main.dart file
import 'dart:async';
import 'package:number_trivia/pages/home.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: splash_screen(),
));
}
class splash_screen extends StatefulWidget {
#override
_splash_screenState createState() => _splash_screenState();
}
class _splash_screenState extends State<splash_screen> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>home()
)
)
);
}
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FlutterLogo(size: MediaQuery.of(context).size.height,),
);
}
}
The error says - The argument type 'JsObject' can't be assigned to the parameter type 'BuildContext'.
How do I correct it?
Any help will be much appreciated:)

When widget build completed, you can call Timer function.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Timer(Duration(seconds: 3), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>home()
)
);
});
});
}

to that pushReplacement method, you passed a context which wasn't specified upper in the widget tree.
try wrapping the screen with a widget that has a build method so that it creates a BuildContext that you can use.
like this:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: splash_screen(context),
);
}
}
class splash_screen extends StatefulWidget {
BuildContext context;
splash_screen(this.context);
#override
_splash_screenState createState() => _splash_screenState();
}
class _splash_screenState extends State<splash_screen> {
#override
void initState() {
super.initState();
Timer(
Duration(seconds: 3),
() => Navigator.pushReplacement(
widget.context, MaterialPageRoute(builder: (context) => home())));
}
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FlutterLogo(
size: MediaQuery.of(context).size.height,
),
);
}
}
does that help?

Related

Flutter splash screen error - Navigator operation requested with a context that does not include a Navigator. How can I solve this error

Edit: (main.dart)
Added Sentry which actually starts the app
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const SplashScreen()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
New to flutter, creating a splash screen to an app that was built with MaterialApp but getting an error. HOw can I solve this without a onPress function
Error:
Exception has occurred.
FlutterError (Navigator operation requested with a context that does not include a Navigator.
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.)
import 'package:flutter/material.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
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();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Thank you in advance.
EDIT: I changed the solution after you provided more information about the code.
This error is happening because you are using a context that does not have a Navigator in it, this is happening probrably because the widget that you are getting the context is parent of the MaterialApp() widget, to solve it you should create another widget that is a child of the MaterialApp() instead of using the parent widget, let me give you an example instead:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
This may give an error because you are using the context of a widget that is the parent of the MaterialApp() widget, to solve it just create another widget that is a child of MaterialApp().
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: AnotherWidget(),
);
}
}
class AnotherWidget extends StatelessWidget {
const AnotherWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
I was playing with your code, and fixed it for you, and there are basically two ways to solve it, you can create a MaterialApp() before calling the SplashScreen() in the runApp() function like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(
const MaterialApp(
home: SplashScreen(),
),
),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
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();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Or you can create an intermediate widget to hold the MaterialApp() and then inside this widget you can call SplashScreen(), like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const MyApp()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
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();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
In this second solution, the intermediate widget is the MyApp() widget, and in my opinion, I consider this solution as being the best one for your problem, because if you ever wanted to load a different screen based on the different states, for example:
If a user is signed in you load a home page, and if a user is not signed in you load a sign up page.
Doing this, or anything similar is much easier when you have this intermediate widget that holds the MaterialApp(), and you can even create some logic to integrate the Splash Screen too, but I don't know what you are trying to achieve, so pick the solution you find the best for your problem.

Flutter Argument passing

I was trying to pass argument to a button widget but I'm getting below error message.
Here is my argument:
ElevatedRegisterButton(
navigator: Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return const RegisterPage1();
})))
Here is my widget where I was trying to pass argument:
import 'package:flutter/material.dart';
class ElevatedRegisterButton extends StatefulWidget {
const ElevatedRegisterButton({super.key, required this.navigator});
final String navigator;
#override
State<ElevatedRegisterButton> createState() => _ElevatedRegisterButtonState();
}
class _ElevatedRegisterButtonState extends State<ElevatedRegisterButton> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () {
widget.navigator;
},
child: const Text('Register'),
);
}
}
Here is the error message I'm getting:
The argument type 'Future' can't be assigned to the parameter type 'String'.
You need to set the navigator member to type final void Function(), because the onPressed property of ElevatedButton requires this type. You also need to pass it differently, because push is another type of function. Lastly, simply set onPressed to widget.navigator.
An example code is below based on your code snippet:
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) =>
const MaterialApp(home: Scaffold(body: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) => SafeArea(
// see the difference, push is within () {}
child: ElevatedRegisterButton(navigator: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const RegisterPage1()));
}),
);
}
class ElevatedRegisterButton extends StatefulWidget {
const ElevatedRegisterButton({super.key, required this.navigator});
final void Function() navigator;
#override
State<ElevatedRegisterButton> createState() => _ElevatedRegisterButtonState();
}
class _ElevatedRegisterButtonState extends State<ElevatedRegisterButton> {
#override
Widget build(BuildContext context) => ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
// here simply set the function
onPressed: widget.navigator,
child: const Text('Register'),
);
}
// added for demo purposes
class RegisterPage1 extends StatelessWidget {
const RegisterPage1({super.key});
#override
Widget build(BuildContext context) =>
const Scaffold(body: SafeArea(child: Text('RegisterPage1')));
}
Problem 1
In your code:
class ElevatedRegisterButton extends StatefulWidget {
const ElevatedRegisterButton({super.key, required this.navigator});
// this line
final String navigator;
you are accepting a String navigator; but you are passing to it a Future:
ElevatedRegisterButton(
navigator: Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return const RegisterPage1();
})))
problem 2
If you try running your code, you'll get an error:
setState() or markNeedsBuild called during build
So, to fix the issues, refactor your code as follows:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light().copyWith(),
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
body: ElevatedRegisterButton(navigator: Navigator.of(context))),
),
);
}
}
class ElevatedRegisterButton extends StatefulWidget {
const ElevatedRegisterButton({super.key, required this.navigator});
final NavigatorState navigator;
#override
State<ElevatedRegisterButton> createState() => _ElevatedRegisterButtonState();
}
class _ElevatedRegisterButtonState extends State<ElevatedRegisterButton> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.navigator.push(MaterialPageRoute(
builder: (context) => Text("h1"),
));
});
},
child: const Text('Register'),
);
}
}
See also
setState() or markNeedsBuild called during build

Flutter NullSafety cannot push Page after showing and change modalProgressHUD

I'm using Provider package to expose a simple boolean variabile that allow to change the status of variable "inAsyncCall" of the ModalProgressHUD widget.
When i try to do somethings before navigate to another page, and i want to display the circlular progress indicator during that computation, when the Future terminated, the current widget has been disposed and i cannot use Navigator.push():
Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
this is my Provider with ChangeNotifier class:
class CartProvider with ChangeNotifier {
bool _inAsync = false;
bool get inAsync => _inAsync;
void setInAsync(bool flag) {
this._inAsync = flag;
notifyListeners();
}
}
I inject the provider before the MaterialApp widget like this:
void main() async {
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => CartProvider(),
)
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
And this is the simple home page where i access via context the provider injected:
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final cartProvider = context.watch<CartProvider>();
return ModalProgressHUD(
inAsyncCall: cartProvider.inAsync,
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('HOME'),
FirstStatefulWidget(),
],
),
),
bottomNavigationBar: SecondStatefulWidget(),
),
);
}
}
class FirstStatefulWidget extends StatefulWidget {
FirstStatefulWidget({Key? key}) : super(key: key);
#override
_FirstStatefulWidgetState createState() => _FirstStatefulWidgetState();
}
class _FirstStatefulWidgetState extends State<FirstStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
Future doSomething() async {
cartProvider.setInAsync(true);
await Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
}
#override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('do call'),
onPressed: doSomething,
),
);
}
}
class SecondStatefulWidget extends StatefulWidget {
SecondStatefulWidget({Key? key}) : super(key: key);
#override
_SecondStatefulWidgetState createState() => _SecondStatefulWidgetState();
}
class _SecondStatefulWidgetState extends State<SecondStatefulWidget> {
late CartProvider cartProvider = context.read<CartProvider>();
void goToAnotherPageAfterCall() async {
try {
cartProvider.setInAsync(true);
Future.delayed(
Duration(seconds: 2),
() => {
cartProvider.setInAsync(false),
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => SecondPage(),
),
)
},
);
} on Exception catch (e) {
cartProvider.setInAsync(false);
}
}
#override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text('goToAnotherPage'),
onPressed: goToAnotherPageAfterCall,
),
);
}
}

Error thrown on navigator push : “!_debugLocked': is not true.”

When I tried to push from the initstate , this error shows up. Help
Import files here
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:corona_app/screens/home.dart';
Loading Screen Stateful Widget
class LoadingScreen extends StatefulWidget {
#override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
#override
initState
void initState() {
// TODO: implement initState
super.initState();
Navigator.push(context, MaterialPageRoute(builder: (context)=>Home()));
}
#override
Widget build
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SpinKitPouringHourglass(
color: Colors.green,
size: 100,
),
),
);
}
}
Alpha Bot Try this code:
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => _afterLayout(context));
super.initState();
}
Then create this method and navigate to your Home:
_afterLayout(BuildContext context) {
// TODO
Navigator.push(context, MaterialPageRoute(builder: (context)=>Home()));
}

This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets

I have got follow app:
class MyAppState extends State<MyApp>
{
TenderApiProvider _tenderApiProvider = TenderApiProvider();
Future init() async {
await _tenderApiProvider.getToken();
}
MyAppState()
{
init();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => _tenderApiProvider),
],
child: MaterialApp(
title: "My App",
routes: {
'/': (context) => HomePage(),
'/splash-screen': (context) => SplashScreen(),
'/result_table': (context) => ResultDataTable(),
}
),
);
}
}
I need to draw firstly SplashScreen current code show at start HomePage.
In splash-screen I need to switch to HomePage after all data loaded. Here it's code:
Widget build(BuildContext context) {
TenderApiProvider apiProv = Provider.of<TenderApiProvider>(context);
return StreamBuilder(
stream: apiProv.resultController,
builder: (BuildContext context, AsyncSnapshot snapshot) {
//...
if(apiProv.apiKeyLoadingState == ApiKeyLoadingState.Done && apiProv.regionsLoadingState == RegionsLoadingState.Done)
{
Navigator.of(context).pushNamed("/"); // Should it be placed in Build??
}
});
}
Could you help me and show to to draw at app start SplashScreen and then switch from it to HomePage?
You will need to wrap your SplashScreen() inside a StatefulWidget so you can fetch your data in initState(). It is important to wrap fetch() logic inside a SchedulerBinding.instance.addPostFrameCallback() to access the BuildContext inside initState(). Also, that way, you avoid conflicts with RenderObjects that get destoryed while they are actually build.
Following a complete minimal example.
EDIT: You cant use await in initState({}).
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Wrapper(),
);
}
}
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
_loadDataAndNavigate()
});
}
_loadDataAndNavigate() async {
// fetch data | await this.service.fetch(x,y)
Navigator.of(context).pushNamed('/');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SplashScreen(),
);
}
}
i use splashScreen
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SplashHome(),
routes: <String, WidgetBuilder>{
'/HomeScreen': (BuildContext context) => new ImageHome()
},
);
}
}
class SplashHome extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _SplashHome();
}
}
const timeout = const Duration(seconds: 2);
class _SplashHome extends State<SplashHome>{
startTimeout() {
return new Timer(timeout, handleTimeout);
}
void handleTimeout() {
Navigator.of(context).pushReplacementNamed('/HomeScreen');
}
#override
void initState() {
super.initState();
startTimeout();
}
#override
Widget build(BuildContext context) {
return new Container(
color: Colors.lightBlue,
);
}
}