I have been looking at all the answers on here to pass arguments when doing named route navigation but they seem to be old answers or they don't work.
From what was written it should be working but it doesn't seem to do anything, so I am not sure where my error is.
This is how I have it setup:
Main.dart (With my named routes setup):
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
),
initialRoute: HomePageScreen.id,
routes: {
HomePageScreen.id: (context) => HomePageScreen(),
AddItemScreen.id: (context) => AddItemScreen(),
AdvertiseScreen.id: (context) => AdvertiseScreen(),
HomePageFilterScreen.id: (context) => HomePageFilterScreen(),
HomePageResultsScreen.id: (context) => HomePageResultsScreen(),
ItemPageProfileScreen.id: (context) => ItemPageProfileScreen(),
ItemPageProfileSuggestUpdateScreen.id: (context) => ItemPageProfileSuggestUpdateScreen(),
ItemPageWhereToBuyAddStoreToDatabaseScreen.id: (context) => ItemPageWhereToBuyAddStoreToDatabaseScreen(),
ItemPageWhereToBuyMapScreen.id: (context) => ItemPageWhereToBuyMapScreen(),
ItemPageWhereToBuyScreen.id: (context) => ItemPageWhereToBuyScreen(),
MenuScreen.id: (context) => MenuScreen(),
NotAvailableScreen.id: (context) => NotAvailableScreen(),
TermsScreen.id: (context) => TermsScreen(),
}
);
}
}
HomePageResultsScreen.dart (On button click I am using push named to navigate to the next page, this is working because the new page 'ItemPageProfileScreen is opening):
onTap: () {
Navigator.pushNamed(context, ItemPageProfileScreen.id, arguments: 'MyTestString');
}
ItemPageProfileScreen.dart (I have tried using MaterialApp onGenerateRoute to get the arguments and print to screen to test but it is not working):
class ItemPageProfileScreen extends StatefulWidget {
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
MaterialApp(
onGenerateRoute: (routeSettings){
final arguments = routeSettings.arguments;
print(arguments.toString());
},
);
return Scaffold(),
Thanks for your help.
EDIT Second attempt:
class ItemPageProfileScreen extends StatefulWidget {
final String argument;
ItemPageProfileScreen(this.argument);
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text(widget.argument),
There is an official article on how to pass arguments with named routing. https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
The main idea is pretty straightforward: pass arguments into the constructor of your screen widget.
In the official docs (in the link above) they actually used both approaches with named routing and with regular routing even though the article stated about named routing.
Anyways. Focus on the constructor and arguments.
Where can you access the constructor of your screen with named routing if you pass only the name of the route when you navigate? In onGenerateRoute method. Let's do it.
Overwrite onGenerateRoute method in your top screen MyApp (that's where your mistake was). And if you do it you don't need routes: {} there (your second mistake)
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
),
initialRoute: HomePageScreen.id,
onGenerateRoute: (settings) {
if(settings.name == ItemPageProfileScreen.id) {
String msg = settings.arguments;
return MaterialPageRoute(builder: (_) => ItemPageProfileScreen(msg));
} else if(...
},
Get the arguments from the widget constructor:
class ItemPageProfileScreen extends StatefulWidget {
final String argument;
ItemPageProfileScreen(this.argument);
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
String msg = widget.argument;
...
And sending arguments over on tap:
onTap: () {Navigator.pushNamed(context, ItemPageProfileScreen.id, arguments: 'MyTestString');}
Hope this helps.
Related
I am trying switch to a different screen in Flutter project using onPressed but it is not generating any outcome not sure what is the reason.
Here is the homescreen page:
onPressed: () {
const User_Profile();
print("Hello");
},
Here is the user profile:
class User_Profile extends StatefulWidget {
const User_Profile({Key? key}) : super(key: key);
#override
State<User_Profile> createState() => _user_profileState();
}
class _user_profileState extends State<User_Profile> {
#override
Widget build(BuildContext context) {
return const Text("User Profile");
}
}
Question:
How to switch screens using Onpressed? What am I doing wrong noting that the word Hello for debugging is printed everytime.
Try below code and use Navigator.push refer navigation
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => User_Profile(),
),
);
},
child: const Text('User Profile'),
),
You have to use a function instead of your class like this:
Navigator.push(context, MaterialPageRoute(builder: (context)=>User_profile()));
call this:
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=>User_profile()));
},
instead of this:
onPressed: () {
const User_Profile();
print("Hello");
},
as you know you can't go to a specific page by calling the constructor method in a class. you have 2 ways:
use Navigator.push like this:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => User_Profile(),
),
);
and you can send the parameters to the next page by sending by constructor parameters like: User_Profile(name: 'yourName').2) you can use Navigator.pushNamed. you can define routeName in your main class of the project like this:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MyApp(),
);
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
canvasColor: Colors.transparent,
),
initialRoute: '/',
routes: {
'/': (context) => Splash(),
'/user_profile': (context) => User_Profile(),
},
);
}
}
as you see you defined a routeName '/user_profile' and you can use Navigator.pushNamed and if you want to pass parameters to the next page you have to use arguments like this:
Navigator.pushNamed(
context,
'/user_profile',
arguments: {"name" : "yourName"},);
and this code is for getting the arguments that you've passed in your User_Profile :
var arguments = ModalRoute.of(context)!.settings.arguments as Map;
var name = arguments['name'] as String;
I recommend you to use the second way to know all your routes of your projects.
Good Luck;)
I know the difference between RepositoryProvider and RepositoryProvider.value: the first one creates the repository for you and the second one receives a repository that is already created.
Please see the difference between the 2 code blocks - the first one is okay, the second one gives the following error.
RepositoryProvider.of() called with a context that does not contain a repository of type AuthRepository.
No ancestor could be found starting from the context that was passed to RepositoryProvider.of<AuthRepository>().
This can happen if the context you used comes from a widget above the RepositoryProvider.
The context used was: HomeScreen(dirty)
I don't understand why code 2 gives an error.
Code 1: success
class MyApp {
void main() {
// 1) Let the RepositoryProvider create the AuthRepository
runApp(RepositoryProvider(
crate: (context) => AuthRepository(),
child: BlocProvider(
create: (context) => AuthCubit(authRepository: RepositoryProvider.of<AuthRepository>(context)),
child: const MaterialApp(
// 2) Show HomeScreen that will access the AuthRepository instance
home: HomeScreen()
)
)
)
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final authCubit = BlocProvider.of<AuthCubit>(context);
// 3) This will succeed
final authRepo = RepositoryProvider.of<AuthRepository>(context);
return Scaffold(
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return const Text(state.toString());
},
),
);
}
}
Code 2: error
class MyApp {
void main() {
// 1) Create a repository instance of AuthRepository
final authRepo = AuthRepository();
// 2) Add this AuthRepository instance to the RepositoryProvider.value
runApp(RepositoryProvider.value(
value: (context) => authRepo,
child: BlocProvider(
create: (context) => AuthCubit(authRepository: authRepo),
child: const MaterialApp(
// 3) Show HomeScreen that will access the AuthRepository instance
home: HomeScreen()
)
)
)
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final authCubit = BlocProvider.of<AuthCubit>(context);
// 4) This will fail
final authRepo = RepositoryProvider.of<AuthRepository>(context);
return Scaffold(
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return const Text(state.authenticationStatus.toString());
},
),
);
}
}
The value should be a repository not a function.
Instead of
runApp(RepositoryProvider.value(
value: (context) => authRepo,
try
runApp(RepositoryProvider.value(
value: authRepo,
Basically, I want to return an OTP (One Time Pin) input screen if the app opens and there isn't a valid user file, otherwise the app should open with a menu (Using Provider).
What I tried was:
Main calls runApp(const MyApp()); which is stateless.
MyApp's build returns MultiProvider with its child const AuthenticatedMaterialApp(), which is a stateful widget.
Here I tried various things , including setting different routes based on the provided values, but keep getting unexpected (for me) results; e.g. in _AuthenticatedMaterialAppState:
class _AuthenticatedMaterialAppState extends State<AuthenticatedMaterialApp> {
late ConnectionNotifier connectionNotifier;
late Authenticate auth;
late bool isUserOkay;
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
connectionNotifier = context.read<ConnectionNotifier>();
auth = context.watch<Authenticate>();
isUserOkay = (auth.isActive && auth.isKnown);
developer.log('build isUserOkay:$isUserOkay isKnown: ${auth.isKnown}',
name: 'AuthenticatedMaterialApp');
String initRoute = (!isUserOkay) ? '/otp' : '/menu';
developer.log('initRoute $initRoute', name: 'AuthenticatedMaterialApp');
if (!isUserOkay) {
developer.log('isUserOkay: $isUserOkay return OTP app ', name: 'AuthenticatedMaterialApp');
return MaterialApp(
navigatorObservers: [routeObserver],
title: 'Named Routes',
theme: ThemeData(scaffoldBackgroundColor: Colors.white),
initialRoute: '/otp',
routes: {
'/otp': (context) => const OTPScreen(),
},
);
} else {
developer.log('isUserOkay: $isUserOkay return menu app ', name: 'AuthenticatedMaterialApp');
return MaterialApp(
navigatorObservers: [routeObserver],
title: 'Named Routes',
theme: ThemeData(scaffoldBackgroundColor: Colors.white),
initialRoute: '/menu',
routes: {
'/menu': (context) => const MenuScreen(),
'/stocks': (context) => const StocksRoute(),
'/stocks/stock': (context) => const StockPage(),
'/clients': (context) => const ClientsRoute(),
'/clients/client': (context) => const ClientPage(),
'/viewings': (context) => const ViewingsRoute(),
'/viewings/viewing': (context) => const ViewingPage(),
'/offers': (context) => const OffersRoute(),
'/offers/offer': (context) => const OfferPage(),
'/checkin': (context) => const CheckinScreen(),
'/calendar': (context) => const CalendarScreen(),
'/reports': (context) => const ReportsScreen(),
'/calculators': (context) => const CalculatorsScreen(),
'/test': (context) => const TestRoute(),
},
);
}
}
}
... eventually generates output that reads [AuthenticatedMaterialApp] isUserOkay: true return menu app , but what is shown on the screen is the OTP screen.
Why? - has it something to do with AuthenticatedMaterialApp being a const, or?
I also tried registering all the routes but having initialRoute: be a variable string but get basically the same result.
I can get it working by creating a separate 'home` route, and checking the user there and ~redirecting like:
class _HomeState extends State<Home> {
late Authenticate auth;
#override
Widget build(BuildContext context) {
auth = context.watch<Authenticate>();
developer.log('build', name: '_HomeScreenState');
if (auth.isKnown) {
if (auth.isActive == true) {
return const MenuScreen();
} else if (auth.isActive == false) {
return const OTPScreen();
}
}
return Container();
}
}
while I have all the routes including OTP, menu etc all registered in the AuthenticatedMaterialApp, which I suppose is fine, but I would like to understand why my initial approach doesn't/can't work?
I have this bit of code that I want to pop all screens until it gets to the base screen with Name "/".
Navigator.of(context).popUntil(ModalRoute.withName("/"));
Before I call the popUntil method, I navigated using:
Navigator.of(context).pushNamed("/Loading");
Navigator.of(context).pushReplacementNamed("/Menu");
But the result I'm getting is all the screens are getting popped until it gets to the black screen. What should I change to make it stop at "/"?
Here is how it's set up:
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
onGenerateRoute: AppRouter().onGenerateRoute,
initialRoute: '/',
),
);
}
}
class AppRouter {
Route? onGenerateRoute(RouteSettings routeSettings) {
switch (routeSettings.name) {
case '/':
return MaterialPageRoute(builder: (_) => const LoadingScreen());
case '/Menu':
return MaterialPageRoute(builder: (_) => const MenuScreen());
case '/Loading':
return MaterialPageRoute(builder: (_) => const LoadingScreen());
}
}
}
The ModalRoute.withName predicate is used when a route is tied to a specific route name. Because you're using onGenerateRoute (which is typically a last resort) instead of the routes table in your MaterialApp there is no route associated with the / route name.
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
);