How do I change it such that the change is instant? - flutter

import 'package:flutter/material.dart';
class RandomPage extends StatefulWidget {
#override
_RandomScreenState createState() => _RandomScreenState();
}
class _RandomScreenState extends State {
String _selectedValue = 'Select';
bool appear = false;
Widget FirstDropDownButton() {
return Container(
child: DropdownButton <String> (
value: _selectedValue,
icon: Icon(Icons.arrow_drop_down),
underline: Container(
height: 1.5,
color: Colors.blueGrey,
),
onChanged: (String newValue) {
setState(() {
_selectedValue = newValue;
});},
items: <String> ['Select','One'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);}).toList(),
)
);
}
Widget FirstFlatButton() {
return FlatButton(
child: Text("Next"),
onPressed: () {
if (_selectedValue == 'Select') {
print("Cannot be NULL");
appear = false;
}
else if (_selectedValue == 'One')
appear = true;
});
}
Widget getWidget() {
if (appear == true) {
return Column(
children: <Widget>[
Text("Hello")
],
);
}
else
return Column(
children: <Widget>[
Text("Bye")
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget> [
FirstDropDownButton(),
FirstFlatButton(),
getWidget(),
],
)
);
}
}
Hi this is a simpler version of the code of mine.
If I choose 'one' from the dropdownbutton the hello text should appear but it doesnt, only when I go ahead and change the value of the dropdownbutton again then will it reflect the change.
How do I make it such that when I select 'one' such that text hello appears immediately and when i select 'select' the text bye appears and hello disappears at the click of the flatbutton without toggling the options again for the change to be reflected?

i have taken the liberty to modify your code a bit, and made it more general.
For live demo check: codepen
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: RandomPage(),
),
);
}
class RandomPage extends StatefulWidget {
#override
_RandomScreenState createState() => _RandomScreenState();
}
class _RandomScreenState extends State {
int _selectedValue = 0;
bool appear = false;
List<String> values = ['Select', 'One', 'Two', 'Three'];
Widget FirstDropDownButton() {
return Container(
child: DropdownButton<String>(
value: values[_selectedValue],
icon: Icon(Icons.arrow_drop_down),
underline: Container(
height: 1.5,
color: Colors.blueGrey,
),
onChanged: (String newValue) {
setState(() {
_selectedValue = values.indexWhere((element) => element == newValue);
});
changeFlag();
},
items: values.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
));
}
void changeFlag() {
if (values[_selectedValue] == 'One') {
setState(() {
appear = true;
});
} else {
setState(() {
appear = false;
});
}
}
Widget FirstFlatButton() {
return FlatButton(
child: Text("Next"),
onPressed: () {
setState(() {
_selectedValue = (_selectedValue + 1) % (values.length);
});
changeFlag();
});
}
Widget getWidget() {
if (appear == true) {
return Column(
children: <Widget>[Text("Hello")],
);
} else
return Column(
children: <Widget>[Text("Bye")],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("StackOverflow")),
body: Column(
children: <Widget>[
FirstDropDownButton(),
FirstFlatButton(),
getWidget(),
],
));
}
}
Update: Now that I re-read the question,
I figured that you wanted to show either the value Hello or Bye depending upon the value on dropdown, when the user clicked Next button,
but i am sure you got the idea whatsoever,
just remove the setState on the flatbutton and changeFlag in the dropdown and you'll be fine.

Related

How do implement a DropdownButton using Getx in Flutter

