How to remove the first screen from route in Flutter? - flutter

I am creating a loading screen for an app. This loading screen is the first screen to be shown to the user. After 3 seconds the page will navigate to the HomePage. everything is working fine. But when the user taps back button the loading screen will be shown again.
FIRST PAGE CODE
import 'dart:async';
import 'package:flutter/material.dart';
import 'home_page.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Future.delayed(
Duration(
seconds: 3,
), () {
// Navigator.of(context).pop(); // THIS IS NOT WORKING
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlutterLogo(
size: 400,
),
),
);
}
}
HOMEPAGE CODE
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('HomePage'),
),
),
);
}
}
I tried to add Navigator.of(context).pop(); before calling the HomePage but that is not working. This will show a blank black screen.
Any ideas??

You need to use pushReplacement rather than just push method. You can read about it from here: https://docs.flutter.io/flutter/widgets/Navigator/pushReplacement.html
And to solve your problem just do as explain below.
Simply replace your this code:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
with this:
Navigator. pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);

Yes, I found the same problem as you. The problem with replace is that it only works once, but I don't know why it doesn't work as it should. For this after a few attempts, I read the official guide and this method exists: pushAndRemoveUntil (). In fact, push on another widget and at the same time remove all the widgets behind, including the current one. You must only create a one Class to management your root atrough the string. This is the example:
class RouteGenerator {
static const main_home= "/main";
static Route<dynamic> generatorRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case main_home:
return MaterialPageRoute(builder: (_) => MainHome());
break;
}
}
}
This class must be add to the Main in:
MaterialApp( onGenerateRoute: ->RouteGenerator.generatorRoute)
Now to use this method, just write:
Navigator.of(context).pushNamedAndRemoveUntil(
RouteGenerator.main_home,
(Route<dynamic> route) => false
);

Related

Flutter screen not changing after provider state changed

