I'm developing an application using flutter, I got to display the first page and I created sub pages.
My problem is that I don't know how to display a second page when you click on an item. example: I created a grid of 8 boxes and each box must display a unique page at the click. i.e. item 1 returns page 1, item 2 returns page 2, item 3 returns page 3... etc.
here is a capture of the first page
You can achieve this with the following code example. With the Navigator you can go to the next page and display it.
class NavigatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GridView.builder(
itemCount: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => nextPage(context, index),
child: Card(
...
),
);
},
),
),
);
}
void nextPage(BuildContext context, int index) {
if (index == 0) {
Navigator.push(context, MaterialPageRoute(builder: (context) => Page1()));
} else if (index == 1) {
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2()));
}
...
}
}
what you need is the Navigator object. In flutter it's used to navigate the user between screens by having one initial screen and then stacking more on top of it. This is a good tutorial on the basics.
In general good practise is to use named routes (tutorial here) but you don't need to use named routes to use the Navigator. In your example you can put the following in a onPressed argument on each item:
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return PageTwo()
);
},
);
On first Screen, write this code.
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return SecondScreen()
);
},
);
You must import the Second Screen in First Screen dart file.
Related
I'm trying to pass an argument from data table and pushing the values to a modal in a flutter web project. I tried reading a few topics but I'm struggling to find a solution.
Here is how am trying to pass the data to the modal, which I don't think is correct!-but am newbie in flutter. The solution works for me when I route to the page instead of a modal.
previewEstate(element) {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return PreviewEstateModal();
// Navigator.pushReplacement(
// context,
// PageRouteBuilder(
// pageBuilder: (_, __, ___) => PreviewEstateModal(),
// transitionDuration: Duration(seconds: 0),
// settings: RouteSettings(arguments: element),
// ),
// );
},
);
}
The commented part of my code works if I route to that page i.e am able to receive the route setting argument, but I want to do this by using a modal. What could I be doing wrong?
You can try with following code
previewEstate(element) {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return PreviewEstateModal(estate: element); // here you pass the arguments
},
);
}
In your PreviewEstateModal, initialize the argument(EstateSearch)
class PreviewEstateModal extends StatelessWidget {
final EstateSearch estate;
PreviewEstateModal({this.estate});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'${estate}',/// initilize estate property
),
),
);
}
}
I was now trying for days to retrieve my firestore values, but no luck so posting it here.
I have a Firestore database and some data. I want to retrieve this with the help of Flutter.
This is what I have been doing.
So I have a Flutter screen where it shows a simple 3-dot dropdown in the AppBar.
It has two options: edit and cancel.
What I want is, when I press edit, it should open a new screen and should pass the data that I retrieved from firestore.
This is where I have edit and cancel dropdown (3 dots) and calling the a function (to retrieve data and open the new screen).
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.news.headline.toUpperCase()),
actions: <Widget>[
PopupMenuButton<String>(
onSelected: (value) {
_open_edit_or_delete(value); // caling the function here
},
itemBuilder: (BuildContext context) {
return {'Edit', 'Delete'}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
),
],
),
body: _get_particular_news(widget.news),
);
}
and this is the open_edit_or_delete function it is calling. But it doesn't open up (navigate) to the screen I am calling.
open_edit_or_delete(String selectedOption) {
News news;
Visibility(
visible: false,
child: StreamBuilder(
stream: FireStoreServiceApi().getNews(),
builder: (BuildContext context, AsyncSnapshot<List<News>> snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
Navigator.push(
context, MaterialPageRoute(builder: (_) => FirstScreen(news:news)));
return null;
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
news = snapshot.data[index];
},
);
}
},
));
}
And in case you need the FireStoreServiceApi().getNews(), here it is as well.
// get the news
Stream<List<News>> getNews() {
return _db.collection("news").snapshots().map(
(snapshot) => snapshot.documents
.map((doc) => News.fromMap(doc.data, doc.documentID))
.toList(),
) ;
}
Can someone please help me?
You are not passing data correctly to your fromMap method.
You can access data using doc.data['']
If you have data and documentID property in it then following will work.
News.fromMap(doc.data.data, doc.data.documentID))
I don't know your fromMap method and i also don't what your snapshot contains, if this did not work for you then add them too.
I have 4 different items in my bottom navigation menu and in one case I need to navigate to one page and delete all the pages in my stack so that I use this code to navigate my another screen;
Navigator.pushAndRemoveUntil(
context,
PageRouteBuilder(
pageBuilder: (context, anim1, anim2) => AnamenuTemelSayfa(currentIndex: 2),
transitionsBuilder: (context, anim1, anim2, child) => Container(child: child),
transitionDuration: Duration(milliseconds: 200),
),
(_) => true,
);
After this navigation when I hit the back button on the physical device it backs to the last screen that I've just came, to handle with this problem I change true value into false but when I do it after this navigation event in any pages of the app on the physical device back button does not work so that I try to do another thing is that to wrap second page with WillPopScope not to go back I've defined this code;
//Second page
onWillPop: () async {
return await Future.value(false);
},
if you want use willpopscope you should use Navigator.push instead of Navigator.pushAndRemoveUntil
pushAndRemoveUntil remove all pages until your current page then you cant go back to previous page and your WillPopScope not calling, so if you want prevent user to back to previous page you should wrap your scaffold with WillPopScope widget like this:
WillPopScope(
onWillPop: () async => false,
child: Scaffold(
// your code here
)
)
EDIT:
use this navigator (it`s remove all page until YourNewPage)
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (BuildContext context){
return YourNewPage();
}), (Route<dynamic> route) => false);
Your route predicate should return false to remove all the previous page and push new page.
Navigator.pushAndRemoveUntil(
context,
PageRouteBuilder(
pageBuilder: (context, anim1, anim2) => AnamenuTemelSayfa(currentIndex: 2),
transitionsBuilder: (context, anim1, anim2, child) => Container(child: child),
transitionDuration: Duration(milliseconds: 200),
),
(_) => false, // routePredicate
);
And now you don't need to use WillPopScope.
Is there any limitation in BottomSheet that we cannot update the widget states? As you can see in the example below I'm using a Switch but its display is not changing, although the value update, it's just that it doesn't get re-render again.
This is part of StatefulWidget right now.
This same problem I'm experiencing with the DropdownButton widget. These both work in normal page fine.
Does anybody have the idea?
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return BottomSheet(
onClosing: () {},
builder: (BuildContext context) {
return Switch(
onChanged: (bool v) {
debugPrint('v is ${v.toString()}');
// b = v; <<-- This is also not working when using StatelessWidget
setState(() => b = v);
debugPrint(b.toString());
},
value: b,
);
},
);
},
);
The problem here is that the BottomSheet you are creating is not part of your StatefulWidget. If you only made your widget stateful for the purpose of using setState inside of showModalBottomSheet, you can revert that change now.
What you really want to do is set the state inside of your BottomSheet. You do that by either passing a StatefulWidget to the builder or by using a StatefulBuilder, which I will do for this example for the sake of simplicity:
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return BottomSheet(
onClosing: () {},
builder: (BuildContext context) {
bool b = false;
return StatefulBuilder(
builder: (BuildContext context, setState) => Switch(
onChanged: (bool v) {
setState(() => b = v);
},
value: b,
),
);
},
);
},
);
I also moved the b value inside the builder function of the BottomSheet.
If you want to use the value of b inside of your original StatefulWidget as well, you would move it out again and you probably want to call this.setState as well to also update the other widget (only if you need it to update).
I also faced the same problem. Its a small trick, you need to insert StatefulBuilder in the showModalBottomSheet. I will use a different code to make someone out there understand easily using checkbox now that the answer comes way late.
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
mpesachecked =false;
return StatefulBuilder(builder: (BuildContext context, StateSetter mystate) {
return Padding(
padding: const EdgeInsets.fromLTRB(10.0, 70.0, 20.0, 20.0),
child: Column(
children: <Widget>[
Checkbox(
value: mpesachecked,
activeColor: Colors.green,
onChanged: (value) {
mystate(() {
mpesachecked = value;
});
}),
])
));
});
)
NOTE: the new state within the showModalBottomSheet is mystate
I have a List Builder that creates a list based off of the documents listed in Firestore. I am trying to take the value generated from a Firestore snapshot and pass it out of the class to a variable that is updated every time the user clicks on a different entry from the List Builder.
Here is the class making the Firestore interaction and returning the ListBuilder:
class DeviceBuilderListState extends State<DeviceBuilderList> {
final flutterWebviewPlugin = new FlutterWebviewPlugin();
#override
void initState() {
super.initState();
// Listen for our auth event (on reload or start)
// Go to our device page once logged in
_auth.onAuthStateChanged
.where((user) {
new MaterialPageRoute(builder: (context) => new DeviceScreen());
});
// Give the navigation animations, etc, some time to finish
new Future.delayed(new Duration(seconds: 1))
.then((_) => signInWithGoogle());
}
void setLoggedIn() {
_auth.onAuthStateChanged
.where((user) {
Navigator.of(context).pushNamed('/');
});
}
#override
Widget build(BuildContext context) {
return new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.data != null)
return new StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(snapshot.data.uid)
.collection('devices')
.snapshots,
builder: (context, snapshot) {
if (!snapshot.hasData)
return new Container();
return new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new ListView.builder(
itemCount: snapshot.data.documents.length,
padding: const EdgeInsets.all(10.0),
itemBuilder: (context, index) {
DocumentSnapshot ds =
snapshot.data.documents[index];
return new Card(
child: new GestureDetector(
onTap: () {
var initialStateLink = "${ds['name']}";
Navigator.of(context).pushNamed("/widget");
},
child: new Text(
" ${ds['name']}",
style: new TextStyle(fontSize: 48.0),
),
));
}),
);
},
);
else return new Container();
}
);}
}
Then I want to send the var initialStateLink to a different function in the same dart file:
Future<String> initialStateUrl() async {
final FirebaseUser currentUser = await _auth.currentUser();
Firestore.instance.collection('users')
.document(currentUser.uid).collection('devices').document(initialStateLink).get()
.then((docSnap) {
var initialStateLink = ['initialStateLink'];
return initialStateLink.toString();
});
return initialStateUrl().toString();
}
So that it returns me the proper String. I am at a complete loss on how to do this and I was unable to find another question that answered this. Thanks for the ideas.
You can use Navigator.push(Route route) instead of Navigator.pushNamed(String routeName)
And I don't encourage you to place navigation code deeply inside the widget tree, it's hard to maintain your logic of application flow because you end up with many pieces of navigation code in many classes. My solution is to place navigation code in one place (one class). Let's call it AppRoute, it looks like:
class AppRoute {
static Function(BuildContext, String) onInitialStateLinkSelected =
(context, item) =>
Navigator.of(context).push(
new MaterialPageRoute(builder: (context) {
return new NewScreen(initialStateLink: initialStateLink);
}
));
}
and replace your code in onTap:
onTap: () {
var initialStateLink = "${ds['name']}";
AppRoute.onInitialStateLinkSelected(context, initialStateLink);
}
Now, you not only can pass data from class to another class but also can control your application flow in ease (just look at AppRoute class)
Just make initialStateLink variable Global and send it as an argument to the another class like below,
a) Specify route as follows
'/widget' : (Buildcontext context) => new Anotherscreen(initialStateLink)
b) Navigate to the Anotherscreen()
c) Then the Anotherscreen () will be like this,
class Anotherscreen () extends StatelessWidget {
var initialStateLink;
Anotherscreen (this.initialStateLink);
......
}
I ended up finding a different solution that solved the problem (kind of by skirting the actual issue).
A MaterialPageRoute allows you to build a new widget in place while sending in arguments. So this made it so I didn't have to send any data outside of the class, here is my code:
return new Card(
child: new GestureDetector(
onTap: () {
Navigator.push(context,
new MaterialPageRoute(
builder: (BuildContext context) => new WebviewScaffold(
url: ds['initialStateLink'],
appBar: new AppBar(
title: new Text("Your Device: "+'${ds['name']}'),
),
withZoom: true,
withLocalStorage: true,)
));},