How to pass parameters through navigation in flutter? - android

I need to pass a id parameter to a separate page while navigating from one page to another, I am currently using named routes to navigate but they are not letting me pass parameters.

You have to use the "final" variable in the second route class and pass the values during instantiation of that class' object.
The below navigation example was obtained from Flutter docs I just added the "passing data to a new page" process.
class FirstRoute extends StatelessWidget {
const FirstRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("First Route"),
),
body: Center(
child: ElevatedButton(
child: const Text("Open route"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const SecondRoute(text: "This is the text")),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({Key? key, required this.text}) : super(key: key);
final String text;
#override
Widget build(BuildContext context) {
print(text);
return Scaffold(
appBar: AppBar(
title: const Text("Second Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Go back!"),
),
),
);
}
}

Related

Flutter Nested Navigation Back Button handling on Android

Background
I have a Navigator widget on my InitialPage and I am pushing two routes ontop of it (NestedFirstRoute and NestedSecondRoute). When I press the physical back button on Android, Both the routes in Navigator are popped (which is expected).
Use case
So I would like to handle this case when the back button is pressed only the top route (NestedSecondRoute) must be popped.
Solution I tried
To deal with this issue I have wrapped the Navigator widget in WillPopScope to handle the back button press events and assigned keys to nested routes so as to use them when popping routes in the willPop scope.
I get an exception on this line
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
Exception has occurred.
_CastError (Null check operator used on a null value)
Heres the minimal and complete code sample
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case NestedFirstPage.route:
return MaterialPageRoute(
builder: (context) {
return WillPopScope(
onWillPop: () async {
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
NestedFirstPage.firstPageKey.currentState!.pop();
return false;
} else if (NestedSecondPage.secondPageKey.currentState!
.canPop()) {
NestedSecondPage.secondPageKey.currentState!.pop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
switch (settings.name) {
case Navigator.defaultRouteName:
return MaterialPageRoute(
builder: (context) => const NestedFirstPage(),
settings: settings,
);
case NestedSecondPage.route:
return MaterialPageRoute(
builder: (context) => const NestedSecondPage(),
settings: settings,
);
}
},
),
);
},
settings: settings,
);
}
},
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const InitialPage(title: 'Initial Page'),
);
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
);
}
}
class NestedFirstPage extends StatelessWidget {
const NestedFirstPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
Navigator.pushNamed(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Here's a slightly modified version of the above code which will allow pushing nested routes and can be popped via android back button
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/nested/first': (context) => const NestedFirstPage(),
'/nested/first/second': (context) => const NestedSecondPage()
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const RouteManager());
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
static const String route = '/';
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState != null &&
navigatorKey.currentState!.canPop()) {
navigatorKey.currentState!.pop();
return true;
}
return false;
},
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
navigate(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
),
);
}
}
Future<void> navigate(BuildContext context, String route,
{bool isDialog = false, bool isRootNavigator = true}) =>
Navigator.of(context, rootNavigator: isRootNavigator).pushNamed(route);
final navigatorKey = GlobalKey<NavigatorState>();
class RouteManager extends StatelessWidget {
const RouteManager({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/nested/first':
builder = (BuildContext _) => const NestedFirstPage();
break;
case '/nested/first/second':
builder = (BuildContext _) => const NestedSecondPage();
break;
default:
builder =
(BuildContext _) => const InitialPage(title: 'Initial Page');
}
return MaterialPageRoute(builder: builder, settings: settings);
});
}
}
class NestedFirstPage extends StatelessWidget {
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
const NestedFirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
navigate(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Heres a real world example with a Nested Bottomavigationbar.

Android back button not working with navigators inside tabs on Flutter

I need a navigator inside each tab, so when I push a new Widget, the tab bar keeps on screen. The Code is working very well, but the android back button is closing the app instead of running Navigator.pop()
import 'package:flutter/material.dart';
void main() {
runApp(const TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
const TabBarDemo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 1,
child: Scaffold(
bottomNavigationBar: const BottomAppBar(
color: Colors.black,
child: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
],
),
),
body: TabBarView(
children: [
Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => IconButton(
icon: Icon(Icons.directions_car),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => newPage()))),
);
},
),
],
),
),
),
);
}
}
class newPage extends StatelessWidget {
const newPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("new page"),
),
body: Center(child: Icon(Icons.add)),
);
}
}
the code is also available here but on dartpad you cannot test the android back button.
First you should create a key for your navigator
final GlobalKey<NavigatorState> homeNavigatorKey = GlobalKey();
then add this key to your navigator
Navigator(
key: homeNavigatorKey,
then wrap your Navigator in a WillPopScope widget and add the onWillPop as follows
child: WillPopScope(
onWillPop: () async {
return !(await homeNavigatorKey.currentState!.maybePop());
},
child: Navigator(
key: homeNavigatorKey,
this will check if the navigatorKey can pop a route or not, if yes it will pop this route only if no it will pop itself thus closing the app

why isn't the listview showing the text that input by the user

I need your help.
I'm making a function for my app that has the user add something by pressing the add button, it will then navigate to an adding page and then from the adding page it will add a new listtile in the listview. But I don't know why the text that was input by the user cannot be shown. Can anyone help me?
import 'package:flutter/material.dart';
import 'storage for each listview.dart';
import 'package:provider/provider.dart';
import 'adding page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Storage(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage()
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final provider = Provider.of<Storage>(context, listen: false);
final storageaccess = provider.storage;
return Scaffold(
appBar: AppBar(
title: Text('app'),
),
body: ListView.builder(
itemCount: storageaccess.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(storageaccess[index].title),
subtitle: Text(storageaccess[index].titlediary.toString()),
onTap: () {},
onLongPress: () {
//delete function here
},
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => addpage()));
}, //void add
tooltip: 'add diary',
child: Icon(Icons.add),
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
/// this one I did not do anything first this one for later today just make UI
class Things {
String title;
DateTime titlediary;
Things({required this.title, required this.titlediary});
}
class addpage extends StatefulWidget {
#override
_addpageState createState() => _addpageState();
}
class _addpageState extends State<addpage> {
String title = '';
#override
Widget build(BuildContext context) {
final TextEditingController titleController=TextEditingController(text: title);
final formKey = GlobalKey<FormState>();
return Scaffold(
appBar: AppBar(
title: Text('enter page ',style: TextStyle(fontSize: 30),),
),
body:Form(
key: formKey,
child: Column(
children: [
TextFormField(
controller: titleController,
autofocus: true,
validator: (title) {
if (title!.length < 0) {
return 'enter a title ';
} else {
return null;
}
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'title',
),
),
SizedBox(height: 8),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black),
),
onPressed: () {
if (formKey.currentState!.validate()) {
final accessthing = Things(
title: title,
titlediary: DateTime.now(),
);
final provideraccess = Provider.of<Storage>(context, listen: false);
provideraccess.add(accessthing);
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>MyHomePage()));
}
},
child: Text('Save'),
),
],
),),);
}
}
class Storage extends ChangeNotifier {
List<Things> storage = [
Things(
title: 'hard code one ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
Things(
title: 'hard code two ',
titlediary: DateTime.now(),
),
];
void add(Things variablethings) {
storage.add(variablethings);
} notifyListeners();
}
after the user clicks the addbutton, it will send them to an adding page, then after clicking save, the data will be saved into a storage page and then the provider will add the data provided by the user, but the text will not show on the listtile.
I suspect this is happening because you are Navigating to HomePage again using,
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>MyHomePage()));
but this time it is not connected to your provider context.
[
In detail: As you have used ChangeNotifierProvider() in MyApp then connected the MyHomePage() there. But if you push again in Navigator, then fluter creates a separate instance of MyHomePage() widget. Which will not be connected to ChangeNotifierProvider() in MyApp
].
In place of this, use Navigator.of(context).pop();
And in onPressed() use this,
floatingActionButton: FloatingActionButton(
---> onPressed: () async {
---> await Navigator.push(
context, MaterialPageRoute(builder: (context) => addpage()));
---> setState((){});
},

showing a dialog doesn't work when the widget is the direct child of MaterialApp

I'm trying to create an alert dialog in Flutter, but the dialog doesn't work when it is under MaterialApp and instead gives an error. Below is the code:
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 MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Inputs and alerts'),
),
body: ElevatedButton(
child: const Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Text('This is a text'),
content: Text('this is the content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('No'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('Yes'),
)
],
);
},
);
},
),
),
);
}
}
Error
But when I extract the ElevatedButton to a stand-alone widget, the alert dialog works fine. Here is the code:
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 MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Inputs and alerts'),
),
body: sn(),
),
);
}
}
class sn extends StatelessWidget {
const sn({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Text('This is a text'),
content: Text('this is the content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('No'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('Yes'),
)
],
);
},
);
},
),
);
}
}
Output
Can anyone tell me the cause of this behaviour? Any help is greatly appreciated.
This happens because you can only call showDialog(context) passing in a BuildContext that has an MaterialApp as an ancestor widget. The context you're getting access in your build() method from your first example is a context that does not have any MaterialApp above it.
Just like you did, you can solve this by extracting your Scaffold into another widget to have access to it's BuildContext in the build method.
Another solution is to use a Builder widget. It exposes a new context to it's child that now has in it the reference to any widget above it (in this case the MaterialApp).
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 MaterialApp(
home: Builder(builder: (context) {
return Scaffold(
appBar: AppBar(
title: const Text('Inputs and alerts'),
),
body: ElevatedButton(
child: const Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Text('This is a text'),
content: Text('this is the content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('No'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('Yes'),
)
],
);
},
);
},
),
);
}),
);
}
}