I'm new in flutter and trying to understand flutter state management concept using provider. This the image scenario what I'm trying to do
I have created a file called auth_provider.dart file under the folder called Providers
class AuthProvider with ChangeNotifier{
bool isLogin = false;
Future createUser() async
{
isLogin = true;
notifyListeners();
}
Future login() async
{
isLogin = true;
notifyListeners();
}
void logout()
{
isLogin = false;
notifyListeners();
}
}
This the Signup button that I have created in the login page
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SignupPage()
),
);
},
child: const Text(
'Signup Button',
),
)
This is the signUp button in signup screen
child: ElevatedButton(
onPressed: () => signUpSubmit(),
child: const Text(
'Sign Up',
),
),
I have written a signUpSubmit future like below
Future<void> signUpSubmit() async {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
I have used AuthProvider consumer in main.dart page
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => AuthProvider(),
),
],
child: Consumer<AuthProvider>(
builder: (ctx,auth,child){
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? const HomeScreen():const LoginPage(),
routes: {
HomeScreen.routeName: (ctx) => const HomeScreen(),
SignupPage.routeName: (ctx) => const SignupPage(),
LoginPage.routeName: (ctx) => const LoginPage(),
},
);
}
),
);
}
}
After click on signup button I'm getting true in main page , which I have given a print under Consumer builder in main.dart page. So according to MaterialApp widget home condition page should redirect to HomeScreen but it's not moving. Why it's not moving ? What is the main cause and what it the best way to solve this problem ?
Note : If I try it from login screen redirection is working fine. But according to my image flow (Login -> signup) it's not working.
here is the code you are looking for, but bear in mind with the implementation you have right now, if the user opens the app again, it will redirect them to the signin page. because the boolean value will disappear once the user closes the app.
change your main.dart file like the following..
main function
void main() {
// you just need to add the multiprovider and the change notifier provider class
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: const MyApp(),
),
);
}
here is the MyApp class as i understand it.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (ctx, auth, child) {
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? MyHomePage() : LoginPage(),
routes: {
MyHomePage.routeName: (ctx) => MyHomePage(),
LoginPage.routeName: (ctx) => LoginPage(),
//NavScreen.routeName: (ctx) => const NavScreen(),
},
);
});
}
}
Change the signup button in the register page to the following.
ElevatedButton(
onPressed: () {
signUpSubmit(context);
Navigator.of(context).pushNamed(HomeScreen.routeName);
},
and the signupsubmit function like this..
signUpSubmit(BuildContext context) {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
The main cause of your problem is that you are pushing a new route (screen) from login page and the best way to solve problem is to pop that route (screen) from sigupPage.
On click of Signup button from login page you are pushing a new route, so in order to redirect to HomeScreen from SignupPage first you need to pop that route so that you can see the updated changes.
Future<void> signUpSubmit() async {
Navigator.of(context).pop();
Provider.of<AuthProvider>(context, listen: false).createUser();
}
https://docs.flutter.dev/cookbook/navigation/navigation-basics

flutter-web - Avoid initialRoute from initiating when the app launched with a different route via the browser's address bar?

New to Flutter.
I'm making an app that has a splash screen that initially shows up when the user opens the app. After 3 seconds, the app will show the login or the dashboard screen, depending on the authentication state.
Here's my code.
main.dart
void main() {
runApp(myApp);
}
MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
);
splash_screen.dart
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
AuthState authState = await Auth.getAuthState();
String route = authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
// build() override goes here...
}
I've been debugging the app with a web-server. When the app launches with the url localhost:8000/, everything seems fine. However, if the app started with the url localhost:8000/notes, the splash screen, I think, still gets initiated. What happens is the app will show the notes screen, then after 3 seconds, the app will open another notes screen.
Any ideas?
Because first render always started at root '/', it's preferable to use your own path for splash screen, like
initialRoute: '/splash'.
To hide this path in the address bar, replace routes map with route generator:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (RouteSettings settings) {
// print current route for clarity.
print('>>> ${settings.name} <<<');
switch (settings.name) {
case '/splash':
return MaterialPageRoute(
builder: (context) => SplashScreen(),
// settings omitted to hide route name
);
case '/signin':
return MaterialPageRoute(
builder: (context) => SignInScreen(),
settings: settings,
);
case '/notes':
return MaterialPageRoute(
builder: (context) => NotesScreen(),
settings: settings,
);
case '/':
// don't generate route on start-up
return null;
default:
return MaterialPageRoute(
builder: (context) => FallbackScreen(),
);
}
},
initialRoute: '/splash',
);
}
}
See since the main logic is we cannot have await in the init state so the page will build irrespective of the any logic you provide. I have a solution to this, there may be some advance or other good solutions too, so this is what I would use.
I would use a concept of future builder. What it will do is wait for my server and then build the whole app.
So process is
In your main.dart
use
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
//await for my server code and according to the variable I get I will take action
//I would have a global parameter lets say int InternetOff
await checkServer();
runApp(MyApp());
} catch (error) {
print(error);
print('Locator setup has failed');
//I can handle the error here
}
}
Now MyApp stateless Widget that will help us choose our path
class MyApp extends Stateless Widget{
Widget build(BuildContext context) {
//Using this FutureBuilder
return FutureBuilder<String>(
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// AsyncSnapshot<Your object type>
// Now if InternetOff is equal to one I would make it go to home
if(InternetOff==1) return MaterialApp(
theme: ThemeData.light(),
home: CheckInternet(),
debugShowCheckedModeBanner: false,
);
//else go to Home similarly with these if and else you can add more conditions
else {
return MaterialApp(
theme: ThemeData.dark(),
home: UserHome(),
debugShowCheckedModeBanner: false,
);
}
}
}
},
);
}
}
First of all, flutter-web like any other Single Page Application supports hash based routing. As a result if you want to access
localhost:8000/notes
you have to access it as
localhost:8000/#/notes
Cleaner way to handle auth state
Call getAuthState function before runApp() to make sure that the auth state is set before app is initialized. And pass authState to SplashScreen widget as parameter.
void main() {
WidgetsFlutterBinding.ensureInitialized();
AuthState authState = await Auth.getAuthState();
runApp(MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(authState: authState),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
));
}
splash_screen.dart
class SplashScreen extends StatefulWidget {
final AuthState authState;
SplashScreen({Key key, this.authState}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
String route = widget.authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
And if you want even more cleaner way to handle auth state, you have to use state management solution like Provider.

Flutter Routes Emulators keeps rendering '/' instead of InitialRoute

New to Flutter.
Here is the content of my main page:
void main() => runApp(MaterialApp(
initialRoute: '/location',
routes: {
'/': (context)=> Loading(),
'/location': (context)=> Location(),
'/home': (context)=> Home(),
},
));
Consider that in my '/': (context)=> Loading(), there is a function that is executed in initState at the end of which it redirects to '/home', but as you can see in my main.dart I set the initial route to '/location' so the emulators is not supposed to open the loading file in order to be redirected to the home file, however it keeps happening.
When I clean & run my emulator, it opens on the location file and then somehow the function in the loading file gets executed which takes me back to the home file.
I do not know if I am making sense, please let me know if you have a hard time understanding.
Optional, here is my loading.dart function:
class _LoadingState extends State<Loading> {
String time;
void fetchIt() async {
final WorldTimeClass defaultinstance = WorldTimeClass(flag: 'Algiers', url: 'Africa/Algiers', location: 'Algiers');
DateTime getTimeFormat = await defaultinstance.getTimeFormat();
await defaultinstance.getTimeHumanFormat(getTimeFormat);
// here the navigator to home.dart
await Navigator.pushReplacementNamed(context, '/home', arguments: {
'location': defaultinstance.location,
'time': defaultinstance.time,
'isDayTime': defaultinstance.isDayTime
});
}
#override
void initState() {
time = 'loading...';
super.initState();
fetchIt(); // here the function executed
}
...
Kindly, explain to me why is this happening?
I solved this by changing -in the routes: argument- :
'/home' to 'home',
'/location' to 'location', and
kept '/' as is.
I do not know why this solved the problem but, it works now.
the route represented by '/' must be your initial route that is the screen that you want to show when your app is started . Otherwise if you use '/' for any other screen , that screen will run behind the scenes . Use the code below as an example . I want to show Page 2 as the home screen that is why I have set initial route to '/' and
'/':(BuildContext ctx)=>Page2()
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// import 'package:path_provider/path_provider.dart';
void main() => runApp(Example());
class Example extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: "Example",
initialRoute: '/',
routes: {
'/':(BuildContext ctx)=>Page2(),
'/page1':(BuildContext ctx)=>Page1(),
'/page3':(BuildContext ctx)=>Page3()
},
);
}
}
class Page1 extends StatefulWidget{
Page1State createState()=> Page1State();
}
class Page1State extends State<Page1> {
#override
void initState() {
super.initState();
timerFunction();
}
Timer timerFunction(){
return new Timer(Duration(seconds: 3),handleTimeout);
}
handleTimeout(){
Navigator.pushNamed(context, '/page3');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Page1"),
),
);
}
}
class Page2 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
body:Container(
alignment: Alignment.center,
child :Column(
children:<Widget>[
Padding(padding: EdgeInsets.only(top: 100)),
Text("Page2"),
RaisedButton(
child: Text("go to page1"),
onPressed: ()=>Navigator.pushNamed(context, '/page1'),
)
]
),
),
);
}
}
class Page3 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Page3"),
),
);
}
}
First, try to restart the app by re-running flutter run

