How to toggle boolean in listview item independently? - flutter

I am trying to change the color of a container with a button press. It looks at the boolean value and determines the color. How can I change the boolean for each individual listview item independently?
I am using provider for state management also. This is just an example of the type of thing I am trying to do.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
bool colorToggle = false;
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, i) {
return Container(
width: 100,
height: 100,
color: colorToggle ? Colors.blue : Colors.green,
child: GestureDetector(
onTap: () {
//how to change the colorToggle independendently with each listview item?
print('tapped');
},
child: Text('change color'),
),
);
},
),
),
);
}
}

You cannot achieve this with with a single boolean value.
You can use a List instead. Add the index of the item to the List once an item is tapped. Then check the List for the item's index to set color
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List toggled = List();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, i) {
return Container(
width: 100,
height: 100,
color: toggled.contains(i) ? Colors.blue : Colors.green,
child: GestureDetector(
onTap: () {
toggled.add(i);
setState(() {});
},
child: Text('change color'),
),
);
},
),
),
);
}
}

Related

how can I have an isolated behavior per element in a list?

I want only the item I click on to change color, but all items inherit the new color,
how can I have an isolated behavior per element in a list?
import 'package:flutter/material.dart';
class myClass extends StatefulWidget {
#override
State<myClass> createState() => _myClassState();
}
class _myClassState extends State<myClass> {
Color color =Colors.green ;
List ListOfProfils = ["a","b","c"] ;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemBuilder: (context, index) => Container(
key: Key(ListOfProfils[index]),
height: 100,
width: 100,
color : color,
margin: EdgeInsets.all(10),
child: TextButton(
onPressed: () => setState(() {
print(color);
color =Colors.red ;
print(color);
}), child: Text("CLICK")),
),
itemCount: 3,
),
);
;
}
}
change your code to this.
class _myClassState extends State<myClass> {
Color color = Colors.green;
List ListOfProfils = ["a", "b", "c"];
int selectedIndex = -1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemBuilder: (context, index) => Container(
key: Key(ListOfProfils[index]),
height: 100,
width: 100,
color: selectedIndex == index ? Colors.red : color,
margin: EdgeInsets.all(10),
child: TextButton(
onPressed: () => setState(() {
selectedIndex = index;
}),
child: Text("CLICK")),
),
itemCount: 3,
),
);
}
}

How to make short ListView of DropDownButtons build faster?

I have a short ListView of a maximum of 10 items. Each list item will contain a DropDownButton which will hold around 1K DropDownMenuItems for selections.
In native Android, I was able to implement one that performed very smoothly, but with Flutter it takes a while to build the ListView which causes the UI to freeze.
In my case, I will need to rebuild the ListView upon every change in one of its items, so It will be a major issue.
Is there a way to make the ListView build faster, or at least be able to display a ProgressBar till it builds?
N.B: Using --profile configuration to simulate a release version improves the performance a lot, but still there is a sensed freeze.
Here's my sample code which you can directly copy/paste if you want to test it yourself.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool showList = false;
final List<DropdownMenuItem<int>> selections = List.generate(
1000,
(index) => DropdownMenuItem<int>(
value: index,
child: Text("$index"),
),
);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
width: double.infinity,
child: Column(
children: [
ElevatedButton(
child: Text("toggle list visibility"),
onPressed: () {
setState(() {
showList = !showList;
});
},
),
Expanded(
child: showList
? ListView.builder(
cacheExtent: 2000,
itemCount: 10,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Container(
height: 200,
color: Colors.green,
child: Column(
children: [
Text("List Item: $index"),
DropdownButton<int>(
onChanged: (i) {},
value: 1,
items: selections,
),
],
),
),
),
);
})
: Text("List Not Built"),
),
],
),
),
),
);
}
}
Load dropdown when clicking the button.
Add this widget on your main List View
InkWell(
onTap: () {
showDialog(
context: context,
builder: (_) {
return VendorListAlert(selectVendor: selectVendorTap);
});
},
child: // create a widget, looks like your drop down
),
Handle tap event
void selectVendorTap(pass your model){
// logic
}
Sample for custom Alert
No need to create a mutable widget, the immutable widget is better.
class VendorListAlert extends StatefulWidget {
final Function selectVendor;
const VendorListAlert({Key key, this.selectVendor}) : super(key: key);
#override
_VendorListAlertState createState() => _VendorListAlertState();
}
class _VendorListAlertState extends State<VendorListAlert> {
List<UserModel> _searchVendor = [];
#override
void initState() {
super.initState();
_searchVendor = List.from(ypModel);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
width: width,
child: ListView.builder(
shrinkWrap: true,
itemCount: _searchVendor.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {
widget.selectVendor(_searchVendor[index]);
Navigator.pop(context);
},
child:
),
);
},
),
),
);
}
}

Scaffold displaying a bar at the bottom that is not meant to be there