I am trying to implement a DropdownButton with two dropdownmenuitems, spanish and english.
I want a user to select a language and will send this back to getx translations in order to change the app's language.
Here is my controller:
import 'package:get/get.dart';
class DropdownController extends GetxController {
String selectedValue;
var language = <String>['English', 'Espanol'];
void onSelected(String value) {
selectedValue = value;
}
}
Here is my settings page
class SettingsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<DropdownController>(builder: (controller) {
return Scaffold(
body: Column(children: [
Text('Select Language: '),
SizedBox(height: 10.0),
DropdownButton<String>(
hint: Text('Language'),
value: controller.selectedValue,
onChanged: (newValue) {
controller.onSelected(newValue);
},
elevation: 10,
items: [
DropdownMenuItem(
child: Text("English"),
value: controller.selectedValue,
),
DropdownMenuItem(
child: Text("ESpanol"),
value: controller.selectedValue,
),
]),
]),
);
});
}
}
Controller:
class DropdownController extends GetxController {
String? selectedValue;
var language = <String>['English', 'Espanol'];
void onSelected(String value) {
selectedValue = value;
update();
print(selectedValue);
changeLanguage(selectedValue);
}
changeLanguage(String? selectedLanguage) {
switch (selectedLanguage) {
case 'English':
Get.updateLocale(Locale('en_US'));
print('Changed to En');
break;
case 'Espanol':
Get.updateLocale(Locale('en_US'));
print('Changed to Es');
break;
default:
Get.updateLocale(Locale('en_US'));
print('Fallback to En');
break;
}
}
}
Settings page:
class SettingsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<DropdownController>(builder: (controller) {
return Scaffold(
body: Column(children: [
Text('Select Language: '),
SizedBox(height: 10.0),
DropdownButton<String>(
hint: Text('Language'),
value: controller.selectedValue,
onChanged: (newValue) {
controller.onSelected(newValue!);
},
elevation: 10,
items: [
DropdownMenuItem(
child: Text("English"),
value: 'English',
),
DropdownMenuItem(
child: Text("ESpanol"),
value: 'Espanol',
),
]),
]),
);
});
}
}

RadioListTile is not working with setstate

What is working when i click on radio button it is not changing
class OnBoardingCheckBoxWithActionWidget extends StatefulWidget {
String label;
Function onClick;
OnBoardingCheckBoxWithActionWidget(this.label, {this.onClick});
#override
_OnBoardingCheckBoxWithActionWidgetState createState() =>
_OnBoardingCheckBoxWithActionWidgetState();
}
class _OnBoardingCheckBoxWithActionWidgetState
extends State<OnBoardingCheckBoxWithActionWidget> {
bool checked = true;
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RadioListTile(
value: checked,
groupValue: checked,
toggleable: true,
title: Text("${widget.label}"),
onChanged: (val) {
if (widget.onClick != null) {
widget.onClick(val);
}
setState(() {
print("$checked");
checked = !checked;
print("$checked");
});
},
),
),
); // Card(child: Row(children: [Expanded(child: RadioListTile())],),);
}
}
You're trying to change some value from your Parent Stateful widget.
Try changing your current widget like
class OnBoardingCheckBoxWithActionWidget
extends StatelessWidget {
String label;
Function onClick;
var groupValue;
var buttonValue;
OnBoardingCheckBoxWithActionWidget(this.label, {this.onClick, this.groupValue,, this.buttonValue});
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RadioListTile(
value: buttonValue,
groupValue: groupValue,
toggleable: true,
title: Text("${this.label}"),
onChanged: (val) {
if (this.onClick != null) {
this.onClick(val);
}
},
),
),
); // Card(child: Row(children: [Expanded(child: RadioListTile())],),);
}
}
And then call it like
OnBoardingCheckBoxWithActionWidget("Button1", onClick: (value){
setState((){
///Your Other Code
if(value){
groupValue = 'button1';
}
});
},
groupValue: someGroupValueVariableInState, buttonValue: 'button1')
Note that, the groupValue and buttonValue variables will always be some other variables other than booleans. When groupValue == buttonValue, the corresponding button will automatically marked as checked.

Related DropdownButtons and raising onChange event

