Two scaffolds into one page because of navigation in flutter - flutter

I have an issue with the navigation process in my Flutter app.
First, I have a home page that has a button. When I press search, I go to the search page, obviously, and the results will appear on the home page again.
If no results were found, a dialog would appear that says "Try again" and has an OK button.
I want to press the OK button to navigate to the search page again. But there is a problem with the scaffolds like the image below. How can I fix this?
And my home page code, for alert dialog is like this:
Widget build(BuildContext context) {
print(widget.selectedURL);
return MaterialApp(
home: Center(
child: FutureBuilder<List<Pet>>(
future: API.get_pets(widget.selectedURL),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Pet>? pet_data = snapshot.data;
print(pet_data?.length);
return Padding(
padding: const EdgeInsets.all(8.0),
child:(pet_data?.length == 0)
? AlertDialog(
title: const Text("Failed"),
alignment: Alignment.topCenter,
content: const Text(
'No Pets Found. Please try different search options.'),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const Search())),
)
],
)
: ListView.builder(
//showing the list of results
)

The problem is you are wrapping everything inside a MaterialApp.
MaterialApp defines a Navigator property, which means it should be at the top-level of your application and it should be unique. If you have two different MaterialApp they will use their own Navigator, hence they will not share routes. See more informations here MaterialApp.
The usual design is:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: YourWidgets(
[...]
)//YourWidgets
)//Scaffold
);//MaterialApp
}
Try removing MaterialApp, or changing your MaterialApp to something else, like Material if you really need additional customization.

Related

Showing one card in flutter when no other cards are displaying

