I am trying to use an AlertDialog in flutter where the user can press a card to select an icon. Once they've selected the icon, the AlertDialog should show the newly selected icon.
Right now, I have it so that every time the user taps on the card, the card gets reloaded. However, this means that if I select an icon, I need to tap to select a second icon before it gets reloaded with my previous change.
All advice and/or different ideas on how to handle something like this are welcome.
Here is the code I have below:
import 'package:flutter/material.dart';
import 'package:test_003/data/dataStoreLegendItems.dart'; //has defaultIcon which should get updated
import 'package:test_003/dialogs/iconPickerDialog.dart';
class IconPickerCard extends StatefulWidget {
var alertDialogContext;
IconPickerCard({this.alertDialogContext});
#override
_IconPickerCardState createState() => _IconPickerCardState();
}
class _IconPickerCardState extends State<IconPickerCard> {
#override
Widget build(BuildContext context) {
return new Card(
child: ListTile(
leading: legendItems.defaultIcon,
title: Text('Select Icon'),
onTap: () async {
setState(() {
print("First line of IconPickerCard set state");
showIconPickerDialog(widget.alertDialogContext);
print('Icon Picker Card List Tile pressed');
});
},
),
);
}
}
This gets called by the Alert Dialog:
import 'package:flutter/material.dart';
import 'package:test_003/components/iconPickerForPopup.dart';
class ReuseAddPopup extends StatefulWidget {
#override
_ReuseAddPopupState createState() => _ReuseAddPopupState();
}
class _ReuseAddPopupState extends State<ReuseAddPopup> {
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
children: [
IconPickerCard(alertDialogContext: context),
],
),
);
}
}
This is what it looks like:
Alert Dialog
Then when the card is pressed:
IconPicker
After an icon is selected the changes do not get reflected on the card until the card is pressed again:
After Icon is selected
The context is different in overlay widgets such as modalBottomSheet and showDialog so the state does not rebuild, to make rebuild we have to wrap it with a StatefulBuilder widget like so:-
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter dialogState /*You can rename this!*/) {
return Card(
child: ListTile(
leading: legendItems.defaultIcon,
title: Text('Select Icon'),
onTap: () async {
dialogState(() {
print("First line of IconPickerCard set state");
showIconPickerDialog(widget.alertDialogContext);
print('Icon Picker Card List Tile pressed');
});
},
),
);
}
});
would recommend using this in the call to AlertDialog and make everything stateless.
Related
This issue is related with github #2502.
I am using GetMaterialApp from this package.
I'm not sure if this is a bug or not.
How to make the function in the first dialog useable by using Get.toNamed()?
It happened when using the Get.toNamed().
It works fine with Navigator.push() but I need Get.toNamed for the web app.
The first page has a button that will show the first dialog.
The first dialog will show the order type button list.
When pressing an order type button, the program will find a new order of this type and open the second page with a new order data.
The second page has some work to do and this work will open the second dialog.
After finishing this work, the user will click on the back button back to the first page and find a new order again.
The problem is when the second dialog works on the second page.
The first dialog on the first page will not work.
see video example.
web example.
code example:
import 'package:flutter/material.dart';
import 'package:flutter_test_exam_bug/config/path/page_path.dart';
import 'package:get/get.dart';
Future<void> _showMyDialog({required BuildContext context, required Widget child}) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) => child,
);
}
class PageTest extends StatefulWidget {
const PageTest({Key? key}) : super(key: key);
#override
_PageTestState createState() => _PageTestState();
}
class _PageTestState extends State<PageTest> {
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(onPressed: () => Get.toNamed(PagePath.test2), child: const Text("Open second page"))),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_), child: const Text("Open first dialog"));
return Scaffold(body: SafeArea(child: Center(child: openDialogButton_)));
}
}
class PageTest2 extends StatefulWidget {
const PageTest2({Key? key}) : super(key: key);
#override
State<PageTest2> createState() => _PageTest2State();
}
class _PageTest2State extends State<PageTest2> {
ButtonStyle buttonStyle = ElevatedButton.styleFrom(primary: Colors.green);
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context), child: const Text("I am second dialog"), style: buttonStyle)),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_),
child: const Text("Open second dialog"),
style: buttonStyle);
return Scaffold(appBar: AppBar(), body: SafeArea(child: Center(child: openDialogButton_)));
}
}
I think it is a bug.
When opening a dialog, the GETX ROUTE will change to the current page again.
Follow this in https://github.com/jonataslaw/getx/issues/2502
I have created a custom widget for list tile where I need to change icon of fav_outline to fav or google but it does not change, although items are adding and removing working properly but only icon does not change..it was working well before I created a custom widget for it
I have BottomNavigation for two screens alimonies and fav movies like that...
when I click on fav icon of all movie screen...it adds to fav movie ,,that's working fine, but only icon does not change
here is my coding of custom widget and my all movie screen..both are stateful
class MyCard extends StatefulWidget {
final Movie e;
final VoidCallback onfavtab;
MyCard({required this.e,required this.onfavtab});
#override
State<MyCard> createState() => _MyCardState();
}
class _MyCardState extends State<MyCard> {
#override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(widget.e.name.toString()),
subtitle: Text(widget.e.language),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: userlist[0].favmovies.contains(e)==true?Icon(Icons.favorite): Icon(Icons.favorite_outline),onPressed: widget.onfavtab),
IconButton(onPressed: (){}, icon: Icon(Icons.delete)),
],
),
leading: CircleAvatar(
backgroundImage: NetworkImage(widget.e.imageurl),
),
),
);
}
}
and here is my allmovie screen coding where I call custom widget
lass _AllMoviesState extends State<AllMovies> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(children: movielist.map((e) => MyCard(e: e, onfavtab: (){
if(userlist[0].favmovies.contains(e)==true)
userlist[0].favmovies.remove(e);
else
userlist[0].favmovies.add(e);
//looks like this is not working
setState(() {
print(userlist[0].favmovies);
});
},
)).toList(),),
);
}
}
put the icon which you need to edit it in provider class
and whene you click it just change the icon and notify it.
Here goes my code ,
I am using the TextButton to update the order ,But after every change in the dropdown item, onPress is automatically invoked and the function updateOrder is automatically invoked
import 'package:admin/constants/Constants.dart';
import 'package:admin/model/order_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class DetailsScreen extends StatefulWidget {
const DetailsScreen({Key? key}) : super(key: key);
#override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
List<String> _dropDownQuantities = [Pending, Confirmed, Rejected, Success];
late OrderModel order;
late String selectedStatus = order.status;
#override
Widget build(BuildContext context) {
order = ModalRoute.of(context)?.settings.arguments as OrderModel;
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Order Details"),
),
body: Column(children: [
Text(order.id),
DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: selectedStatus,
items: _dropDownQuantities
.map((e) => DropdownMenuItem<String>(
child: Text(e),
value: e,
))
.toList(),
onChanged: (value) {
setState(() {
selectedStatus = value;
});
},
)),
TextButton(onPressed: updateOrder(order, selectedStatus), child: Text("Confirm")),
]));
}
}
updateOrder(OrderModel order, String selected) {
print("I am executed");
}
So whenever i change the dropDown menu,
I am executed is printed in the console.
Edit:
But when i used the container with InkWell it was working fine. Why not working with TextButton ?
You are directly calling the method on build, you can create an inline anonymous function to handle this.
TextButton(
onPressed: ()=> updateOrder(order, selectedStatus),
child: Text("Confirm")),
onPressed Called when the button is tapped or otherwise activated.
While we use onPressed:method() call on every build, on dropDown onChanged we use setState, and it rebuilds the UI and onPressed:method() call again.
What we need here is to pass a function(VoidCallback) that will trigger while we tap on the button. We provide it like,
onPressed:(){
myMethod();
}
More about TextButton.
What I want to do is to have for the user first time reach the screen that oine alertDialog appears.
I use this code to move to another screen:
Navigator.push(context, MaterialPageRoute(builder: (context) => IfProfilePage()));
in this specific case i switch tyo IfProfilPage but what I want is to get in IfProfilePage for the first time an alertdialog which I will build later, but this dialogf will contains some information that user has to follow.
thanks
you can pass it in initstate method
it has to be a stateful widget
class DestinationPage extends StatefulWidget {
#override
_DestinationPageState createState() => _DestinationPageState();
}
class _DestinationPageState extends State<DestinationPage> {
#override
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
showAlertdialog(context);
});
}
and here is a example of alert dialog that you can use
showAlertDialog(BuildContext context) {
// set up the button
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () { },
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("My title"),
content: Text("This is my message."),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
when I tap text widget it will rebuild widget, I don't know why
main.dart
import 'package:flutter/material.dart';
import './loading.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Main Build.");
return MaterialApp(
title: 'chat',
home: LoadingPage(),
);
}
}
loadingPage.dart
import 'package:flutter/material.dart';
import 'signUp/inputPhone.dart';
class LoadingPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Loading Page Build.");
FocusScope.of(context).requestFocus(new FocusNode());
new Future.delayed(Duration(seconds: 3), () {
print("Flutter APP UI");
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => InputPhone()));
});
return Stack(
fit: StackFit.expand,
alignment: AlignmentDirectional.center,
children: <Widget>[
Image.asset("images/bgPatten.png", fit: BoxFit.cover),
Image.asset("images/logoAll.png"),
],
);
}
}
inputPhone.dart
import 'package:flutter/material.dart';
class InputPhone extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("input phone build");
return GestureDetector(
onTap: () {
print("Tap Outside.");
FocusScope.of(context).requestFocus(new FocusNode());
},
child: Scaffold(
appBar: new AppBar(
title: Text("STEP 1"),
leading: Container(),
elevation: 0.0,
),
body: TextField(),
),
);
}
}
but when I change main.dart home to InputPhone(), it will not happen
sorry I really don't know what's wrong with my code
please give me some help... thank you all... :)
When you tap TextField Widget, it makes the keyboard show up. and When keyboard shows up your screen size is changed. And just because your screen size is changed the widgets that you are using needs to build again.
And this thing will happen with both StatefulWidget and StatelessWidget.
You can verify this behavior by making State of an StatefulWidget extend WidgetsBindingObserver class. The method didChangeMetrics() will be called when your KeyBoard shows up by tapping on TextField.
This happens because of scaffold's resizeToAvoidBottomPadding which is by default true.
Refer following comments from the source code.
/// If true the [body] and the scaffold's floating widgets should size
/// themselves to avoid the onscreen keyboard whose height is defined by the
/// ambient [MediaQuery]'s [MediaQueryData.viewInsets] `bottom` property.
///
/// For example, if there is an onscreen keyboard displayed above the
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard.
///
/// Defaults to true.
final bool resizeToAvoidBottomInset;
In StatelessWidget the Build method is being called for many reasons like (screen rotation, animations, scrolling...) so that you should use StatefulWidget