As a follow up to the article of this link. I am working on a Flutter Project in which I want to increase the radius of a circle by means of two buttons.
Now I have partly succeeded, but it does not work quite well yet. I don't really know exactly how to explain this, but I will try my best:
What happened is that as soon as I press the minus button it goes a step smaller. If I press the minus button again, it will become a little smaller again. But as soon as I press the plus again, it remains the same size until I pass the number which was highest.
A video of the problem if its not be clearly explained.
Maybe someone knows how to handle this?
The Code (removed the unnecessary code):
class _AddRayPageState extends State<AddRayPage> {
List<Marker> myMarker = [];
final Set<Circle> circle = {};
GoogleMapController mapController;
int _n = 8;
LatLng startLoc = LatLng(52.0907374, 5.1214201);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Toevoegen'),
),
body: Stack(children: <Widget>[
GoogleMap(
onMapCreated: onMapCreated,
markers: Set.from(myMarker),
initialCameraPosition: CameraPosition(target: startLoc, zoom: 8),
circles: circle,
),
]),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 40.0),
child: Container(
child: Row(
children: <Widget>[
Container(
width: 40.0,
height: 40.0,
child: new FloatingActionButton(
heroTag: "btnAdd",
onPressed: add,
child: new Icon(
Icons.add,
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
new Text('$_n', style: new TextStyle(fontSize: 40.0)),
Container(
width: 40.0,
height: 40.0,
child: new FloatingActionButton(
heroTag: "btnMinus",
onPressed: minus,
child: new Icon(
const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
],
),
),
),
],
),
);
}
void add() {
setState(() {
_n++;
});
addRadiusToMap(_n);
}
void minus() {
setState(() {
if (_n != 1) _n--;
});
addRadiusToMap(_n);
}
void addRadiusToMap(radius) {
setState(() {
double reciprocal(double d) => 1000 * d;
circle.add(Circle(
circleId: CircleId("1"),
center: startLoc,
radius: reciprocal(radius.toDouble()),
));
});
}
circle is a Set, a Set doesn't allow repeated objects, in addRadiusToMap you're adding a new Circle object but if there is a previous one with the same fields (7 and 8 already existed) it won't add it, I think the widget GoogleMaps doesn't see a change in the Set<Circle> and doesn't update/animate the new Circle, for now try adding circle.clear() before adding if the add return false(returns false and the set is not changed)
void add() {
setState(() {
_n++;
addRadiusToMap(_n);
});
}
void minus() {
if (_n != 1)
setState(() {
_n--;
addRadiusToMap(_n);
});
}
void addRadiusToMap(radius) {
//No purpose in having 2 setState
double reciprocal(double d) => 1000 * d;
circle.clear(); //Maybe clear the set before adding to avoid repeated values
circle.add(Circle(
circleId: CircleId("1"),
center: startLoc,
radius: reciprocal(radius.toDouble()),
));
}
I'm not really sure (I haven't used the Google Maps package) what's the purpose of having a Set<Circle> but as far as I understand the code, the Set is not really updated because you already has those values
Related
I have made radius based flutter google maps in which I am using circle property to draw a circle radius based. It only shows up when I increment or decrement the radius but I want it to be showing by default as well when user navigates to location screen in my flutter app. This is how the map screen loads and as soon as i tap the + or - buttons it will start showing the circle like this I don't know why this is happening. If anyone knows what am I doing wrong then please let me know. I am attaching the code of the screen below :
class MapRadius extends StatefulWidget {
const MapRadius({Key? key}) : super(key: key);
#override
State<MapRadius> createState() => _MapRadiusState();
}
class _MapRadiusState extends State<MapRadius> {
// List<Marker> myMarker = [];
final Set<Circle> circle = {};
// late GoogleMapController mapController;
final Completer<GoogleMapController> _controller = Completer();
int _n = 5;
// LatLng startLoc = const LatLng(52.0907374, 5.1214201);
LatLng? currentLatLng;
late GoogleMapController mapController;
Location location = Location();
var latitude;
var longitude;
late LocationData _locationData;
get() async {
_locationData = await location.getLocation();
latitude = _locationData.latitude;
longitude = _locationData.longitude;
setState(() {
currentLatLng = LatLng(latitude, longitude);
});
}
#override
initState() {
super.initState();
get();
}
#override
void setState(VoidCallback fn) {
super.setState(fn);
FirebaseFirestore.instance
.collection("userpreferences")
.doc(FirebaseAuth.instance.currentUser!.uid)
.update({"radius": _n});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFF2A3B6A),
title: const DelayedDisplay(
delay: Duration(seconds: 1), child: Text('Location Range')),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: const [
Padding(
padding: EdgeInsets.only(right: 20),
child: Tooltip(
showDuration: Duration(seconds: 5),
triggerMode: TooltipTriggerMode.tap,
textStyle: TextStyle(
fontSize: 18, color: Colors.white, fontFamily: "ProductSans"),
message:
'Increase or decrease radius according to your own preference. + and - can be used to add Kilometers.\nThe Kilometers are multiples of 10 (e.g. 5 = 50KM)\nMax limit 100 KM nearby. Default is 50KM.\nRange gets updated as soon as you add or remove a kilometer.',
child: Icon(Icons.info),
),
),
],
),
body: currentLatLng == null
? Center(
child: SpinKitSpinningLines(
color: Theme.of(context).primaryColor,
size: 90.0,
lineWidth: 5,
))
: Stack(children: <Widget>[
GoogleMap(
circles: circle,
myLocationEnabled: true,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
initialCameraPosition:
CameraPosition(target: currentLatLng!, zoom: 12),
),
]),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 40.0),
child: Row(
children: <Widget>[
SizedBox(
width: 40.0,
height: 40.0,
child: FloatingActionButton(
heroTag: "btnAdd",
onPressed: add,
child: LineIcon(
LineIcons.plus,
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
const SizedBox(
width: 5,
),
Text('$_n', style: const TextStyle(fontSize: 26.0)),
const SizedBox(
width: 5,
),
SizedBox(
width: 40.0,
height: 40.0,
child: FloatingActionButton(
heroTag: "btnMinus",
onPressed: minus,
child: LineIcon(
LineIcons.minus,
color: Colors.black,
size: 30,
),
backgroundColor: Colors.white,
),
),
],
),
),
],
),
);
}
void add() {
setState(() {
if (_n < 10) {
_n++;
}
addRadiusToMap(_n);
});
}
void minus() {
if (_n != 1) {
setState(() {
_n--;
addRadiusToMap(_n);
});
}
}
void addRadiusToMap(radius) {
double reciprocal(double d) => 700 * d; // 1000 before
circle.clear();
circle.add(Circle(
circleId: const CircleId("1"),
center: currentLatLng!,
radius: reciprocal(radius.toDouble()),
));
}
}
I have a simple stream that returns a countdown when an operation is performed.
static Stream<double> counterStream = (() async* {
double i = 0.1;
double z = 0.0;
while (z < 0.99) {
await Future.delayed(Duration(seconds: 1));
yield z = z+i;
}
})();
I do not start it immediately when the widget is initialized, but by taping on the button that is inside StreamBuilder
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});},
First time Stream run correctly, but the second time does not start, and if I close the screen and return to it again, start the stream - I get an error
Bad state: Stream has already been listened to.
I don’t know how to reload a stream that has already completed its execution and, in theory, has the status ConnectionState.done by clicking on the button.
Full class code
class UpdatePage extends StatefulWidget {
UpdatePage({Key key, this.title}) : super(key: key);
static const String routeName = "/UpdatePage";
final String title;
#override
_UpdatePageState createState() => new _UpdatePageState();
}
class _UpdatePageState extends State<UpdatePage> {
Stream<double> _result;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(""),
),
body: SafeArea(
child: StreamBuilder<double>(
stream: _result,
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
List<Widget> children;
if (snapshot.hasError) {
children = <Widget>[
Center(
child: Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
children = <Widget>[
Center(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(child: Text("Обновление данных", style: new TextStyle( fontSize: 18.0))),
)),
Center(
child: Container(padding: EdgeInsets.all(20.0),child: RaisedButton.icon(
textColor: Colors.white,
icon: FaIcon(FontAwesomeIcons.retweet, size: 18,),
color: Color(0xFF6200EE), label: Text("Обновить"),
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});
},
),),
),
];
break;
case ConnectionState.waiting:
children = <Widget>[
Center(
child: new Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
),
),
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
center: new Text('', style: new TextStyle( fontSize: 20.0)),
),
];
break;
case ConnectionState.active:
children = <Widget>[
Center(
child: new Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
),
),
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
percent: snapshot.data,
center: new Text('${snapshot.data.toStringAsFixed(2)}', style: new TextStyle( fontSize: 20.0)),
progressColor: Colors.orange,
),
];
break;
case ConnectionState.done:
children = <Widget>[
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
center: new Icon(
Icons.done,
size: MediaQuery.of(context).size.width/4,
color: Colors.green,
),
backgroundColor: Colors.grey,
progressColor: Colors.orange,
)
,
Center(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(child: Text("Обновление успешно завершено", style: new TextStyle( fontSize: 18.0))),
)),
Center(
child: Container(padding: EdgeInsets.all(20.0),child: RaisedButton.icon(
textColor: Colors.white,
icon: FaIcon(FontAwesomeIcons.retweet, size: 18,),
color: Color(0xFF6200EE), label: Text("Обновить"),
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});
},
),),
),
];
break;
}
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
)
));
;
}
}
Since counterStream is static, it is initialized once during the lifetime of the app. During its initialization, the async* function is called once.
So, you create only one stream during the lifetime of the application. counterStream is a reference to that stream.
The second time you press the button, you set the _result to what it already was, the reference to the stream that I mentioned above. Therefore, StreamBuilder does not change anything. As far as it is concerned, it's a rebuild without change.
When you close the screen and come back, your new StreamBuilder is trying to treat the existing, already listened to stream as a new stream, hence the error.
The solution
I think you are trying to restart a countdown every time the button is pressed. Editing the code to be like this may solve it:
static Stream<double> Function() counterStream = () async* {
double i = 0.1;
double z = 0.0;
while (z < 0.99) {
await Future.delayed(Duration(seconds: 1));
yield z = z+i;
}
};
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream();
});},
I have a Dialog class in which I want to show different designations that could be assigned to an employee.
In the beginning, I tried to use only a RaisedButton to select the desired designations. Within the App, the Button should change Colors. This part is found within a StatefulWidget.
I also tried a modified version, where I created a new StatefulWidget only for the Dialog part but this part did not have any effect, thus I thought to implement a SwitchListTile to do the same thing.
The SwitchListTile gets activated and deactivated although only the true value gets registered. This means that when I deactivate (swipe to left) the code does not go within the following setState:
setState(() { hEnabled[hDesignations[index].designation] = value; });
Also when the hEnabled Map gets changed within the setState method the following code does not re-run to change the color of the container:
color: hEnabled[hDesignations[index].designation] ? Colors.green : Colors.grey,
Part with the Dialog:
Widget buildChooseDesignations(
BuildContext context, List<Designation> hDesignations) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.circular(8.0),
),
child: _buildDialogChild(context, hDesignations),
);
}
_buildDialogChild(BuildContext context, List<Designation> hDesignations) {
//todo: when editing an employee I need the chosen designations (have to pass a list)
Map<String, bool> hEnabled = new Map<String, bool>();
for (var i = 0; i < hDesignations.length; i++) {
hEnabled[hDesignations[i].designation] = false;
}
return Container(
height: 200.0,
//todo: width not working properly
width: 50,
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: hDesignations.length,
itemBuilder: (context, index) {
return Row(
children: <Widget>[
Expanded(
child: Container(
width: 10,
color: hEnabled[hDesignations[index].designation]
? Colors.green
: Colors.grey,
padding: EdgeInsets.only(left: 80),
child: Text(hDesignations[index].designation,
style: TextStyle(fontWeight: FontWeight.bold),),
),
),
Expanded(
child: SwitchListTile(
value: hEnabled[hDesignations[index].designation],
onChanged: (bool value) {
setState(() {
hEnabled[hDesignations[index].designation] =
value;
});
}),
)
],
);
}),
),
SizedBox(
height: 15.0,
),
RaisedButton(
color: Colors.blueGrey,
child: Text(
'set',
style: TextStyle(color: Colors.white),
),
onPressed: () {
//todo: save the 'newly' selected designations in a list on set click
},
)
],
),
);
}
The Dialog is called when I click on the Add + FlatButton and looks like this:
ButtonTheme(
height: 30.0,
// child: Container(),
child: FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: Colors.blueGrey.shade200,
onPressed: () {
//todo add Dialog
// List<Designation> hList = state.designations;
showDialog(
context: context,
builder: (context) => buildChooseDesignations(
context, state.designations));
// DesignationDialog(
// designations:state.designations));
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
child: Text(
'Add +',
style: TextStyle(color: Colors.black),
),
),
),
Found the problem :)
First I did re-write everything into a new StatefulWidget. This I needed since I want that my widget gets re-build after I click on the SwitchListTile to re-color my Container.
Then I had to move my hEnabled (re-named hChecked) map outside the state. The reason was that the widget would re-build all the everything including the initialization of this map, making the user's input useless.
The same applies to the RaisedButton Widget.
Here is my code:
class DesignationDialog extends StatefulWidget {
final List<Designation> designations;
final Map<String, bool> hChecked;
DesignationDialog({Key key, this.designations, this.hChecked}) : super(key: key);
#override
_DesignationDialogState createState() => _DesignationDialogState();
}
class _DesignationDialogState extends State<DesignationDialog> {
_buildDialogChild(BuildContext context, List<Designation> hDesignations) {
//todo: when editing an employee I need the chosen designations (have to pass a list)
// for (var i = 0; i < hDesignations.length; i++) {
// hChecked[hDesignations[i].designation] = false;
// }
return Container(
height: 200.0,
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: hDesignations.length,
itemBuilder: (context, index) {
// return ButtonTheme(
// //todo: fix the width of the buttons is not working
// minWidth: 20,
// child: RaisedButton(
// color: widget.hChecked[hDesignations[index].designation]
// ? Colors.green
// : Colors.grey,
// child: Text(hDesignations[index].designation),
// onPressed: () {
// //todo mark designation and add to an array
// setState(() {
// widget.hChecked[hDesignations[index].designation] =
// !widget
// .hChecked[hDesignations[index].designation];
// });
// },
// ),
// );
// -- With Switch
return Row(
children: <Widget>[
Expanded(
child: Container(
child: Text(hDesignations[index].designation),
width: 10,
color: widget.hChecked[hDesignations[index].designation]
? Colors.green
: Colors.grey,
)),
Expanded(
child: SwitchListTile(
value: widget.hChecked[hDesignations[index].designation],
onChanged: (bool value) {
setState(() {
widget.hChecked[hDesignations[index].designation] =
value;
});
}),
)
],
);
// -- end
}),
),
SizedBox(
height: 15.0,
),
RaisedButton(
color: Colors.blueGrey,
child: Text(
'set',
style: TextStyle(color: Colors.white),
),
onPressed: () {
//todo: save the 'newly' selected designations in a list on set click
},
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.circular(8.0),
),
child: _buildDialogChild(context, widget.designations),
);
}
This is my code (I'm very new to this so likely it's a bit messy, but it was doing everything I wanted up to the transition element.
What I'm looking to do is, take a list item, which is randomly generated on button press, fade it out and then back in again as a different random item from the list....then when you click again, it fades out, randomises it and fades back in again.
Currently it will change the list item then fade out on button press, then when you press again it will fade in as a new random list item.
var element = 'what oil do you need.';
final _random = new Random();
var fade = true;
#override
Widget build(BuildContext context) {
return Center(
child: FlatButton(
onPressed: (){
setState(() {
element = list[_random.nextInt(list.length)];
fade =! fade;
});
},
child: Container(
width: 500,
height: 500,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 15,
child: Container(
width: 350,
height: 350,
child: Image.asset('images/tealoildrop.png'),
),
),
AnimatedOpacity(
opacity: fade?1:0,
duration: Duration(seconds: 2),
child: Text('$element',
style: TextStyle(
fontFamily: 'Sacramento',
fontSize: 35,
color: Colors.white,
),
),
),
],
),
),
),
);
}
}
Its got to have something to do with this from setState
fade =! fade;
because I'm telling it that whatever is stored in the var fade, to return the opposite....I just don't know how to turn it from true to false to true again in one command...although I could be completely wrong with that!
fade = !fade means turn fade value to false if it's true or viceversa, that's a correct usage for a bool. Can you try below code? I set it false then added a delay then set it true.
var element = 'what oil do you need.';
final _random = new Random();
var fade = true;
#override
Widget build(BuildContext context) {
return Center(
child: FlatButton(
onPressed: (){
setState(() {
element = list[_random.nextInt(list.length)];
fade=!fade;
Future.delayed(const Duration(milliseconds: 500), ()
{setState(() {fade=!fade;});});
});
},
child: Container(
width: 500,
height: 500,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 15,
child: Container(
width: 350,
height: 350,
child: Image.asset('images/tealoildrop.png'),
),
),
AnimatedOpacity(
opacity: fade?1:0,
duration: Duration(seconds: 2),
child: Text('$element',
style: TextStyle(
fontFamily: 'Sacramento',
fontSize: 35,
color: Colors.white,
),
),
),
],
),
),
),
);
}
}
Thank you so much Lunedor, totally pointed me in the right direction!!
So it didn't quite do what I expected, but with a little more tweaking I found that this has the perfect effect
onPressed: () {
setState(() {
fade = !fade;
Future.delayed(const Duration(seconds:2, milliseconds: 500),(){
setState(() {
fade=!fade;
Future.delayed(const Duration(milliseconds: 50),(){
setState(() {
element = list[_random.nextInt(list.length)];
It fades out slowly, paused (and changes the list item in the background) then slowly fade back in with the new item!
I'm looking to highlight a button from a grid when it's pushed.
Unfortunately, when I do so, the whole column lights up. As I'm new to flutter/Dart and to coding in general, I'm not sure if my problème is my lack of logic or something that I wouldn't know about that coding language?
The home page :
import 'package:flutter/material.dart';
import 'package:sequencer_n_lignes/utilities/sequence_class.dart';
class Home extends StatefulWidget {
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
int i = 0, y = 0, indexTempo = 0;
int countChanel = 0, countBtn = 0;
bool isPlaying = false;
List<Button> btnList = List();
List<Chanel> chanelList = List();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.grey[800],
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(20, 10, 20, 10),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 5,
spreadRadius: 1,
)
],
color: Colors.grey[900],
border: Border.all(
color: Colors.white,
width: 0.5,
)),
child: Row(
children: <Widget>[
/*__________________________________________ADD/REMOVE BUTTONS___________________*/
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
for (i = 0; i < 4; i++) {
btnList.removeLast();
countBtn--;
}
setState(() {});
},
),
Text('BUTTONS: $countBtn'),
IconButton(
icon: Icon(
Icons.add,
),
onPressed: () {
for (i = 0; i < 4; i++) {
btnList.add(Button(
id: countBtn,
onColor: Colors.blue,
offColor: Colors.grey[900],
state: false));
countBtn++;
}
setState(() {});
},
),
],
), //
/*_________________________________________ADD/REMOVE CHANEL___________________*/
Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
chanelList.removeLast();
countChanel--;
setState(() {});
},
),
Text('CHANEL: $countChanel'),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
chanelList.add(
Chanel(id: countChanel, buttonList: btnList));
countChanel++;
setState(() {});
},
),
],
),
SizedBox(
width: 30,
),
/*_____________________________________________CONTROLS___________________*/
Row(
children: <Widget>[
IconButton(
icon: Icon(
Icons.play_arrow,
color: (isPlaying) ? Colors.green : Colors.white,
),
onPressed: () {
if (isPlaying)
isPlaying = false;
else
isPlaying = true;
setState(() {});
},
),
IconButton(
icon: Icon(
Icons.stop,
color: (isPlaying) ? Colors.white : Colors.red[900],
),
onPressed: () {
if (isPlaying)
isPlaying = false;
else
isPlaying = true;
setState(() {});
},
),
IconButton(
icon: Icon(
Icons.refresh,
color: Colors.white,
),
onPressed: () {
for (i = 0; i < chanelList.length; i++) {
for (y = 0; y < btnList.length; y++) {
chanelList[i].buttonList[y].state = false;
}
}
setState(() {});
},
),
RaisedButton.icon(
icon: Icon(
Icons.details,
color: Colors.white,
),
label: Text('OK'),
color: Colors.red[900],
onPressed: () {
setState(() {});
},
)
],
),
],
),
),
/*__________________________________________ GRID ___________________*/
Column(
children: List.generate(countChanel, (indexChanel) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(countBtn, (indexBtn) {
return Padding(
padding: EdgeInsets.fromLTRB(3, 0, 3, 0),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 0.1,
spreadRadius: 0.1,
),
],
border: Border.all(
color: Colors.white,
width: 0.5,
),
),
width: 80,
height: 80,
//THATS WHERE THE PROBLEM IS///////////////////////////
child: FlatButton(
// child: Text(
// '${chanelList[indexChanel].id.toString()} \n${chanelList[indexChanel].buttonList[indexBtn].id.toString()}\n$indexChanel-$indexBtn\n${chanelList[indexChanel].buttonList[indexBtn].state}'),
color: (chanelList[indexChanel]
.buttonList[indexBtn]
.state)
? chanelList[indexChanel]
.buttonList[indexBtn]
.onColor
: chanelList[indexChanel]
.buttonList[indexBtn]
.offColor,
onPressed: () {
if (chanelList[indexChanel]
.buttonList[indexBtn]
.state) {
chanelList[indexChanel]
.buttonList[indexBtn]
.state = false;
} else {
chanelList[indexChanel]
.buttonList[indexBtn]
.state = true;
}
setState(() {});
},
),
),
);
}),
),
);
}),
),
],
),
),
);
}
}
The class
class Button {
int id;
Color onColor = Colors.red[900], offColor = Colors.grey[900];
Color actualColor;
bool state = false;
Button({this.id, this.onColor, this.offColor, this.state});
}
class Chanel {
int id;
List<Button> buttonList;
Chanel({this.id, this.buttonList});
}
Screen shot of the app
Pretty big code but I think the problem is that whenever you add a new Channel, you are giving it an existen buttonList. Try creating a new buttonList when you add a new Channel
chanelList.add(
Chanel(
id: countChanel,
// Here is your problem, the reference to the buttons is the same
// in all channels. Try creating new buttons for every channel
buttonList: btnList,
),
);
I'll go over some of the programming logic improvements 1st and then explain why you are getting unexpected results.
1) Color actualColor inside Button class is never used, remove it.
2) Unless each button is going to have different onColor and offColor I suggest moving those two out of the Button class or at least declare them as static. You are needlessly instantiating them over and over again when I'm guessing you only need those once, this is a very tiny memory improvement (especially since you won't have thousands of buttons) but more importantly removing those from the Button class or making them static will make your code easier to read and understand, as well as cut down the number of arguments needed to pass to the constructor (again cleaner code).
3) Your loop counters "i" and "y", declare them where they are needed. Reduce the scope of the variable so that it is only visible in the scope where it is used. There are many... many reasons for doing so, in a nutshell when a larger scope than necessary is used, code becomes less readable, harder to maintain, and more likely to reference unintended variables.
Now for your actual problem. The problem isn't with if/else statements it has to do with lists and how they are handled in memory.
Going back to my 3rd point above, always use the smallest scope possible.
You are declaring your btnList here
class _Home extends State<Home> {
int i = 0, y = 0, indexTempo = 0;
int countChanel = 0, countBtn = 0;
bool isPlaying = false;
List<Button> btnList = List();
List<Chanel> chanelList = List();
Later on you are adding that same btnList to different Channels here:
Text('CHANEL: $countChanel'),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
chanelList.add(
Chanel(id: countChanel, buttonList: btnList));
countChanel++;
setState(() {});
},
I suggest going back to basics and learn in general about arrays , lists and pointers. You should also search for deep and shallow copying.
What you've done in the code block above is setting the same btnList to all of the chanelList items.
Lets say you create btnList that has 4 items. Lets say you create channelList that has 2 items. Then channelList[ 0 ].buttonList[ 0 ].state will always be the same as channelList[ 1 ].buttonList[ 0 ].state because they are both pointing to the same Button.
To get this:
Quick and easy fix would be to do something like this:
IconButton(
icon: Icon(Icons.add),
onPressed: () {
List<Button> tmpBtnList = new List<Button>();
for(int i=0; i<btnList.length; i++){
tmpBtnList.add(new Button(id: i,state: false));
}
chanelList.add(
Chanel(id: countChanel, buttonList: tmpBtnList));
countChanel++;
setState(() {});
},
),
Complete code on DartPad.
PS I would also refrain from manually counting list items like you've done, just use the the provided .length.