I'm Working on Project Where there is A screen For viewing the user Profile and another screen for Editing Profile. I'm using onGenerateRoute Method for Routing and know I can parse an argument and send it over.
How I can use Call Back Function ValueChange with onGenerateRoute Method?
Navigate to the EditingProfile Page and pass the Function as an argument:
Navigator.pushNamed(context, "/editingprofile", arguments: () {
print("Function called");
});
In the onGenerateRoute pass the argument to the EditingProfile either as constructor param and call the variable directly
Route<dynamic>? generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case "/editingprofile":
return MaterialPageRoute(
builder: (context) => EditingPage(settings.arguments as Function));
}
}
class EditingPage extends StatelessWidget {
Function callback;
SecondPage(this.callback, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: OutlinedButton(
onPressed: (){
callback.call();
},
child: Text("Press me"),
),
);
}
}
or pass it inside the MaterialPageRoute as settings param and get the function with ModalRoute
Route<dynamic>? generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case "/editingprofile":
return MaterialPageRoute(
settings: settings,
builder: (context) => EditingProfile());
}
}
class EditingPage extends StatelessWidget {
EditingPage ({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Function callback = ModalRoute.of(context)!.settings.arguments as Function;
return Scaffold(
body: OutlinedButton(
onPressed: (){
callback.call();
},
child: Text("Press me"),
),
);
}
}
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;)
One case I've looked up where this is used is Navigator with WillPopScope as the parent as mentioned in the comment of this answer.
To put my question concretely, the following code should pop from the nested navigator but instead the app exits when the back button is pressed on android.
void main() {
runApp(MaterialApp(title: 'Navigation Basics', home: PearlScreen()));
}
class PearlScreen extends StatefulWidget {
const PearlScreen({Key? key}) : super(key: key);
#override
State<PearlScreen> createState() => _PearlScreenState();
}
class _PearlScreenState extends State<PearlScreen> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => await _navigatorKey.currentState!.maybePop(),
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
Widget screen;
switch (settings.name) {
case '/':
screen = const HomeScreen();
break;
case '/quiz':
screen = const QuizScreen();
break;
default:
throw "Invalid route in Pearl: ${settings.name}";
}
return MaterialPageRoute(
builder: (_) => Material(child: screen),
);
},
),
);
}
}
But as soon as I change it to onWillPop: () async => !await _navigatorKey.currentState!.maybePop(), it works correctly and pops from the nested navigator instead of exiting the app.
What is the significance of !await?
I want to pass screens which i have built in this navigator onTap
onTap: () => Navigator.push(context,
MaterialPageRoute(builstrong textder: (context) => QuestionPaperScreen()))
There is a QuestionPaperScreen() and I want to pass here a screen which is received as a variable on Function call
How can I do it?
In QuestionPaperScreen you can define and variable and mark it as required and pass it from this screen
class QuestionPaperScreen extends StatelessWidget {
final String routeName;
const QuestionPaperScreen({Key? key, required this.routeName}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
Then on tap you can use
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (_)=>QuestionPaperScreen(routeName:'/routeNameHere')));//pass the route name that you received from a function here
},
I have two classes MainScreen and SearchScreen which uses nested function with a boolean value
For example, here in MainScreen, I have a function showMap, its return value is obtained from SearchScreen
Future showMap(bool checkValue) async {
try {
//do something
}
//somewhere in the widgets
#override
Widget build(BuildContext context) {
super.build(context);
//
GestureDetector(
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SearchScreen(
showMapFunction: showMap)));)
}
}
NoW in my SearchScreen I have
class SearchScreen extends StatefulWidget {
final Function showMapFunction;
const SearchScreen({
this.showMapFunction
}); //,this.String});
#override
_SearchScreenState createState() => _SearchScreenState();
}
///
#override
#mustCallSuper
Widget build(BuildContext context) {
GestureDetector(
onTap: () async {
Navigator.pop(
//send back data
// context,
widget.showMapFunction(true));
},
child: Icon(Icons.arrow_back)),
}
This works fine, when I navigate back to MainScreen the function showMap is called, Is there any other way to do this perhaps with provider package or sth? This causes a lot of rebuilds to my widget tree.
What you can do is to await the result of the navigator, like the one used in the Flutter Cookbook.
void _navigateToSearch(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SearchScreen())),
);
}
And then, when you pop the Search Screen, do the following
GestureDetector(
onTap: () async => Navigator.pop(true),
child: Icon(Icons.arrow_back),
),
I also suggest using WillPopScope.
The used Getx Arguments are cleared after the showDialog method is executed.
_someMethod (BuildContext context) async {
print(Get.arguments['myVariable'].toString()); // Value is available at this stage
await showDialog(
context: context,
builder: (context) => new AlertDialog(
//Simple logic to select between two buttons
); // get some Confirmation to execute some logic
print(Get.arguments['myVariable'].toString()); // Variable is lost and an error is thrown
Also I would like to know how to use Getx to show snackbars without losing the previous arguments as above.
One way to do this is to duplicate the data into a variable inside the controller and make a use from it instead of directly using it from the Get.arguments, so when the widget tree rebuild, the state are kept.
Example
class MyController extends GetxController {
final myArgument = ''.obs;
#override
void onInit() {
myArgument(Get.arguments['myVariable'] as String);
super.onInit();
}
}
class MyView extends GetView<MyController> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: Center(child: Obx(() => Text(controller.myArgument()))),
),
);
}
}
UPDATE
Since you are looking for solution without page transition, another way to achieve that is to make a function in the Controller or directly assign in from the UI. Like so...
class MyController extends GetxController {
final myArgument = 'empty'.obs;
}
class MyView extends GetView<MyController> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Obx(() => Text(controller.myArgument())),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
controller.myArgument(Get.arguments['myVariable'] as String);
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(controller.myArgument()); // This should work
}
}
UPDATE 2 (If you don't use GetView)
class MyController extends GetxController {
final myArgument = 'empty'.obs;
}
class MyView extends StatelessWidget {
final controller = Get.put(MyController());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Obx(() => Text(controller.myArgument())),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
controller.myArgument(Get.arguments['myVariable'] as String);
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(controller.myArgument()); // This should work
}
}
UPDATE 3 (NOT RECOMMENDED)
If you really really really want to avoid using Controller at any cost, you can assign it to a normal variable in a StatefulWidget, although I do not recommend this approach since it was considered bad practice and violates the goal of the framework itself and might confuse your team in the future.
class MyPage extends StatefulWidget {
const MyPage({ Key? key }) : super(key: key);
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
String _myArgument = 'empty';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Text(_myArgument),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
setState(() {
_myArgument = Get.arguments['myVariable'] as String;
});
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(_myArgument); // This should work
}
}