I have this layout :
I want when click on Select Time to change this blue container to another container
this is the code of radio buttons :
class TimeRadioButtonsClass extends StatefulWidget {
#override
_TimeRadioButtonsClassState createState() => _TimeRadioButtonsClassState();
List<String> labels;
String picked;
Function function;
TimeRadioButtonsClass({this.picked , this.labels , this.function});
}
class _TimeRadioButtonsClassState extends State<TimeRadioButtonsClass> {
#override
Widget build(BuildContext context) {
return RadioButtonGroup(
orientation: GroupedButtonsOrientation.VERTICAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (String selected) => setState((){
widget.picked = selected;
}),
labels: widget.labels,
picked: widget.picked,
activeColor: Color(0xffFFD243),
onChange: (String label, int index) {
print("label: $label index: $index");
widget.function(label,index);
},
);
}
}
and here I call this class in CheckoutClass :
TimeRadioButtonsClass(picked: picked , labels: when),
NOTE : I used setState in CheckoutClass by access it as parameter in TimeRadioButtonsClass it changes container for just one time.
I don't know the reason why setState doesn't work !
Edit : I pass function as parameter to TimeRadioButtonsClass (I edit TimeRadioButtonsClass code above) and this is the code for calling:
TimeRadioButtonsClass(picked: picked , labels: when , function: (String label , int index){
setState(() {
if(index == 1){
indexRadio = 1;
print("indexRadio 1 : "+indexRadio.toString());
}
else{
indexRadio = 0;
print("indexRadio 2 : "+indexRadio.toString());
}
});
},),
indexRadio == 1 ? Container(
height: 50.0,
color: Colors.blue,
):
Container(
height: 50.0,
color: Colors.red,
),
If your container is outside the class, then you can make a callback on the selected button of the radiobutton.
widget.onTap(selected);
Add this line of code for the onTap method, to make a callback to the class from where it is called.
class TimeRadioButtonsClass extends StatefulWidget {
#override
_TimeRadioButtonsClassState createState() => _TimeRadioButtonsClassState();
List<String> labels;
String picked;
Function onTap;
TimeRadioButtonsClass({this.picked, this.labels, this.onTap});
}
class _TimeRadioButtonsClassState extends State<TimeRadioButtonsClass> {
#override
Widget build(BuildContext context) {
return RadioButtonGroup(
// orientation: GroupedButtonsOrientation.VERTICAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (String selected) {
setState(() {
widget.picked = selected;
});
widget.onTap(selected); //This will make a callback to your class and send the selected value in the onTap method of that class.
},
labels: widget.labels,
picked: widget.picked,
activeColor: Color(0xffFFD243),
onChange: (String label, int index) =>
print("label: $label index: $index"),
);
}
}
Now to get the onTap value from the class, Call your widget as below
NOTE: Here value of 1 and 2 is your radio button picked string 1 is the ASAP and 2 is the Select timer
TimeRadioButtonsClass(
picked: "picked",
onTap: (value) {
if (value == 1) {
//do change for one
containerColor = Colors.blue;
setState(() {
});
} else if (value == 2) {
//do change for two
containerColor = Colors.red;
setState(() {
});
}
},
)
Define a Color for container as Default so to change the color on the onTap method of the class
Color containerColor = Colors.red;
Now Show The container As Follows
Container(
height: 100,
width: 100,
color: containerColor,
);
EDITED 2:- (This code changes the color for container and Radio button also changes now everytime)
String picked = "ASAP"; // Add this line at the top
Replace the Code of Calling TimeRadioButtonsClass Class with this
TimeRadioButtonsClass(
picked: picked,
labels: [
"ASAP",
"Select Timer",
],
onTap: (
int index,
) {
indexRadio = index;
if (index == 0) {
picked = "ASAP";
} else {
picked = "Select Timer";
}
setState(() {});
},
),
indexRadio == 0
? Container(
height: 50.0,
color: Colors.blue,
)
: Container(
height: 50.0,
color: Colors.red,
),
And Also Replace the TimeRadioButtonClass Itself with this code
import 'package:flutter/material.dart';
import 'package:grouped_buttons/grouped_buttons.dart';
class TimeRadioButtonsClass extends StatefulWidget {
#override
_TimeRadioButtonsClassState createState() => _TimeRadioButtonsClassState();
List<String> labels;
String picked;
Function(int) onTap;
TimeRadioButtonsClass({this.picked, this.labels, this.onTap});
}
class _TimeRadioButtonsClassState extends State<TimeRadioButtonsClass> {
#override
Widget build(BuildContext context) {
return RadioButtonGroup(
orientation: GroupedButtonsOrientation.VERTICAL,
margin: const EdgeInsets.only(left: 12.0),
labels: widget.labels,
picked: widget.picked,
activeColor: Colors.red,
onChange: (String label, int index) {
print(index);
widget.onTap(index);
});
}
}
What I have done is that Removed the onSelected (maybe it may come use later), and Made the onTap function to pass the int value
setState() only works within a particular State class.
Whenever you change the internal state of a State object, make the change in a function that you pass to setState
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
setState() docs
Here, setState() is working, but your container isn't in its scope.
When the date is selected, _TimeRadioButtonsClassState.build will be called which doesn't affect your container since your container isn't in that widget (or class, if you prefer)
If possible, try shifting the container inside the widget where you'll be calling setState or try using a different state management approach
A more complex approach would be using GlobalKey and passing it as a parameter to your StatefulWidget and calling setState or using context.findAncestorStateOfType
Try this :
class _TimeRadioButtonsClassState extends State<TimeRadioButtonsClass> {
String _picked;
initState() {
_picked = widget.picked;
super.initState();
}
#override
Widget build(BuildContext context) {
return RadioButtonGroup(
orientation: GroupedButtonsOrientation.VERTICAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (String selected) => setState((){
_picked = selected;
}),
labels: widget.labels,
picked: _picked,
activeColor: Color(0xffFFD243),
onChange: (String label, int index) => print("label: $label index: $index"),
);
}
}
Related
I am using provider for state management, and I've given onTap a value in a function in the ChangeNotifier child class but my app is unresponsive, I mean, when i tap on the widget, it doesn't update state, however, it does change the values i need it to change tho, i know this coz I am debugPrinting in the onTap function and when i tap, it actually prints that the button got tapped, but state doesn't update, widget remains the same until i hot restart, then it updates everything, even hot reload doesn't update it, here's the function
class Storage extends ChangeNotifier{
static const _storage = FlutterSecureStorage();
static const _listKey = 'progress';
List _dataMaps = [];
List<DayTile> dayTileMain = [];
void createDataMap() {
for (int i = 1; i < 101; i++) {
final data = Data(number: i).toJson();
_dataMaps.add(data);
}
}
void createDayTiles() {
for(Map<String, dynamic> data in _dataMaps) {
bool isDone = data['i'];
final dayTile = DayTile(
number: data['n'],
isDone: isDone,
// This is where i need to rebuild the tree
onTap: () async {
data['i'] = true;
notifyListeners();
print(data['i']);
print(isDone);
await writeToStorage();
},
);
dayTileMain.add(dayTile);
}
print('data tiles created');
}
}
and here is the DayTile class
class DayTile extends StatelessWidget {
const DayTile({
Key? key,
required this.number,
required this.isDone,
required this.onTap,
}) : super(key: key);
final int number;
final VoidCallback onTap;
final bool isDone;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 50,
width: MediaQuery.of(context).size.width * .15,
decoration: BoxDecoration(
color: !isDone
? const Color(0xffedecea)
: const Color(0xffedecea).withOpacity(0.1),
borderRadius: BorderRadius.circular(5),
),
child: Center(
child: Stack(
alignment: Alignment.center,
children: [
Center(
child: Text(
number.toString(),
style: const TextStyle(
color: Color(0xff576aa4),
),
),
),
Visibility(
visible: isDone,
child: const Divider(
color: Colors.black,
),
),
],
),
),
),
);
}
}
here is where I listen for the change
Wrap(
spacing: 13,
runSpacing: 13,
children: Provider.of<Storage>(context).dayTileMain,
),
when data['i'] is true, it should update the current instance of DayTile() that it's on in the loop, and in DayTile() I use the value of data['i'] to set the value of bool isDone and depending on whether isDone is true or false, the color of the widget changes and some other things, BUT, they don't change onTap, but they change after I hot restart, when it's read the storage and restored the saved data, could the secureStorage writing to storage at the same time be affecting it?
I solved it, turns out it's not a good idea to listen for events in the model class, it won't listen, so instead of generating the list of widgets in the model class, I moved it outta there, and instead generated it inside the wrap widget, and instead of listening for a list in the model class, i just had the list there in my wrap, if it was a listview i was tryna generated, i would've done this initially with a ListView.builder() i didn't know you could generate a list inside the children of the wrap widget, so i just stuck to defining it in the model, I came across this stack question Flutter: How to use Wrap instead of ListView.builder?
and that's how i knew how to build widgets inside a children property, i was actually just looking for a ListView.builder() version for the Wrap widget, all said, this is what my stuff is looking like
Model
class Storage extends ChangeNotifier {
static const _storage = FlutterSecureStorage();
static const _listKey = 'progress';
List _dataMaps = [];
List<DayTile> dayTileMain = [];
void createDataMap() {
for (int i = 1; i < 101; i++) {
final data = Data(number: i).toJson();
_dataMaps.add(data);
}
}
int get listLength {
return _dataMaps.length;
}
UnmodifiableListView get dataMaps {
return UnmodifiableListView(_dataMaps);
}
void pressed(Map<String, dynamic> map) async {
map['i'] = true;
await writeToStorage();
notifyListeners();
}
Future writeToStorage() async {
final value = json.encode(_dataMaps);
await _storage.write(key: _listKey, value: value);
}
Future<void> getTasks() async {
print('getTasks called');
final value = await _storage.read(key: _listKey);
final taskList = value == null ? null : List.from(jsonDecode(value));
if (taskList == null) {
print('getTasks is null');
createDataMap();
// createDayTiles();
} else {
print('getTasks is not null');
print(taskList);
_dataMaps = taskList;
// createDayTiles();
}
}
Future readFromStorage() async {
await getTasks();
notifyListeners();
}
}
Wrap Builder
class DayTiles extends StatelessWidget {
const DayTiles({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<Storage>(
builder: (_, storageData, __) => Wrap(
spacing: 13,
runSpacing: 13,
children: [
for(Map<String, dynamic> data in storageData.dataMaps)
DayTile(
number: data['n'],
onTap: () {
storageData.pressed(data);
},
isDone: data['i'],
),
],
),
);
}
}
and instead of using a wrap and listening for changes to it's children in the screen class, i just directly use the DayTiles() custom widget i created
I created my own TextField widget and i want to use onChange() function of textField, so i created a callbackFunction as you can see below, im trying to print given param of function but receiving null, how can i handle these callbacks;
CallBack Function
void childCallBack(dynamic value, dynamic property) {
setState(() {
property = value;
});
}
How i call my TextField in main state
TextFieldForProduct(
property: widget.product.name,
func: childCallBack,
)
And my CustomTextFieldWidget
class TextFieldForProduct extends StatelessWidget {
TextFieldForProduct({#required this.property, #required this.func});
var property;
Function func;
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (value) {
func(value, property);
},
),
);
}
}
What is the point of passing widget.product.name to TextFieldForProduct, you can directly set the widget.product.name = value; in the childCallBack since both are available in the same class. Please see the code below:
void childCallBack(String value) {
setState(() {
widget.product.name = value;
});
}
How to call TextField in main state :
TextFieldForProduct(
func: childCallBack,
),
CustomTextFieldWidget
class TextFieldForProduct extends StatelessWidget {
const TextFieldForProduct({#required this.func});
final Function func;
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (String value) => func(value),
),
),
);
}
}
In my app I am generating a ListView and items can be highlighted by tapping on them. That works fine and I also have a callback function that gives me the key for the just selected item. I can currently manually deselect the item by tapping on it again, but will ultimately take that functionality out.
My problem is that I want one and only one item to be selected at a time. In order to create the list I currently take some initial content in the form of a list, generate the tiles and add them to another list. I then use that list to create the ListView. My plan was on the callback from a new selection, run through the list of tiles and deselect them before highlighting the new chosen tile and carrying out the other functions. I have tried various methods to tell each tile to deselect itself but have not found any way to address each of the tiles. Currently I get the error:
Class 'OutlineTile' has no instance method 'deselect'.
Receiver: Instance of 'OutlineTile'
Tried calling: deselect()
I have tried to access a method within the tile class and to use a setter but neither worked so far. I am quite new to flutter so it could be something simple I am missing. My previous experience was with Actionscript where this system would have worked fine and I could access a method of an object (in this case the tile) easily as long s it is a public method.
I'd be happy to have another way to unselect the old item or to find a way to access a method within the tile. The challenge is to make the tiles show not highlighted without them being tapped themselves but when a different tile is tapped.
The code in my parent class is as follows:
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
//String _startType;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
List _outLineTiles = [];
int _counter = 0;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void initState() {
super.initState();
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_startContent.forEach((element) {
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
onTileSelected: clearHilights,
));
_counter++;
});
}
dynamic clearHilights(Key myKey) {
_outLineTiles.forEach((element) {
element.deselect(); // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
Key _foundKey = element.key;
print("Element Key $_foundKey");
});
}
.......
and further down within the widget build scaffold:
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
Then the tile class is as follows:
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final Function(Key) onTileSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.onTileSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
Key _myKey;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
bool _isSelected = false;
set isSelected(bool value) {
_isSelected = value;
print("set is selected to $_isSelected");
}
void changeSelection() {
setState(() {
_myKey = widget.key;
_isSelected = !_isSelected;
if (_isSelected) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
void deselect() {
setState(() {
isSelected = false;
color = Colors.transparent;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
widget.onTileSelected(_myKey);
changeSelection();
},
),
........
Thanks for any help.
Amended Code sample and explanation, that builds to a complete project, from here:
Following the advice from phimath I have created a full buildable sample of the relevant part of my project.
The problem is that the tiles in my listview are more complex with several elements, many of which are buttons in their own right so whilst phimath's solution works for simple text tiles I have not been able to get it working inside my own project. My approach is trying to fundamentally do the same thing as phimath's but when I include these more complex tiles it fails to work.
This sample project is made up of three files. main.dart which simply calls the project and passes in some dummy data in the way my main project does. working_draft.dart which is the core of this issue. And outline_tile.dart which is the object that forms the tiles.
Within working draft I have a function that returns an updated list of the tiles which should show which tile is selected (and later any other changes from the other buttons). This gets called when first going to the screen. When the tile is tapped it uses a callback function to redraw the working_draft class but this seems to not redraw the list as I would expect it to. Any further guidance would be much appreciated.
The classes are:
first class is main.dart:
import 'package:flutter/material.dart';
import 'package:listexp/working_draft.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: WorkingDraft(
startType: "Basic",
name: "Draft",
currentID: "anID",
startContent: [
["Heading", "New Heading"],
["Paragraph", "New Text"],
["Image", "placeholder"],
["Signature", "placeholder"]
],
));
}
}
Next file is working_draft.dart:
import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
final int selectedIndex;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent,
this.selectedIndex});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
int selectedIndex;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
var _outLineTiles = [];
int _counter = 0;
int _selectedIndex;
bool _isSelected;
dynamic clearHilights(int currentIndex) {
setState(() {
_selectedIndex = currentIndex;
});
}
updatedTiles() {
if (_selectedIndex == null) {
_selectedIndex = 0;
}
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_outLineTiles = [];
_startContent.forEach((element) {
_isSelected = _selectedIndex == _counter ? true : false;
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
myIndex: _counter,
onTileSelected: clearHilights,
isSelected: _isSelected,
));
_counter++;
});
}
#override
Widget build(BuildContext context) {
updatedTiles();
return Scaffold(
body: Center(
child: Column(children: [
SizedBox(height: 100),
Text("Outline", style: new TextStyle(fontSize: 15)),
Container(
height: 215,
width: 300,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightGreenAccent,
width: 2,
),
borderRadius: BorderRadius.circular(2),
),
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
),
]),
));
}
}
and finally is outline_tile.dart
import 'package:flutter/material.dart';
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final int myIndex;
final Function(int) onTileSelected;
final bool isSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.myIndex,
this.onTileSelected,
this.isSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
// Key _myKey;
bool _isSelected;
#override
void initState() {
super.initState();
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
}
void deselect() {
setState(() {
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else if (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
} else if (widget.outlineName == "Signature") {
Text("Called Signature");
} else {
Text("Called Image");
}
var _myIndex = widget.myIndex;
widget.onTileSelected(_myIndex);
deselect();
},
),
),
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_up),
onPressed: () {
print("Move Up");
}),
),
SizedBox(height: 5),
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_down),
onPressed: () {
print("Move Down");
}),
),
],
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.add_box),
onPressed: () {
print("Add another");
}),
),
SizedBox(
height: 10,
),
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.delete),
onPressed: () {
print("Delete");
}),
),
],
),
),
],
),
);
}
}
Thanks again
Instead of manually deselecting tiles, just keep track of which tile is currently selected.
I've made a simple example for you. When we click a tile, we just set the selected index to the index we clicked, and each tile looks at that to see if its the currently selected tile.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: Home()),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item: $index'),
tileColor: selectedIndex == index ? Colors.blue : null,
onTap: () {
setState(() {
selectedIndex = index;
});
},
);
},
);
}
}
I created a method that returns a list of Widget using this following code and would like to handle onPressed events individually! (i.e change background color of clicked button)
I'm new to Flutter and can't find a way to do this!
List<Widget> workingHoursButtons() {
List<Widget> timeButtons = [];
for (var i = 8; i <= 17; i++) {
timeButtons.add(
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 59.0,
height: 50.0,
child: FlatButton(
color: i == currentHour ? Color(0xff425660) : null,
shape: RoundedRectangleBorder(
side: BorderSide(),
borderRadius: BorderRadius.circular(3.0),
),
child: Text(
"$i",
style: TextStyle(
color: i == currentHour ? Colors.white : null,
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
onPressed: i < currentHour
? null
: () {
print(i);
},
),
),
),
);
}
return timeButtons;
}
So, based on Igor’s comment, yes, you’ll need a customizable StatefulWidget that can hold a unique id per button so that it can be checked against your current hour. A simplified example:
First, setting up your button list:
workingHoursButtons() {
buttonList = new List();
for (var i = 0; i <= 5; i++) {
buttonList.add(MyButton(index: i, whatHour: _currentHour,));
}
}
What your main build Widget might look like, using a ListView.builder:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: ...,
body: ListView.builder(
itemCount: buttonList.length,
itemBuilder: (BuildContext context, int index) {
return buttonList[index];
},
),
);
}
And your custom button widget, passing it an index and your currentHour:
class MyButton extends StatefulWidget {
MyButton({Key key, this.index, this.whatHour}) : super(key: key);
final int index;
final int whatHour;
#override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
Color _btnColor;
#override
void initState() {
super.initState();
_btnColor = _setInitColor();
}
Color _setInitColor() {
return widget.index == widget.whatHour ? Color(0xff425660) : null;
}
MaterialColor _changeColor() {
// just testing with blue/red colors
return widget.index < widget.whatHour ? Colors.blue : Colors.red;
}
#override
Widget build(BuildContext context) {
return FlatButton(
color: _btnColor,
child: Text('button' + widget.index.toString()),
onPressed: () {
setState(() {
_btnColor = _changeColor();
});
},
);
}
}
Modify as needed.
You need to use StatefulWidget and store colors for each button in a state of your widget. Then, using onPressed event, you can change color for this button (by passing its index) by calling setState method and your changed button will repaint.
I want to thank Igor and TWL for their answers but one thing i've noticed regarding this kind of issue is that there's no "correct" answer. For anyone who'll face this similar issue in the future, i would summarize it in this way.
You'll need a variable that will change for every button press(I had to create 4 in my case)
Call setState() for every change (i.e button press)
Of course depending on your dynamic buttons logic this can get messy but that seems to be the only solution.
On a side note: I think there should a better way to handle button press(using this.?) or maybe it's just me expecting flutter to work like JS
I'm learning Flutter (and coding in general) and I can't seem to find the issue with my latest project. When I run in simulator the slider forms just fine, click the thumb and the label shows, but the thumb won't move on the track at all, and thus never calls the onChanged event.
import 'resources.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class ItemDetail extends StatefulWidget {
final Item item;
ItemDetail({Key key, #required this.item}) : super(key: key);
#override
_ItemDetailState createState() => new _ItemDetailState(item: item);
}
class _ItemDetailState extends State<ItemDetail> {
Item item;
_ItemDetailState({Key key, #required this.item});
#override
Widget build(BuildContext context) {
double margin = ((item.listPrice - item.stdUnitCost)/item.listPrice)*100;
return new Scaffold(
appBar: AppBar(
title: new Text('Item Detail'),
),
body: new Column(
children: <Widget>[
new Padding(padding: EdgeInsets.symmetric(vertical: 20.0)),
new Text(item.itemCode),
new Text(item.itemDescription),
new Text(item.itemExtendedDescription),
new Divider(height: 40.0,),
new Text('List Price: \$${item.listPrice}'),
new Text('Cost: \$${item.stdUnitCost}'),
item.itemType=='N'
? new Text('Non-Stock (${item.itemType})')
: new Text('Stock Item (${item.itemType})'),
new Text('Available: ${item.stockAvailable}'),
new Padding(padding: EdgeInsets.symmetric(vertical: 10.0)),
new Slider(
value: margin,
divisions: 20,
min: 0.0,
max: 100.0,
label: margin.round().toString(),
onChanged: (double value) {
setState(() {
margin = value;
});
},
)
],
),
);
}
}
Problem: In the above example, margin is not a state variable. It is a local variable inside a build method.
Fix: Move this as an instance variable.
Reason: Widgets will get rebuild only when there is a change in its state.
Code:
class _ItemDetailState extends State<ItemDetail> {
Item item;
var margin;
_ItemDetailState({Key key, #required this.item}) {
this.margin = ((item.listPrice - item.stdUnitCost)/item.listPrice)*100;
}
#override
Widget build(BuildContext context) {
//same as now
}
}
For others coming here, I had a slightly different reason that the slider wasn't updating. I had the value set to a constant. Make sure that value is a variable.
This:
Slider(
value: _myValue,
...
onChanged: (newValue) => {
setState(() => _myValue = newValue)
},
),
Not this:
Slider(
value: 0, // <-- problem
...
onChanged: (newValue) => {
setState(() => _myValue = newValue)
},
),
I got this issue when I put the variable inside the build function 😩
#override
Widget build(BuildContext context) {
int brightness = 85;
return Scaffold(
...
Moved the variable outside the function and got it solved 😎
int brightness = 85;
#override
Widget build(BuildContext context) {
return Scaffold(
...
because each time when state is changed, this build method is called and sets the variable back to the assigned value 😝
This might help! (I USED STATEFULBUILDER TO UPDATE THE VALUE)
double _value = 20;
StatefulBuilder(
builder: (context, state) => Center(
child: CupertinoSlider(
value: _value,
min: 0.0,
max: 100.0,
onChanged: (val) {
state(() {
_value = val;
});
},
),
),
)
I think the variable margin does not have scope from where the UI is building. When you debug, you can see changes in variable but it is not rendering. I tried it in following way and able to update the UI value.
class _MyHomePageState extends State<MyHomePage> {
double margin=0;
Widget getSlider(BuildContext context)
{
return Slider(
value:margin.toDouble(),
onChanged: (newRating){
setState(() {
margin = newRating;
});
},
min: 0,
max: 100,
divisions: 5,
);
} // getslider method closed
// #0verride Widget build method
// under children widgets , simply call the getSlider method to place the slider
} //class
I was having an issue when giving the Slider(value: tasksDetails.taskImportance.toDouble(), etc)
I used Slider((_currentImportance ?? tasksDetails.taskImportance).toDouble(), etc)
and could then move the slider. I have no idea why this now works. Can only guess something to do with Denish's reason above 'Reason: Widgets will get rebuild only when there is a change in its state'. The selected slider value was in both cases was save correctly.
My first problem was answered by Dinesh. Then the slider wasn't smooth. I was only able to tap to get it to move. I am on iOS, so I changed the Slider widget to Slider.adaptive.
This changes the slider to a CupertinoSlider.
Copy and paste class for your testing convenience:
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
double _currentSliderValue = 20;
#override
Widget build(BuildContext context) {
return Slider.adaptive(
value: _currentSliderValue,
max: 100,
divisions: 5,
label: _currentSliderValue.round().toString(),
onChanged: (double value) {
setState(() {
_currentSliderValue = value;
});
},
);
}
}