I have two DropdownButton widget. Content of second one depends on first one selection. Second DropdownButton will initiate refresh of third widget. How can I initiate refresh of second DropdownButton when first one is populated? And then how can I refresh third widget when second DropdownButton populated also?
class ParentBloc {
Stream<List<Parent>> get items => _controller.asyncMap(...);
Future<List<Parent>> _callApi() {
// call endpoint /parents
}
}
class ChildBloc {
ChildBloc(this.parentId);
int parentId;
Stream<List<Child>> get items => _controller.asyncMap(...);
Future<List<Child>> _callApi() {
// call endpoint /parents/$parentId/children
}
}
// This bloc created at init state
ParentBloc parentBloc;
// This bloc will be created only after value will
// be selected in the Parent dropdownbutton because
// I need to know `parentId`.
ChildBloc childBloc;
#override
void initState() {
super.initState();
parentBloc = ParentBloc();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
StreamBuilder<List<Parent>>(
stream: parentBloc.items,
builder: (context,snapshot) {
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownButtonItem();
}),
);
}
),
// Content of this widget depends on above one
StreamBuilder<List<Child>>(
stream: childBloc.items,
builder: (context,snapshot) {
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownButtonItem();
}),
);
}
),
// Content of this widget depends on above one
StreamBuilder<List<Grandchild>>(
stream: grandchildBloc.items,
builder: (context,snapshot) {
return ListView(),
);
}
),
]
);
}
Provided you're doing this inside a StatefulWidget, you can use setState inside one of your functions where you update the variables that in turn have to be used to control what is currently displayed in each of your widgets.
It should look something like this (inside your Dropdown):
onChanged: (newValue) {
setState(() {
_currentSelection = newValue;
});
},
Update: after discussion in the comments, here's a working example that I made of how something can be updated based on a value selected inside a dropbox, hope it helps:
import 'package:flutter/material.dart';
class ExampleWidget extends StatefulWidget {
#override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
List<String> someStringsToSelectFrom = [
'value1',
'value2',
'value3',
];
String selectedValue;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Select something:'),
DropdownButton<String>(
value: selectedValue,
icon: Icon(
Icons.arrow_downward,
color: Colors.deepPurpleAccent,
),
iconSize: 24,
elevation: 16,
style: TextStyle(
fontSize: 30.0,
color: Colors.green,
),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String newValue) {
setState(() {
selectedValue = newValue;
});
},
items: someStringsToSelectFrom.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Text('This will update after you selected a value:'),
someStringsToSelectFrom.contains(selectedValue) ? Text(selectedValue + ' was selected') :
Text('Still waiting for user action.'),
],
),
),
);
}
}

Is it possible in a dropdown button the displayed value to not be in the menu?

I tried to do it but I get an error. I was wondering if there is some kind of loophole.
Also, is there a way for the menu to open below the displayed value?
Here is the code:
import 'package:flutter/material.dart';
import './grile_view.dart';
class Grile extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return GrileState();
}
}
class GrileState extends State<Grile> {
var _bGrile = ['bgrila 1', 'bgrila 2'];
var _bio = 'bgrila 1';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
child: ListView(
children: <Widget>[
// DropdownButton
DropdownButton<String>(
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
// DropdownButton End
]
)
)
);
} // build
} // GrileState
I am new at programming so excuse anything dumb I wrote in the code.
The DropdownButton class contains a very handy hint property. According to the DropdownButton documentation:
If [value] is null and [hint] is non-null, the [hint] widget is
displayed as a placeholder for the dropdown button's value.
What does this mean for you?
All you have to do is add a hint widget to your DropdownButton and then remove the assignment from your dynamic value.
Here's a brief example of a DropdownButton with a hint property:
class DropDownPage extends StatefulWidget {
#override
_DropDownPageState createState() => _DropDownPageState();
}
class _DropDownPageState extends State<DropDownPage> {
int dropdownValue; // Notice how this isn't assigned a value.
#override
Widget build(BuildContext context) {
return Center(
child: DropdownButton(
// Here's the hint property. I used a Text widget,
// but you can use any widget you like
hint: Text("Pick a Widget"),
value: dropdownValue,
items: [
DropdownMenuItem(child: Text("FlatButton"), value: 0),
DropdownMenuItem(child: Text("Container"), value: 1),
DropdownMenuItem(child: Text("Scaffold"), value: 2),
DropdownMenuItem(child: Text("GestureDetector"), value: 3),
],
onChanged: (value) {
setState(() {
dropdownValue = value;
});
},
));
}
}
This is the result:
So in your code, here's what you have to do in your own code.
Edit this: var _bio = 'bgrila 1';
And change it to this: var _bio;
Then add a hint property to your DropdownButton:
DropdownButton<String>(
hint: Text("Pick a *****"), // Replace with your hint widget
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
So that your entire code looks like this:
class Grile extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return GrileState();
}
}
class GrileState extends State<Grile> {
var _bGrile = ['bgrila 1', 'bgrila 2'];
var _bio;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
child: ListView(
children: <Widget>[
// DropdownButton
DropdownButton<String>(
hint: Text("Pick a *******"),
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
// DropdownButton End
]
)
)
);
} // build
} // GrileState

