Related
I am trying to build a filter using a dynamically populated DropDownMenu that gets "categories" from an API endpoint and then upon one of these categories being selected, filters and only displays the organizations that fall into the selected category. This list of organizations is built using either OrganizationListWidget or OrganizationByCategoryListWidget depending on whether the user has currently selected some category or not yet, respectively. The issue is, this only works successfully the first time a user picks a category, beyond this, anytime the user picks a different category from the dropdown the organization list remains unchanged and I am unsure why this is occurring. I am brand new to Flutter so I apologize for poor code quality/if this is trivial. If anyone could explain why this is happening and a possible fix it would be much appreciated.
Edit: The API is working, the organizations initially load in and build into the list fine, and they are filtered correctly the first time a category is selected.
import 'package:flutter/material.dart';
import 'package:myteam_app/categoryform.dart';
import 'package:myteam_app/screens/organization_form.dart';
import 'package:myteam_app/models/organization.dart';
import 'package:myteam_app/resources/size_resource.dart';
import 'package:myteam_app/utility/myteam_event_listener.dart';
import 'package:myteam_app/widgets/myteam_screen.dart';
import 'package:myteam_app/widgets/myteam_search_bar.dart';
import 'package:myteam_app/widgets/organization_by_category_list_widget.dart';
import 'package:myteam_app/widgets/organization_list_widget.dart';
import 'package:myteam_app/api/myteam/api.dart';
/// HomeScreen is the home page or starting page of the app when the user is logged in
///
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _currentSelection = "";
static const String ADD_ORGANIZATION_CHOICE = 'Add Organization';
static const String ADD_CATEGORY_CHOICE = 'Add Category';
//var widg = OrganizationByCategoryListWidget(_currentSelection);
// reference: how to programmatically open drawer https://stackoverflow.com/a/57748219/17836168
final GlobalKey<ScaffoldState> _key = GlobalKey();
final List<String> items = [ADD_ORGANIZATION_CHOICE, ADD_CATEGORY_CHOICE];
final Future<List<String>> categories = API().getCategories();
final myteamEventListener<void, void> onDrawerOpenEvent =
myteamEventListener();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => myteamScreen(
key: _key,
onOpenDrawerEvent: onDrawerOpenEvent,
body: Column(
// padding: EdgeInsets.symmetric(vertical: 30.0),
children: <Widget>[
myteamSearchBar(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Add..."),
items: items.map(buildMenuItem).toList(),
onChanged: (value) {
switch (value) {
case ADD_ORGANIZATION_CHOICE:
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrganizationForm()),
);
break;
}
case ADD_CATEGORY_CHOICE:
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryForm()),
);
break;
}
}
},
),
),
FutureBuilder<List<String>>(
future: API().getCategories(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//print("we have called anew.");
//print(snapshot.data);
}
return snapshot.hasData
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Select a category"),
items: snapshot.data?.map((item) {
return DropdownMenuItem<String>(
value: item, child: Text(item));
}).toList(),
onChanged: (value) {
setState(() {
print(
"cur selection is + " + _currentSelection);
_currentSelection = value.toString();
//status += 1;
//print(_currentSelection);
//OrganizationByCategoryListWidget(
// _currentSelection);
});
},
value: _currentSelection == ""
? null
: _currentSelection,
))
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Select a category"),
items: snapshot.data?.map((item) {
return DropdownMenuItem<String>(
value: item, child: Text(item));
}).toList(),
onChanged: (value) {
setState(() {
//print("ok");
_currentSelection = value.toString();
//status += 1;
//OrganizationByCategoryListWidget(
//_currentSelection);
});
},
value: _currentSelection,
));
}),
_currentSelection == ""
? OrganizationListWidget()
: OrganizationByCategoryListWidget(
_currentSelection, UniqueKey())
// homeContent(),
],
),
);
Widget createCard(Organization organization) {
return Card(
margin: const EdgeInsets.all(SizeResource.cardMargin),
child: Padding(
padding: const EdgeInsets.all(SizeResource.cardInnerPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(organization.name),
Text("Description"),
],
),
));
}
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
);
}
Currently, I have an AlertDialog with an IconButton. The user can click on the IconButton, I have two colors for each click. The problem is that I need to close the AlertDialog and reopen to see the state change of the color icon. I want to change the IconButton color immediately when the user clicks it.
Here is the code:
bool pressphone = false;
//....
new IconButton(
icon: new Icon(Icons.phone),
color: pressphone ? Colors.grey : Colors.green,
onPressed: () => setState(() => pressphone = !pressphone),
),
Use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
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.
The sample code:
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);
},
);
}),
);
},
),
);
},
);
And as I mentioned, this is what is said on the showDialog docs:
[...] The widget returned by the builder does not share a context with the location
that showDialog is originally called from. Use a StatefulBuilder or a
custom StatefulWidget if the dialog needs to update dynamically.
This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.
Update:
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return MyDialog();
});
},
)));
}
}
class MyDialog extends StatefulWidget {
#override
_MyDialogState createState() => new _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
Color _c = Colors.redAccent;
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
color: _c,
height: 20.0,
width: 20.0,
),
actions: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () => setState(() {
_c == Colors.redAccent
? _c = Colors.blueAccent
: _c = Colors.redAccent;
}))
],
);
}
}
First you need to use StatefulBuilder. Then i am setting _setState variable, which even could be used outside StatefulBuilder, to set new state.
StateSetter _setState;
String _demoText = "test";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
_setState is used same way as setState method. For example like this:
_setState(() {
_demoText = "new test text";
});
If you're separating your data from the UI via View Models and using the Provider package with ChangeNotifier, you'll need to include your current model like so within the widget calling the dialog:
showDialog(context: context, builder: (dialog) {
return ChangeNotifierProvider.value(
value: context.read<ViewModel>(),
child: CustomStatefulDialogWidget(),
);
},
Note that there may be a cleaner way to do this but this worked for me.
Additional info regarding Provider: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Not sure if this is best practice, but I solved the issue of updating both the dialog state and the content state by wrapping the setState functions, after using the top answer to add state to the dialog:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, newSetState) { // Create a "new" state variable
return AlertDialog(
content: DropdownButton(
value: listItem.type,
items: allItems
onChanged: (value) {
newSetState(() {
setState(() {
// HERE SET THE STATE TWICE
// Once with the "new" state, once with the "old"
});
});
})
),
);
}
);
}
),
In fact, you can use StatefullBuilder but the problem is that when you use this widget you cant change the state of the base screen! Prefer to navigate to a new screen in order to use setState.
I was stuck with this issue.You have to Change the name of setState to any Other name and pass this set state to all sub functions.
This will update your Dialog ui on time.
return StatefulBuilder(
builder: (context, setStateSB) {
return AlertDialog(
title: Text("Select Circle To Sync Data!" ,style: TextStyle(color: Colors.white),),
content: Column(
children: [
Text("Select Division!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DivisionName_firstValue,
items: _DivisionName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black)),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DivisionName_firstValue = newValue!;
if(sync_DivisionName_firstValue !="Select Division Name"){
print("sync_DivisionName_firstValue$sync_DivisionName_firstValue");
_getDistrictName(sync_DivisionName_firstValue,setStateSB);
}else{
refreashDivisionName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select District!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DistrictName_firstValue,
items: _DistrictName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DistrictName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name"){
print("sync_DistrictName_firstValue$sync_DistrictName_firstValue");
_getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,setStateSB);
}else{
refreashDistrictName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Tehsil!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_TehsilName_firstValue,
items: _TehsilName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_TehsilName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name"){
print("sync_TehsilName_firstValue$sync_TehsilName_firstValue");
_getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,setStateSB);
}else{
refreashTehsilName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Rating Area Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_RatingAreaName_firstValue,
items: _RatingAreaName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_RatingAreaName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name" && sync_RatingAreaName_firstValue != "Select Rating Area Name"){
print("sync_RatingAreaName_firstValue$sync_RatingAreaName_firstValue");
_getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue,setStateSB);
}else{
refreashWardCircleName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Ward Circle Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_circle_name_firstValue,
items: _circle_name_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_circle_name_firstValue = newValue!;
print("sync_circle_name_firstValue$sync_circle_name_firstValue");
// if(sync_circle_name_firstValue != "Select Ward Circle Name"){
//
// _getWardCircleName(sync_RatingAreaName_firstValue);
// }else{
//
// }
});
},
)),
),
],
),
),
]),
backgroundColor:Color(0xFFEC9F46),
actions: [
okButton,SyncButton
],
);
},
);
One of the Inner Funciton is like this.
Future<void> refreashDivisionName( StateSetter setInnerState) async {
final List<String> _division_name = await getDivisionNameList();
final List<String> _district_name_list = await getDistrictName(sync_DivisionName_firstValue);
final List<String> _tehsil_name_list = await getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue);
final List<String> _rating_area_name_list = await getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue);
final List<String> _ward_circle_name_list = await getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue);
setInnerState(() {
_division_name.insert(0, "Select Division Name");
_DivisionName_list = _division_name;
sync_DivisionName_firstValue = _DivisionName_list[0];
_district_name_list.insert(0, "Select District Name");
_DistrictName_list = _district_name_list;
sync_DistrictName_firstValue = _DistrictName_list[0];
_tehsil_name_list.insert(0, "Select Tehsil Name");
_TehsilName_list = _tehsil_name_list;
sync_TehsilName_firstValue = _TehsilName_list[0];
_rating_area_name_list.insert(0, "Select Rating Area Name");
_RatingAreaName_list = _rating_area_name_list;
sync_RatingAreaName_firstValue = _RatingAreaName_list[0];
_ward_circle_name_list.insert(0, "Select Ward Circle Name");
_circle_name_list = _ward_circle_name_list;
sync_circle_name_firstValue = _circle_name_list[0];
});
}
I hope you under Stand.
base on Andris's answer.
when dialog share the same state with parent widget, you can override parent widget's method setState to invoke StatefulBuilder's setState, so you don't need to call setState twice.
StateSetter? _setState;
Dialog dialog = showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
// set the function to null when dialo is dismiss.
dialogFuture.whenComplete(() => {_stateSetter = null});
#override
void setState(VoidCallback fn) {
// invoke dialog setState to refresh dialog content when need
_stateSetter?.call(fn);
super.setState(fn);
}
Currently to retrieve the value of Dialog I use
showDialog().then((val){
setState (() {});
print (val);
});
Example
1st screen
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AddDespesa();
}).then((val) {
setState(() {});
print(val);
}
);
}
2nd screen
AlertDialog(
title: Text("Sucesso!"),
content: Text("Gasto resgristrado com sucesso"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
Will be printed true,
I am using the dropdown_search version 0.4.3 package, I am able to populate the dropdown with items when clicking on the search bar , but when i start typing it isn't returning any items.
I am fetching the initial data from Firebase cloud storage.
Please have a look at my code below.
#override
Widget _buildBody() {
return OurContainer(
child: Column(
children: <Widget>[
DropdownSearch(
showSearchBox: true,
isFilteredOnline: true,
onFind: (String filter) => getData(filter),
popupItemBuilder: _customPopupItemBuilderExample,
dropdownBuilder: _customDropDownExample,
),
],
),
);
}
Widget _customDropDownExample(
BuildContext context, MainCategoryModel item, String itemDesignation) {
return Container(
child: (item?.name == null)
? ListTile(
contentPadding: EdgeInsets.all(0),
title: Text("No item selected"),
)
: ListTile(
contentPadding: EdgeInsets.all(0),
title: Text(item.name),
),
);
}
Widget _customPopupItemBuilderExample(
BuildContext context, MainCategoryModel item, bool isSelected) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 8),
decoration: !isSelected
? null
: BoxDecoration(
border: Border.all(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.circular(5),
color: Colors.white,
),
child: ListTile(
selected: isSelected,
title: Text(item.name),
subtitle: Text(item.name.toString()),
),
);
}
Future<List<MainCategoryModel>> getData(filter) async {
final tempList = <MainCategoryModel>[];
if (filter.length > 0) {
tempList.addAll(_mainCategoryList
.where((element) => element.name.toLowerCase().contains(filter)));
_mainCategoryList.addAll(tempList);
} else {
_mainCategoryList.clear();
_mainCategoryList.addAll(await OurDatabase().getMainCategory());
}
return _mainCategoryList;
}
Your getData method looks incorrect. Assuming your _mainCategoryList is the list from Firebase, the following synchronous method should work:
List<MainCategoryModel> getData(filter) {
if (filter.length < 1) {
return _mainCategoryList;
}
return _mainCategoryList.where((element) => element.name.toLowerCase().contains(filter)).toList();
}
This method will not mutate your _mainCategoryList, instead leaving it as a cache so the search will be faster and reduce your read count from Firebase. If this does not do what you need, let me know in the comments below this question and I would be happy to help.
I am passing data from my alertdialog to item of listview.
Where I can find information about it?
I tried use TextEditingController, with Navigation Routes. And I used materials from this Tutorial
This is my code:
class MeasurementsScreen extends StatefulWidget {
#override
_MeasurementsScreenState createState() => _MeasurementsScreenState();
}
class _MeasurementsScreenState extends State<MeasurementsScreen> {
List<_ListItem> listItems;
String lastSelectedValue;
var name = ["Рост", "Вес"];
var indication = ["Введите ваш рост", "Введите ваш вес"];
TextEditingController customcintroller;
void navigationPageProgrammTrainingHandler() {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ProgrammTrainingHandler()),
);
}
#override
void initState() {
super.initState();
initListItems();
}
Future<String> createAlertDialog(BuildContext context, int indexAl){
customcintroller = TextEditingController();
if(indexAl < 2){
return showDialog(context: context, builder: (context){
return AlertDialog(
title: Text(name[indexAl]),
content: TextField(
textDirection: TextDirection.ltr,
controller: customcintroller,
style: TextStyle(
color: Colors.lightGreen[400],
fontSize: 18.5),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(bottom: 4.0),
labelText: indication[indexAl],
alignLabelWithHint: false,
),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done,
),
actions: <Widget>[
FlatButton(
child: const Text('ОТМЕНА'),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: const Text('ОК'),
onPressed: () {
Navigator.of(context).pop(customcintroller.text.toString());
},
),
],
);
});
} else if (indexAl > 1){
navigationPageProgrammTrainingHandler();
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Color(0xff2b2b2b),
appBar: AppBar(
title: Text(
'Замеры',
style: new TextStyle(
color: Colors.white,
),),
leading: IconButton(
icon:Icon(Icons.arrow_back),
color: Colors.white ,
onPressed:() => Navigator.of(context).pop(),
),
),
body: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: listItems.length,
itemBuilder: (BuildContext ctxt, int index){
return GestureDetector(
child: listItems[index],
onTap: () {
createAlertDialog(context, index).then((onValue){
});
}
);
},
),
);
}
void initListItems() {
listItems = [
new _ListItem(
bgName: 'assets/images/soso_growth.jpg',
name: customcintroller.text.toString().isEmpty == false ? customcintroller.text.toString() : "Рост",
detail: "Нажми, чтобы добавить свой рост"),
new _ListItem(
bgName: 'assets/images/soso_weight.jpg',
name: customcintroller.text.toString().isEmpty == false ? customcintroller.text.toString() : "Вес",
detail: "Нажми, чтобы добавить свой вес"),
new _ListItem(
bgName: 'assets/images/soso_chest.jpg',
name: "Грудь",
detail: "PRO-версия"),
new _ListItem(
bgName: 'assets/images/soso_shoulder.jpg',
name: "Плечи",
detail: "PRO-версия"),
new _ListItem(
bgName: 'assets/images/soso_biceps.jpg',
name: "Бицепс",
detail: "PRO-версия")
];
}
}
class _ListItem extends StatelessWidget {
_ListItem({this.bgName, this.name, this.detail});
// final int index;
final String bgName;
final String name;
final String detail;
#override
Widget build(BuildContext context) {
return Container(
height: 180.0,
margin: const EdgeInsets.symmetric(
vertical: 1.0,
),
child: new Stack(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage(bgName),
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.45), BlendMode.darken),
fit: BoxFit.cover,
alignment: Alignment.center),
),
child: new SizedBox.expand(
child: Container(
alignment: Alignment.center,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text(
name,
style: new TextStyle(fontSize: 29.0, color: Colors.white),
),
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: new Text(
detail,
style:
new TextStyle(fontSize: 16.0, color: Colors.white),
),
)
],
),
),
),
),
],
),
);
}
}
I expect to get text from alertdialog.
Do you want to update data in your current screen or in another Scaffold/screen? Solution for the first option would be to set your desired data in a setState or pass it to a Model (with ScopedModel) and update the view. E.g. :
FlatButton(
child: const Text('ОТМЕНА'),
onPressed: () {
setState(() {
myData = data;
Navigator.of(context).pop();
}); // not sure if this will work, could you try?
}
),
If it is in a new/other screen, you can pass it in the Navigator like for example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewScreen(data: myData),
),
);
},
Does this solve your problem? Otherwise, please elaborate on what it is you need.
I tried to use Navigator.of(context).pop("OK"); in the AlertDialog but it doesn't work.
use ValueChanged as a param on the showDialog method and it works well.
class Dialogs{
static showAlertDialog(BuildContext context, String title, String message,
{List<String> actions, ValueChanged onChanged}) async {
if (actions == null) {
actions = ["OK"];//default OK button.
}
await showDialog(
context: context,
child: AlertDialog(
title: Text(title ?? 'Message'),
content: Text(message),
actions: actions
.map((e) => new FlatButton(
child: Text(e.trim()),
onPressed: () {
Navigator.of(context).pop(e.trim());
if (onChanged != null) {
onChanged(e.trim());
}
}))
.toList(),
),
);
}
}
the caller looks like
_onProcess(){
String result = "";
await Dialogs.showAlertDialog(context, "Warning", "Warning message", actions: ["OK", "Cancel"],
onChanged: (value) {
result = value;
});
if (result != "OK") {
Dialogs.showSnackBarMessage(context, "Cancel This PTN");
return;
}
....process with the OK logic.
}
If you want to change an item of the list you can do that with setState() before calling Navigator.pop(). It is not possible to pass data through Navigator.pop() though, as you may be able to do with Navigator.push(... MyPage(data)).
You could achieve what you want through State management. You can check for state management tutorials in general. But the two practices that are used most in my opinion are Scoped Model and BLoC pattern. These practices help you pass data through the widget tree back and forward. I would recommend BLoC pattern there are many tutorials about it. Also there is a package which can be very helpful with BLoC pattern: flutter_bloc.
Currently, I have an AlertDialog with an IconButton. The user can click on the IconButton, I have two colors for each click. The problem is that I need to close the AlertDialog and reopen to see the state change of the color icon. I want to change the IconButton color immediately when the user clicks it.
Here is the code:
bool pressphone = false;
//....
new IconButton(
icon: new Icon(Icons.phone),
color: pressphone ? Colors.grey : Colors.green,
onPressed: () => setState(() => pressphone = !pressphone),
),
Use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
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.
The sample code:
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);
},
);
}),
);
},
),
);
},
);
And as I mentioned, this is what is said on the showDialog docs:
[...] The widget returned by the builder does not share a context with the location
that showDialog is originally called from. Use a StatefulBuilder or a
custom StatefulWidget if the dialog needs to update dynamically.
This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.
Update:
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return MyDialog();
});
},
)));
}
}
class MyDialog extends StatefulWidget {
#override
_MyDialogState createState() => new _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
Color _c = Colors.redAccent;
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
color: _c,
height: 20.0,
width: 20.0,
),
actions: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () => setState(() {
_c == Colors.redAccent
? _c = Colors.blueAccent
: _c = Colors.redAccent;
}))
],
);
}
}
First you need to use StatefulBuilder. Then i am setting _setState variable, which even could be used outside StatefulBuilder, to set new state.
StateSetter _setState;
String _demoText = "test";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
_setState is used same way as setState method. For example like this:
_setState(() {
_demoText = "new test text";
});
If you're separating your data from the UI via View Models and using the Provider package with ChangeNotifier, you'll need to include your current model like so within the widget calling the dialog:
showDialog(context: context, builder: (dialog) {
return ChangeNotifierProvider.value(
value: context.read<ViewModel>(),
child: CustomStatefulDialogWidget(),
);
},
Note that there may be a cleaner way to do this but this worked for me.
Additional info regarding Provider: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Not sure if this is best practice, but I solved the issue of updating both the dialog state and the content state by wrapping the setState functions, after using the top answer to add state to the dialog:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, newSetState) { // Create a "new" state variable
return AlertDialog(
content: DropdownButton(
value: listItem.type,
items: allItems
onChanged: (value) {
newSetState(() {
setState(() {
// HERE SET THE STATE TWICE
// Once with the "new" state, once with the "old"
});
});
})
),
);
}
);
}
),
In fact, you can use StatefullBuilder but the problem is that when you use this widget you cant change the state of the base screen! Prefer to navigate to a new screen in order to use setState.
I was stuck with this issue.You have to Change the name of setState to any Other name and pass this set state to all sub functions.
This will update your Dialog ui on time.
return StatefulBuilder(
builder: (context, setStateSB) {
return AlertDialog(
title: Text("Select Circle To Sync Data!" ,style: TextStyle(color: Colors.white),),
content: Column(
children: [
Text("Select Division!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DivisionName_firstValue,
items: _DivisionName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black)),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DivisionName_firstValue = newValue!;
if(sync_DivisionName_firstValue !="Select Division Name"){
print("sync_DivisionName_firstValue$sync_DivisionName_firstValue");
_getDistrictName(sync_DivisionName_firstValue,setStateSB);
}else{
refreashDivisionName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select District!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DistrictName_firstValue,
items: _DistrictName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DistrictName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name"){
print("sync_DistrictName_firstValue$sync_DistrictName_firstValue");
_getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,setStateSB);
}else{
refreashDistrictName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Tehsil!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_TehsilName_firstValue,
items: _TehsilName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_TehsilName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name"){
print("sync_TehsilName_firstValue$sync_TehsilName_firstValue");
_getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,setStateSB);
}else{
refreashTehsilName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Rating Area Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_RatingAreaName_firstValue,
items: _RatingAreaName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_RatingAreaName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name" && sync_RatingAreaName_firstValue != "Select Rating Area Name"){
print("sync_RatingAreaName_firstValue$sync_RatingAreaName_firstValue");
_getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue,setStateSB);
}else{
refreashWardCircleName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Ward Circle Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_circle_name_firstValue,
items: _circle_name_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_circle_name_firstValue = newValue!;
print("sync_circle_name_firstValue$sync_circle_name_firstValue");
// if(sync_circle_name_firstValue != "Select Ward Circle Name"){
//
// _getWardCircleName(sync_RatingAreaName_firstValue);
// }else{
//
// }
});
},
)),
),
],
),
),
]),
backgroundColor:Color(0xFFEC9F46),
actions: [
okButton,SyncButton
],
);
},
);
One of the Inner Funciton is like this.
Future<void> refreashDivisionName( StateSetter setInnerState) async {
final List<String> _division_name = await getDivisionNameList();
final List<String> _district_name_list = await getDistrictName(sync_DivisionName_firstValue);
final List<String> _tehsil_name_list = await getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue);
final List<String> _rating_area_name_list = await getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue);
final List<String> _ward_circle_name_list = await getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue);
setInnerState(() {
_division_name.insert(0, "Select Division Name");
_DivisionName_list = _division_name;
sync_DivisionName_firstValue = _DivisionName_list[0];
_district_name_list.insert(0, "Select District Name");
_DistrictName_list = _district_name_list;
sync_DistrictName_firstValue = _DistrictName_list[0];
_tehsil_name_list.insert(0, "Select Tehsil Name");
_TehsilName_list = _tehsil_name_list;
sync_TehsilName_firstValue = _TehsilName_list[0];
_rating_area_name_list.insert(0, "Select Rating Area Name");
_RatingAreaName_list = _rating_area_name_list;
sync_RatingAreaName_firstValue = _RatingAreaName_list[0];
_ward_circle_name_list.insert(0, "Select Ward Circle Name");
_circle_name_list = _ward_circle_name_list;
sync_circle_name_firstValue = _circle_name_list[0];
});
}
I hope you under Stand.
base on Andris's answer.
when dialog share the same state with parent widget, you can override parent widget's method setState to invoke StatefulBuilder's setState, so you don't need to call setState twice.
StateSetter? _setState;
Dialog dialog = showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
// set the function to null when dialo is dismiss.
dialogFuture.whenComplete(() => {_stateSetter = null});
#override
void setState(VoidCallback fn) {
// invoke dialog setState to refresh dialog content when need
_stateSetter?.call(fn);
super.setState(fn);
}
Currently to retrieve the value of Dialog I use
showDialog().then((val){
setState (() {});
print (val);
});
Example
1st screen
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AddDespesa();
}).then((val) {
setState(() {});
print(val);
}
);
}
2nd screen
AlertDialog(
title: Text("Sucesso!"),
content: Text("Gasto resgristrado com sucesso"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
Will be printed true,