Issue with statefulWidget unable to make desired changes - flutter

I am working on a statefulWidget and my purpose is to make sure that the next button is not clickable until an option (in this language is selected). However it doesn't seem to work, I also added Yaesin's(Someone who answered) answer to the code
ListView.builder(
itemCount: histoires.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
histoires[index].title,
style: TextStyle(color: Colors.pink),
),
trailing: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) =>
AlertDialog(
content: Column(children: [
InkWell(
onTap: () {
_handleTap;
},
child: ListTile(
trailing: Icon(Icons
.flag_circle_rounded),
title: Text(
"French",
style: TextStyle(
color: Colors
.blueGrey),
))),
_active
? InkWell(
onTap: () {},
child: Image.asset(
"assets/nextactive.png",
height: height * 0.2,
width: width * 0.4),
)
: Image.asset(
"assets/nextinactive.png",
height: height * 0,
width: width * 0)
]),
));
});
}));
}),

To update dialog UI, you can use StatefulBuilder's setState
return StatefulBuilder(
builder: (context, setState) =>
AlertDialog(
content: Column(children: [
While using separate method, pass the StatefulBuilder's setState to the function. For your case, it will be
onPressed: () async {
await showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setStateSB) => AlertDialog(
content: Column(children: [
InkWell(
onTap: () {
_handleTap(setStateSB);
},
child: ListTile(
Also make sure to receive this setStateSB(renamed to avoid confusion with state's setState).
_handleTap(setStateSB){ ....
More about using StatefulBuilder

Since your in a Dialog, for setState to work, you need to wrap it with a StatefulBuilder.
You haven't included your full code, so I'm using this example taken from the docs:
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int? selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int? value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
See also
A YouTube video by the Flutter team explaining StatefulBuilder

Related

Flutter: setState() or markNeedsBuild() called during build from onPressed

I am trying to show a dialog when someone clicks on the trailing icon of a ListTile but I am getting the "setState() or markNeedsBuild() called during build" error message. Here are the cards (in a MyCard class) and the function which is giving me the error message (checkCard):
static Widget buildCard(MyCard card, BuildContext context) {
var dateFormat = DateFormat('MM/dd/yyyy');
return Column(
children: [
Align(
alignment: Alignment.centerRight,
child: Text(dateFormat.format(card.createdOn.toDate()))),
const SizedBox(height: 6),
ListTile(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
tileColor: Colors.white,
leading: CircleAvatar(child: Text(card.subCategory)),
title: Text("Score: " + card.score + " Misses: " + card.misses),
subtitle: card.comment.isNotEmpty
? Text("Comment(s): " + card.comment)
: null,
trailing: IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: checkCard(card, context)),
),
const SizedBox(height: 18),
],
);
}
static checkCard(MyCard card, BuildContext context) {
showDialog(context: context, builder: (context) => const Text("Hello"));
}
and they get built from StatefulWidgets as follows:
#override
Widget build(BuildContext context) =>
FutureBuilder<List<Map<String, dynamic>>>(
future: MyCard.getData(3, Utils.ddfDropdown)!
.whenComplete(() => setState(() {
isLoading = false;
})),
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return FutureBuilder<List<MyCard>>(
future: MyCard.readData(snapshot.data),
builder: (context, cards) {
if (cards.hasData) {
final card = cards.data!;
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: card.length,
itemBuilder: (context, index) {
return MyCard.buildCard(card[index], context); //Cards being built here
},
),
);
}
I looked up this error elsewhere; one suggestion was to do the following:
static checkCard(MyCard card, BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(context: context, builder: (context) => const Text("Hello"));
});
}
but it seems like doing that calls the showDialog without having to click on the trailing IconButton and the screen then simply goes black.
Another suggestion was to do something like:
static checkCard(MyCard card, BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback((_) {
showDialog(context: context, builder: (context) => const Text("Hello"));
});
}
but the same thing happens (it seems like showDialog gets called without having to click and the screen goes black).
The issue is coming from, calling the method while building the widget.
onPressed: checkCard(card, context)),
You like to call the method when it is pressed, so it can be
onPressed:()=> checkCard(card, context)),

Flutter alert box is not updating picked image from gallery

I am using an alert box where I am getting the image from gallery of the user, but the updated image is not getting displayed.
When I close the alert box and again open the alert box, then it's getting displayed. I am using provider package to handle the data.
Here is a video of what I am getting now
Here is my code:
child: ChangeNotifierProvider<MyProvider>(
create: (context) => MyProvider(),
child: Consumer<MyProvider>(
builder: (context, provider, child) {
return Column(
children: [
ElevatedButton(
onPressed: () {
showDialog(
barrierDismissible: true,
context: context,
barrierColor: Colors.black.withOpacity(0.5),
builder: (ctx) => AlertDialog(actions: <Widget>[
----> // alert box styling
Expanded(
child: Column(
children: [
CircleAvatar(
backgroundColor:
Colors.transparent,
radius: 175,
child: ClipOval(
child: provider
.image !=
null
? Image.network(
provider.image
.path,
height: 200,
)
: Image.asset(
'assets/profile.webp',
width: 250.0,
height: 250.0,
fit: BoxFit
.cover,
),
)),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: <Widget>[
ElevatedButton(
onPressed: () async {
var image = await ImagePicker()
.pickImage(
source: ImageSource
.camera);
provider.setImage(
image);
},
child: Text(
'Use camera',
style: t3b,
),
),
},
child: const Text('Click me ')),
],
);
},
),
),
),
);
}
}
class MyProvider extends ChangeNotifier {
var image;
Future setImage(img) async {
image = img;
notifyListeners();
}
I am also facing the same issue in mobile development then I know we have to rebuild the whole dialog and then it will work well
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
also sharing the reference I used for this: Reference for solving this same

Values in RangeSlider inside AlertDialog not updating

I cannot understand why my rangeslider is not updating values when dragging. I am supposed to update the state with the onChanged function, but nothing seems to work. It only works when I press the "Apply" button and I reopen my alertDialog again, where I see the values of the slider updated. All this is wrapped inside and Appbar in a statefulWidget. When I press the filter button a pop up appears with the filter.
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Filter'),
content: SizedBox(
child: Card(
child: Column(
children: [
Text('Age'),
RangeSlider(
values: _rangeValues,
divisions: 20,
labels: RangeLabels(
_rangeValues.start.round().toString(),
_rangeValues.end.round().toString()),
onChanged: ( value ) {
_rangeValues = value ;
setState(() {
isFiltering = false;
varSelectedFilterAgeStart = value.start;
varSelectedFilterAgeEnd = value.end;
});
},
min: 0.0,
max: 20.0,
),
],
),
),
),
actions: [
ElevatedButton(
child: const Text('Apply'),
onPressed: () {
setState(() {
isFiltering = true;
varSelectedFilterAge = varSelectedFilterAgeStart;
});
Navigator.of(context).pop(varSelectedFilterAge);
},
),
ElevatedButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
What am I doing wrong??
Wrap your AlertDialog with StatefulBuilder and use its setState.
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) => AlertDialog(

AlertDialog should show another AlertDialog but it's not happening till i close it and open it again

I want to show another AlertDialog when I click on one of its children
But when I click on it doesn't
Show it until I close the Alert and open it again
I want to navigate to Second AlertDialog without closing it
Any help will be appreciated
any way to make it open another dialog or a way to close it and open it again
Here is the code
padding: const EdgeInsets.only(top: 12.0),
child: ListView(children: [
FutureBuilder<DropDown>(
future: getDropData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
DropDown dropdown = snapshot.data;
return RaisedButton(
color: maincolor,
splashColor: accentcolor,
onPressed: () {
showDialog(
context: context,
useSafeArea: true,
child: Center(
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: ListView.builder(
itemCount: dropdown.categories.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(
top: 8.0, right: 8, left: 8),
child: Container(
),
child: FlatButton(
onPressed: () {
setState(() {
categoryID = dropdown
.categories[index]
.categoryId;
});
getDropData();
},
child: Text(
dropdown.categories[index].name,
)),
),
);
}),
),
));```
I think you just need to use setState() to wrap the showDialog() but I can't test it because I don't have a DropDown class.
EDIT
I tried with a simple structure and it works fine. As Dung Ngo mentioned, just use a StatefulBuilder to build the content of the first AlertDialog widget. Triggering another showDialog() inside to bring up the second AlertDialog widget.
class _YourWidgetState extends State<YourWidget> {
AlertDialog alert = AlertDialog(content: Center(child:Text("Second Alert Dialog")));
#override
Widget build(BuildContext context) {
return RaisedButton(onPressed: (){
showDialog(
context: context,
builder: (_) => AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState){
return Column(
children: <Widget>[
RaisedButton( onPressed: (){
showDialog(context: context, builder: (_) => alert);
}),
RaisedButton(onPressed: (){
showDialog(context: context, builder: (_) => alert);
}),
],
);
}
),
)
);
});
}
}
Results
The setState doesn't work until you close the first one because it belongs to the context of mainpage, not the context of your 1st dialog.
You can use a StatefulBuilder to create a a StateSetter that invoke a rebuild base on the context of the 1st dialog: https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html
Thanks to Dung Ngo and Kennith for your answers it really helped me and I learnt from your answers a lot your answers were right by the way
here what I have done
a method that opens the second dialog
showGeneralDialog(
barrierLabel: "Barrier",
barrierDismissible: true,
barrierColor: maincolor,
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (_, __, ___) {
return FutureBuilder<Manufacturer>(
future: getManufucturer(categoryID, parentID),
builder: (context, snapshot) {
if (snapshot.hasData) {
Manufacturer dropdown = snapshot.data;
return Container(
height: 400,
width: 200,
child: ListView.builder(
itemCount: dropdown.manufacturers.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: RaisedButton(
child: Text(
dropdown.manufacturers[index].name,
style: GoogleFonts.cairo(
color: maincolor,
fontSize: 14,
fontWeight: FontWeight.bold),
),
onPressed: () async {
parentID =
dropdown.manufacturers[index].manufacturerId;
manufacturerID = parentID;
print(parentID);
Manufacturer newDropDown =
await getManufucturer(categoryID, parentID);
Navigator.pop(context);
Navigator.pop(context);
}),
);
},
),
);
} else {
return Center(
child: CircularProgressIndicator(
strokeWidth: 12,
backgroundColor: maincolor,
),
);
}
});
},
transitionBuilder: (_, anim, __, child) {
return ScaleTransition(
scale: Tween(begin: 0.0, end: 1.0).animate(anim),
child: child,
);
},
);
}
then I created the button that opens the first dialog and it that dialog I
called that method that I defined earlier
again thanks for your efforts Dung and kennith you really helped me

How to add tabs to an alert dialog in flutter

I have a stand-alone class called dialogs.dart which contains a reusable alert dialog. I want this alert dialog to have tabs, but I have no idea how to implement it if it's possible. Any ideas are highly appreciated.
Here's my code for dialogs.dart
import 'package:flutter/material.dart';
enum DialogAction{yes,abort}
class Dialogs{
static int selectedRadio;
static void setSelectedRadio(int val){
selectedRadio=val;
}
static Future<DialogAction> yesAbortDialog(
BuildContext context,
String title,
String body,
)async{
final action= await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
title: Text(title),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(3, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
actions: <Widget>[
FlatButton(
onPressed: ()=>Navigator.of(context).pop(DialogAction.abort),
child: const Text('cancel'),
),
RaisedButton(
onPressed: ()=>Navigator.of(context).pop(DialogAction.yes),
child: const Text('Proceed', style: TextStyle(color: Colors.white),),
color: Colors.green,
),
],
);
}
);
return(action!=null)?action: DialogAction.abort;
}
}
Don't use 'AlertDialog' for this purpose. Use 'Dialog' class. Inside it you can configure your own child the way you like. Tie tabs and events to it. Refer this - https://api.flutter.dev/flutter/material/Dialog-class.html
You can use 'container' with margin to get the barrier effect.