I have a group of cards that are connected to my cloud firestore which shows when the icon related to each card is chosen. However, I want a card that tells the user to choose an icon when none of the other cards are showing. This is my code:
class Tips extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream:
FirebaseFirestore.instance.collection('moodIcons').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please Wait');
var currentCards = 0;
return Container(
child: new ListView(
children: snapshot.data.documents.map<Widget>((DocumentSnapshot document) {
if (document.data()['display'] == true) {
//code to display each card when specific icon is chosen
} else {
if (currentCards < 1) {
currentCards++;
return new Card(
child: new Column(
children: <Widget>[
new Container(
width: 400.0,
height: 45.0,
child: new Text(
"Choose an icon to show a tip!",
),
],
));
Currently, the card shows all the time but I want it to disappear when an icon is clicked. When an icon is clicked, the value of display becomes true.
I tried to use a counter so that if the value of display is true then it adds one to the counter then if the counter is more than 0, the card in the else statement wont show. However, this didn't work as I couldn't return the value of the counter from within the if statement.
Any help would be appreciated!
You could conditionally add an item at the beginning of your ListView if no document is set to display: true:
class Tips extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('moodIcons').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please Wait');
return Container(
child: new ListView(children: [
if (!snapshot.data.documents
.any((doc) => doc.data()['display'] == true))
Card(child: Text("Choose an icon to show a tip!")),
...snapshot.data.documents
.where((doc) => doc.data()['display'] == true)
.map((doc) => MyWidget(doc))
.toList(),
]),
);
},
),
);
}
}
If you want this to be done by StatelessWidget you need some state management like Bloc or Riverpod, your own etc.
a good answer here
You need to abstract your stream and create an event/action when you click on the icon.
That event is to be handlined via the above mentioned approaches. where a new card will be pushed to the stream.
Or you can make your widget Stateful, create a list of Card Widgets (to provide to your list builder) and use setState to add a element to a Card to the list.

How to handle back button of android in flutter

I have navigate from one page to another page like from a button which is on the page
1st Page <---> 2nd Page <---> 3rd Page <---> 4th Page
If i am on the any page after clicking on the back button of android i want to navigate to the homepage instead of going to the previous page.
Like if i am on page 2 if i press back button i go to the page 1 but i want to pop all the elements on the stack and go to the main home page same for all.
Future<bool> pushPage(BuildContext context) {
// return Navigator.of(context).pop(true);
return Navigator.of(context).pushNamedAndRemoveUntil('/quiz2',
ModalRoute.withName("/landingPage"));
}
class _Quiz1State extends State<Quiz1> {
bool hasSolved = false;
int solvedOption = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
pushPage(context);
return false;
},
child: Scaffold(
body: Stack(
alignment: Alignment.topCenter,
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color(0xffBD00FF),
Color(0xffF1509E),
],
),
),
),
mainQuiz(),
Container(
child: uiElements(context),
margin: EdgeInsets.only(top: 16),
),
],
),
),
);
}
mainquiz() function is the 2nd Page Function
We know that Navigator.pop() removes to topmost page available on the stack. There is another function named Navigator.popUntil() which will keep on popping out the pages from the stack until the desired page is reached.
For that first (If you have not done this yet) you need to give names to all your pages. These will act like an ID card for the page when you reference them in you functions. For that on the topmost part of your tree where the widget MaterialApp is located, do like this :
MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder> {
'/screen1': (BuildContext context) => new Screen1(),
'/screen2' : (BuildContext context) => new Screen2(),
'/screen3' : (BuildContext context) => new Screen3(),
'/screen4' : (BuildContext context) => new Screen4()
},
)
Basically what you do here is you pre-define the names for all the routes you will be taking in your app.
Now say the above example's navigation works like this :
Screen1 -> Screen2 -> Screen3 -> Screen4
Now say I am at Screen4() ..... then my current stack will be [Screen4,Screen3,Screen2,Screen1] .....(latest first, oldest last), and on hitting the back button I need to directly pop out to Screen1().... (Home page in your case). Then instead of out simple Navtigator.pop() I will do something like this :
Navigator.popUntil(context, ModalRoute.withName('/screen1'));
This will keep popping out the widgets until it reaches screen1.
Hope this solves your porblem.
NOTE:
Once you have preDefined all your routes at the start, instead of calling Navigator.push(context,MaterialPageRoute((context) => Screen1());
You can do this :
Navigator.pushNamed(context, '/screen1');
Edit :
Say if your HomePage was not already present in stack and you want to show it after popping out all the screens. You can do :
Navigator.of(context).pushNamedAndRemoveUntil('/homepage', (Route<dynamic> route) => false);
This will add Homepage() after removing all the other screens present in the stack. [" (Route route) => false " will pop out all the screens from the stack. ].
Then if you want to pop out the screen only till a given screen is reached(say till we reach screen1) you can do something like:
Navigator.of(context).pushNamedAndRemoveUntil('/homepage','/screen1');
After 1st page use Navigator.pushReplacement instead Navigator.push
You can also use this to go first page:
Navigator.of(context).popUntil((route) => route.isFirst);

How to pass context to a child widget in flutter

I have a statefull widget W1 which calls a stateless widget W2.
W2 has onTap functionality. I want to show an alert dialog in W2's onTap().
onTap:() {Alert(context: context, title:'Hi');},
I dont get any error, but no alert is shown on tap. I tried passing context as a parameter to W2 but I still dont see any dialog box.
What is the right way to show a dialog box from W2?
I am using rflutter_alert package Link
Thanks
You have to wrap your Alert(context: context, title:'Hi'); with showDialog(context: context, builder: (BuildContext context) => Alert(context: context, title:'Hi'));
Here is the cookbook sample:
Future<void> _neverSatisfied() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Anyway, for your question about how to pass context, If you are creating a Stateless or Stateful widget you dont need to pass the context, you can get it from build(BuildContext context) {}.
Adding .show() in end solved it.
onTap:() {Alert(context: context, title:'Hi').show();}
Its clearly documented in rflutter_alert package, but I somehow missed it.
Pass the context in the function call in onTap:
onTap:(context) {Alert(context: context, title:'Hi');},

Is there a simple way to make android back button act exactly like app bar back button?

I want the hardware back button act exactly like my app bar back button always. I want it for all my screens. WillPopScope seems not easy to use because my appbar will be always the child of the WillPopScope and therefore I would need to pass data to parent for many different cases.
Edit(more details):
In registration page of my app, if the registration process is on going I am blocking the user interaction with the app by using IgnorePointer but of course this does not work for hardware back button.
Also I designed my appbar back button according to the state of the registration page. So there are at least 3 cases for appbar back button. I want all to apply to the hardware back button too. The problem is I am in a child widget therefore I don't want to call function for each case and update the state of the parent widget. It is not useful and It is very painful to do.
If I understood your problem correctly, I have something that might help you. I've implemented this code on one of my projects. However, I have used Provider and Bloc for state management.
signup_page.dart
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: ChangeNotifierProvider<SignupProvider>(
create: (context) => SignupProvider(),
child: BlocProvider<SignupBloc>(
create: (context) => _signupBloc,
child: BlocListener<SignupBloc, SignupState>(
listener: (context, state) {
if (state is SignupCancelled) {
_closeSignup();
} else if (state is SignupSuccessful) {
_closeSignup(delay: 1000);
} else if (state is SignupInitial) {
_returnToCredentials();
} else if (state is SignupProceededToDetails) {
_proceedToDetails();
}
},
child: Scaffold(
appBar: AppBar(
title: const Text("Signup"),
),
body: SafeArea(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: <Widget>[
const SignupCredentialsForm(),
const SignupDetailsForm(),
],
),
),
),
),
),
),
);
}
Future<bool> _onWillPop() async {
int page = _pageController.page.toInt();
_signupBloc.add(
BackButtonPressed(page: page),
);
return false;
}
_closeSignup({int delay = 0}) async {
await Future.delayed(Duration(milliseconds: delay));
Navigator.of(context).pop();
}
_returnToCredentials() {
_pageController.animateToPage(0,
duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
}
_proceedToDetails() {
_pageController.animateToPage(1,
duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
}
If you look at my code, I have a PageView consisting of 2 pages. The PageView pages both use AppBar, and contains the leading back button on the top left.
Normally, pressing the back button on the appbar, or the hardware back button will lead to closing the page, and navigating back to the previous page.
However, since I have WillPopScope, on signup_page, that is overriden. Hence, I'm able to control what I want to happen when the back button (appbar or hardware) is pressed. Which you can see, I can move to the next page view using _proceedToDetails(), or return to the first form using _returnToCredentials(), and close the page if I'm in the first form and back button is pressed, using _closeSignup(), similarly, closing the page even when I'm at the last form if the signup is successful.

Navigator gets the wrong context

I have a flutter application which shows a camera, then scans a qr-code using git://github.com/arashbi/flutter_qrcode_reader and on the call back, I want to navigate to another screen to show the information I get from some query. The problem is, it seems Navigator get a wrong context, so instead of full page navigation, I see the second page pushed inside my first page as a widget.
The build of first page is as follow :
#override
Widget build(BuildContext context) {
Widget main =
new Scaffold(
appBar: _buildAppBar(),
body: Column(
children: <Widget>[
_eventButton ?? new Text(""),
new Center(
child: new Text("Hi"))
],
),
floatingActionButton: new FloatingActionButton(
onPressed:
() {
new QRCodeReader()
.setAutoFocusIntervalInMs(200)
.setForceAutoFocus(true)
.setTorchEnabled(true)
.setHandlePermissions(true)
.setExecuteAfterPermissionGranted(true)
.scan().then((barcode) => _showTicketPage(barcode));
}
,
child: new Icon(Icons.check),
),
);
if (!_loading) {
return main;
} else {
return new Stack(
children: [main,
new Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(220, 220, 220,
0.3) // Specifies the background color and the opacity
),
child: new Center(child: new CircularProgressIndicator(),))
]
);
}
}
The 'Hi' Widget is for debugging, and the navigate method is
void _showTicketPage(var barcode) {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => TicketScreen(barcode)));
}
After getting the barcode, the second screen - TicketScreen - is pushed under 'Hi' Widget. I didn't even know this was possible to do.
I tried to get the context of the screen, save it to a field var, but that didn't help either.
How can I fix it? I ran out of ideas.