Undefined named 'context' while navigating

import 'package:flutter/material.dart';
import 'fruits_listing_card.dart';
import 'fruits_page.dart';
Map<String, Widget> fruits = {
"banana": FruitsListingCards(
fruitBGColor: 0xFFF8A8B5,
fruitImagePath: 'images/fruits/banana.png',
fruitName: 'Banana',
fruitPrice: 'Rs. 105',
fruitShortDescription: 'Ripe & Tasty',
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => FruitsPage()),);
},
),
}
// Second File
import 'package:flutter/material.dart';
class FruitsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(),
),
);
}
}
Both the code are in different files.
FruitsListingCards is a widget that has a Gesture detector functionality. onTap is a parameter that takes function.
I am using FruitsListingCards in the main file and whenever a user taps on it, should go to the FruitsPage screen. But the error is not letting me to do so. Any solution with proper explanantion will help me a lot.
EDIT:
For proper understanding of code, check my repo:
https://github.com/RaghavTheGreat1/fruits_delivery/tree/master/lib
You have to provide context some how, so that it can connect the last screen and next screen.
You can wrap inside a function for that.
Following minimal code will help you more to understand.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class FruitsPage extends StatelessWidget {
final Function call;
FruitsPage({this.call});
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("press"),
onPressed: call,
),
);
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Text("FruiysPage"),
),
),
);
}
}
callme(context) {
Map<String, Widget> fruits = {
"banana": FruitsPage(
call: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewPage()),
);
},
)
};
return fruits;
}
Well, I figured out the problem on omy own and it was easier than what other people had suggested me even if they haven't looked into my code committed in my GitHub repo.
The quick solution or fix was to add Navigator.push(context, MaterialPageRoute(builder: (context) => fruitsPage)); inside the anonymous function of GestureDetector in FruitsListingCards and then returning fruitsPage(which is a dynamic variable that will take Class object as parameter).

Flutter user redirect if logged in

still a beginner in flutter. below is a sample chat apps i tried to redirect user depending on their login status.
so far tested with emulator, the outputs is what i expected. my questions are:
1.is this the correct approach for user redirect, or is there a better way as in better refactored code?
2.any refactoring can be done for the 'return materialApp', as it is very repetitive. (only changing initialRoute)
3.any implication to runApp a StatefulWidget? because all tutorial normally starts runApp a StatelessWidget
import 'package:flutter/material.dart';
import 'package:chatting/screens/login_screen.dart';
import 'package:chatting/screens/registration_screen.dart';
import 'package:chatting/screens/chat_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
void main() => runApp(LoadPage());
class LoadPage extends StatefulWidget {
#override
_LoadPageState createState() => _LoadPageState();
}
class _LoadPageState extends State<LoadPage> {
Future checkIfLoggedIn;
#override
void initState() {
super.initState();
checkIfLoggedIn = FirebaseAuth.instance.currentUser();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<FirebaseUser>(
future: checkIfLoggedIn,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
default:
if (snapshot.hasData)
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: ChatScreen.id,
routes: {
ChatScreen.id: (context) => ChatScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
},
);
else
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: LoginScreen.id,
routes: {
ChatScreen.id: (context) => ChatScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
},
);
}
});
}
}
Yeah your code looks good to me. There's no problem using a StatefulWidget in runApp.
The only additional tip I'd give is that, typically, for larger applications, you'll want to use the BLoC pattern to manage state. If you added that pattern to this code sample, it would abstract the logic you're doing away from this component, and you could manage the future in the bloc. You could then use a stateless widget for your loading screen. The Flutter Bloc library provides useful, straightforward abstractions that show how to implement the bloc pattern.