My Scaffold displays a bar at the bottom like this: that I did not put there (I know that it is the scaffold because when I remove it the bar is gone. but I cant do this without scaffold) this is my code:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
// TODO: implement initState
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
body: Column(
children: <Widget>[
Center(
child: Text('Scaffold Bar test'),
),
Container(
height: MediaQuery.of(context).size.height - 88,
child: ListView.builder(itemBuilder: (BuildContext context,int index) {
return Container(
height: 40.0,
width: MediaQuery.of(context).size.width,
color: index % 2 == 0 ? Colors.blue : Colors.orange,
);
}),
),
],
),
);
}
}
Set resizeToAvoidBottomPadding: false in the Scaffold.
And use MediaQuery.removePadding with removeTop: true to remove unnecessary padding at top of the ListView.builder.
Use Expanded instead of getting height from MediaQuery.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
// TODO: implement initState
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
}
#override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: Scaffold(
backgroundColor: Colors.red,
resizeToAvoidBottomPadding: false,
body: Column(
children: <Widget>[
Center(
child: Text('Scaffold Bar test'),
),
Expanded(
child: ListView.builder(itemBuilder: (BuildContext context,int index) {
return Container(
height: 40.0,
width: MediaQuery.of(context).size.width,
color: index % 2 == 0 ? Colors.blue : Colors.orange,
);
}),
),
],
),
),
);
}
}

Change only one item style instead everyone. There's changing all ListTile instead one

After when I tapped the ListTile there should change only one item but when I tapped there changed all ListTile.
class _HomeScreenState extends State<HomeScreen> {
bool checked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 7,
itemBuilder: (BuildContext context, int i) {
child: return Container(
decoration: new BoxDecoration(
color: checked ? Colors.teal : Colors.white
),
child: ListTile(
onTap: () => setState(() => checked = true),
title: Text("My product"),
),
);
},
),
);
}
}
Try below code,
class _HomeScreenState extends State<HomeScreen> {
int checkedIndex = -1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 7,
itemBuilder: (BuildContext context, int i) {
return Container(
decoration: BoxDecoration(
color: checkedIndex == i ? Colors.teal : Colors.white
),
child: ListTile(
onTap: () => setState(() => checkedIndex = i),
title: Text("My product"),
),
);
},
),
);
}
}
Try using number which will hold index of tapped list item instead of bool and just check if(i == checked_index). In your case problem is that your bool checked don't know which item is checked because checked is global variable for your whole list.

Flutter Custom Multi Selection

Question
Hi, how can I implement a custom multi selection widget in flutter? Here is an example :
I have a list of widget created with a ListView.builder and I simply want to change color based on userTap.
How can I implement this? I'm able to change color when user tap on one option but then, when another button is tapped I can't understand how to reverse the state of the old selected option.
I was searching for a solution with flutter standard state management or maybe with bloc library.
example code
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index){
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: SelectableContainer(text: 'option', index: index,),
);
},
),
);
}
}
class SelectableContainer extends StatefulWidget {
final String text;
final int index;
SelectableContainer({
#required this.text,
#required this.index
});
#override
_SelectableContainerState createState() => _SelectableContainerState();
}
class _SelectableContainerState extends State<SelectableContainer> {
bool _isSelected = false;
Color unselectedColor = Colors.white;
Color selectedColor = Colors.blue;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
setState(() {
_isSelected = true;
});
},
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 50,
color: _isSelected ? selectedColor : unselectedColor,
child: Center(
child: Text(widget.text),
),
),
);
}
}
Thanks
First store the selection in list when the user select an item
selectionList.add(title.id);
Then in the ListView.builder change the color of the title if it's in the selectionList
Title(color: selectionList.contain(listOfTitls[index].id)? Colors.green : Colors.White);
update
this trick will do it for you
return GestureDetector(
onTap: (){
setState(() {
_isSelected =_isSelected? false:true;//this line
});
},
In your setState method, you can just do _isSelected = !_isSelected; instead of always setting it to true. That way the tile will get unselected if _isSelected is set to true.
Although the above way wil solve your problem, there is also another widget flutter has for this same scenario called ListTile. Here is a link to the documentation. You might also want to check that out. It also has an onTap callback built into it directly.
Here is a quick app I built using ListTile. Hope it solves your problem. Cheers!
import 'package:flutter/material.dart';
class DemoClass extends StatefulWidget {
_DemoClassState createState() => _DemoClassState();
}
class _DemoClassState extends State<DemoClass> {
int _selectedIndex = -1;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
height: 50,
color: _selectedIndex == index ? Colors.blue : Colors.transparent,
child: ListTile(
title: Text('This is some title'),
onTap: () => setState(() {
_selectedIndex = index;
}),
),
);
}
),
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Center(
child: Container(
child: DemoClass(),
),
),
);
}
}
void main() {
runApp(MyApp());
}