how to fix flutter exception : Navigator operation requested with a context that does not include a Navigator

I am trying to create a drawer navigation using flutter framework,
but i am getting the following exception every time I run it
Another exception was thrown: Navigator operation requested with a
context that does not include a Navigator.
so what is the solution, any help ?
I used Navigator class as the following
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new AppStates();
}
}
class AppStates extends State<MyApp> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("Application App Bar"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
),
),
);
}
}
and the code of the NextPage class is
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Next Page App Bar"),
),
),
);
}
}
It looks like you don't have a Navigator setup for current context. Instead of using StatefulWidget you should try MaterialApp as your root App. MaterialApp manages a Navigator for you. Here is an example of how to setup an App in your main.dart
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: MyApp(),
));
}
This is because the context that you're using is from the app level before a Navigator has actually been created. This is a common problem when creating "simple" single file apps in Flutter.
There are a number of possible solutions. One is to extract your Drawer into it's own class (extend Stateless/StatefulWidget accordingly), then in it's build override, the parent Scaffold will have already been created containing a Navigator for you to use.
class MyDrawer extend StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
);
}
The other, if you want to keep this Drawer in the same file, is to use a Builder instead, which has the same effect:
drawer: Builder(builder: (context) =>
Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Next Page"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage()));
},
)
],
),
),
),
you need to create a new Widget as home in MaterialApp like this:-
(This worked for me)
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: Center(child: Text("Click Me")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTaskScreen()),
);
},
),
);
}
}

Categories

Resources