How to access all of child's state from Parent Widget in flutter?

I have a parent widget called createRoutineScreen and it has 7 similar children widget called RoutineFormCard. RoutineFormCard is a form and which has a state _isPostSuccesful of boolean type to tell whether the form is saved to database or not. Now, I have to move to the other screen from createRoutine only when all of it's 7 children has _isPostSuccesful true. How can I access all of children's state from createRoutineScreen widget?
My Code is:
class CreateRoutineScreen extends StatefulWidget {
final String userID;
CreateRoutineScreen({this.userID});
//TITLE TEXT
final Text titleSection = Text(
'Create a Routine',
style: TextStyle(
color: Colors.white,
fontSize: 25,
)
);
final List<Map> weekDays = [
{"name":"Sunday", "value":1},
{"name":"Monday", "value":2},
{"name":"Tuesday", "value":3},
{"name":"Wednesday", "value":4},
{"name":"Thursday", "value":5},
{"name":"Friday", "value":6},
{"name":"Saturday", "value":7},
];
#override
_CreateRoutineScreenState createState() => _CreateRoutineScreenState();
}
class _CreateRoutineScreenState extends State<CreateRoutineScreen> {
Routine routine;
Future<List<dynamic>> _exercises;
dynamic selectedDay;
int _noOfRoutineSaved;
List _keys = [];
Future<List<dynamic>>_loadExercisesData()async{
String url = BASE_URL+ "exercises";
var res = await http.get(url);
var exercisesList = Exercises.listFromJSON(res.body);
//var value = await Future.delayed(Duration(seconds: 5));
return exercisesList;
}
#override
void initState(){
super.initState();
_exercises = _loadExercisesData();
_noOfRoutineSaved = 0;
for (int i = 0; i< 7; i++){
_keys.add(UniqueKey());
}
}
void _changeNoOfRoutineSaved(int a){
setState(() {
_noOfRoutineSaved= _noOfRoutineSaved + a;
});
}
#override
Widget build(BuildContext context) {
print(_noOfRoutineSaved);
return Scaffold(
appBar: AppBar(
title:Text("Create a Routine"),
centerTitle: true,
actions: <Widget>[
FlatButton(
child: Text("Done"),
onPressed: (){
},
),
],
),
body: Container(
color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top:5.0,left: 10,right: 10,bottom: 10),
child: FutureBuilder(
future: _exercises,
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: widget.weekDays.length,
itemBuilder: (context,index){
return RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key:_keys[index]
);
},
);
}
else if(snapshot.hasError){
return SnackBar(
content: Text(snapshot.error),
);
}
else{
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
)
);
}
},
)
),
);
}
}
And my child widget is:
class RoutineFormCard extends StatefulWidget {
final Function createRoutineState;
final String weekDay;
final List<dynamic> exerciseList;
final String userID;
final Function changeNoOfRoutineSaved;
RoutineFormCard({this.createRoutineState,
this.weekDay, this.exerciseList, this.changeNoOfRoutineSaved,
this.userID, Key key}):super(key:key);
#override
_RoutineFormCardState createState() => _RoutineFormCardState();
}
class _RoutineFormCardState extends State<RoutineFormCard> {
bool _checkBoxValue= false;
List<int> _selectedExercises;
bool _inAsyncCall;
bool _successfulPost;
#override
void initState(){
super.initState();
_selectedExercises = [];
_inAsyncCall = false;
_successfulPost= false;
}
void onSaveClick()async{
setState(() {
_inAsyncCall = true;
});
String url = BASE_URL + "users/routine";
List selectedExercises = _selectedExercises.map((item){
return widget.exerciseList[item].value;
}).toList();
String dataToSubmit = jsonEncode({
"weekDay":widget.weekDay,
"userID": widget.userID==null?"5e9eb190b355c742c887b88d":widget.userID,
"exercises": selectedExercises
});
try{
var res =await http.post(url, body: dataToSubmit,
headers: {"Content-Type":"application/json"});
if(res.statusCode==200){
print("Succesful ${res.body}");
widget.changeNoOfRoutineSaved(1);
setState(() {
_inAsyncCall = false;
_successfulPost = true;
});
}
else{
print("Not succesful ${res.body}");
setState(() {
_inAsyncCall = false;
});
}
}catch(err){
setState(() {
_inAsyncCall = false;
});
print(err);
}
}
Widget saveAndEditButton(){
if(_inAsyncCall){
return CircularProgressIndicator();
}
else if(_successfulPost)
{
return IconButton(
icon: Icon(Icons.edit, color: Colors.black,),
onPressed: (){
widget.changeNoOfRoutineSaved(-1);
setState(() {
_successfulPost = false;
});
},
);
}
else{
return FlatButton(child: Text("Save"),
onPressed: !_checkBoxValue&&_selectedExercises.length==0?null:onSaveClick,);
}
}
//Card Header
Widget cardHeader(){
return AppBar(
title: Text(widget.weekDay, style: TextStyle(
fontFamily: "Raleway",
fontSize: 20,
color: Colors.black,),
),
actions: <Widget>[
saveAndEditButton()
],
backgroundColor: Colors.lime[400],
);
}
Widget cardBody(){
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Rest Day"),
Checkbox(
value: _checkBoxValue,
onChanged: (value){
setState(() {
_checkBoxValue = value;
});
},
)
],
),
),
_checkBoxValue?Container():
SearchableDropdown.multiple(
hint: "Select Exercise",
style: TextStyle(color: Colors.black),
items: widget.exerciseList.map<DropdownMenuItem>((item){
return DropdownMenuItem(
child: Text(item.name), value: item
);
}).toList(),
selectedItems: _selectedExercises,
onChanged: (values){
setState(() {
_selectedExercises = values;
});
},
isExpanded: true,
dialogBox: true,
),
],
);
}
#override
Widget build(BuildContext context) {
print("<><><><><><><><><><><>${widget.weekDay} called");
return Card(
elevation: 8.0,
child: Form(
key: GlobalKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
cardHeader(),
_successfulPost?Container():cardBody()
],
),
),
);
}
}
As you can see, I've tried callBack from parent widget which increases or decrease no of form saved from each of the child widget. It does the work but, when one form is saved, parent state is modified and all other children got rebuild which is unnecessary in my opionion. What's the best way to do it?
Try to use GlobalKey instead of UniqueKey for each RoutineFormCard. It will help you to access the state of each RoutineFormCard. You can do it like this :
// 1. In the top of your CreateRoutineScreen file, add this line (make your RoutineFormCardState class public before)
final List<GlobalKey<RoutineFormCardState>> routineFormCardKeys = <GlobalKey<RoutineFormCardState>>[
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
];
// 2. Then construct your RoutineFormCard using the right key
RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key: routineFormCardKeys[index]
);
// 3. Now you can create a method in CreateRoutineScreen which will check the state of all RoutineFormCard
bool _allRoutineFormCardsCompleted() {
bool result = true;
for (int i = 0; i < 7; i++)
result = result && routineFormCardKeys[i].currentState.isPostSuccessful;
return result;
}
// 4. Finally use the result of the previous method where you want to move on another page
I'm sharing a quick idea to solve your problem, I've not tested it, but I'm ready to improve the answer if needed
Hope this will help!