Rebuild widget when contents of list change - flutter

I have objects in a list that users can remove. If the list is empty, I want to display some text but I tried to do this with a ListView.builder() and it displayed the text many times and I want it to be displayed just once. Is there some sort of property that I can listen to in the list?

import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List items =new List();
bool _isLoading =false;
#override
void initState() {
// TODO: implement initState
super.initState();
items = getDummyList();
setState(() {
_isLoading=false;
});
}
#override
Widget build(BuildContext context) {
return
MaterialApp(
home: Scaffold(
body: _isLoading
?Center(
child: CircularProgressIndicator())
:
items.length==0?Center(child: Text('No items in the list'),):
Container(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(items[index]),
onDismissed: (direction) {
setState(() {
items.removeAt(index);
});
},
child: Container(
height: 50.0,
decoration: BoxDecoration(border: Border.all(width: 1.0)),
padding: EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
Text(
items[index],
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
)
],
),
),
);
},
)),
),
);
}
List getDummyList(){
setState(() {
_isLoading=true;
});
List list = List.generate(5, (i) {
return "Item ${i +1 }";
});
return list;
}
}
Check out this example , i have made a swipe dismiss example for listview,where if you remove the items it will display the text.
let me know if it works.
Thanks.

Related

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:
),
);
},
),
),
);
}
}

Access List from statefulwidget to state

I want to pass a List from screen 1 to screen 2 statefulwidget and want to add data to it.
List type Question,
class Question {
String questionText;
String answerText;
Question({this.questionText, this.answerText});
}
I passed the list to 2nd screen
class CardPage extends StatefulWidget {
final List<Question> questionBank;
CardPage({#required this.questionBank});
#override
......
I added the content to the list from state,
TextField(onChanged: (text) {question = text;}),
TextField(onChanged: (text) {answer = text;}),
FlatButton(
child: Text("Create"),
onPressed: () {setState(() {
questionBank.add(Question(questionText: question, answerText: answer));});
}
)
Bt I don't know how to connect the List in stateful widget to the state to access it. I know there is widget for it but don't know how to completely import the list to state with it.
Anyone help me
You can pass a function instead of list to your CardPage. It should be called when you create a new question. I think it is the most simple solution.
You CardPage should be like this:
class CardPage extends StatefulWidget {
final Function(Question) createQuestion;
CardPage({Key key, #required this.createQuestion}) : super(key: key);
#override
State<StatefulWidget> createState() => _CardPageState();
}
class _CardPageState extends State<CardPage> {
String _question = '';
String _answer = '';
#override
Widget build(BuildContext context) {
return Column(children: [
TextField(onChanged: (text) {
_question = text;
}),
TextField(onChanged: (text) {
_answer = text;
}),
FlatButton(
child: Text("Create"),
onPressed: () {
setState(() {
final question =
Question(questionText: _question, answerText: _answer);
widget.createQuestion(question);
});
})
]);
}
}
Question lists owner state should be like this:
class _FirstWidgetState extends State<FirstWidget> {
final List<Question> questionBank = [];
#override
Widget build(BuildContext context) {
return ...
CardPage(createQuestion: _createQuestion);
...
}
void _createQuestion(Question question) {
setState(() {
questionBank.add(question);
});
}
}
Try this
simple demo.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_State createState() => _State();
}
class _State extends State<MyApp> {
final List<String> names = <String>['apple', 'samsung', 'shirsh'];
TextEditingController nameController = TextEditingController();
void addItemToList(){
setState(() {
names.insert(0,nameController.text);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Column(
children: <Widget>[
Row(
children: [
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.all(20),
child: TextField(
controller: nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Contact Name',
),
),
),
),
Expanded(
flex: 2,
child: RaisedButton(
child: Text('Add Item'),
onPressed: () {
if(nameController.text.toString()!="")
addItemToList();
},
),
)
],
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: names.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
margin: EdgeInsets.all(2),
color: Colors.cyan,
child: Center(
child: Text('${names[index]}',
style: TextStyle(fontSize: 18),
)
),
);
}
)
)
]
)
);
}
}

how Can i make this Single selection Flutter?

I have an Apps which is having a listview with the reaction button in a flutter . I want to make this when a user clicked any of this love icon then it's filled with red color.
enter image description here
enter image description here
Like this image but the problem is when I clicked one of this love icon all of the icons turned into red color but I only want to change the color of love of icon which one is Selected.
import 'package:flutter/material.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: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool like;
#override
List<String> user = ['Dipto', 'Dipankar', "Sajib", 'Shanto', 'Pranto'];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Demu'),
),
body: Center(
child: Container(
child: ListView.builder(
itemCount: user.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(10),
height: 50,
width: MediaQuery.of(context).size.width * 0.8,
color: Colors.yellowAccent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
user[index],
),
Positioned(
child: IconButton(
icon: _iconControl(like),
onPressed: () {
if (like == false) {
setState(() {
like = true;
_iconControl(like);
});
} else {
setState(() {
like = false;
_iconControl(like);
});
}
},
),
),
],
),
);
},
),
)),
);
}
_iconControl(bool like) {
if (like == false) {
return Icon(Icons.favorite_border);
} else {
return Icon(
Icons.favorite,
color: Colors.red,
);
}
}
}
I also try with using parameter but Its failed Like that :
child: IconButton(
icon: _iconControl(true),
onPressed: () {
if (false) {
setState(() {
_iconControl(true);
});
} else {
setState(() {
_iconControl(false);
});
}
},
),
Can you help me Please. Thanks in advance
You can create a modal class to manage the selection of your list
Just create a modal class and add a boolean variable to maintaining selection using. that boolean variable
SAMPLE CODE
import 'package:flutter/material.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: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool like;
List<Modal> userList = List<Modal>();
#override
void initState() {
userList.add(Modal(name: 'Dipto', isSelected: false));
userList.add(Modal(name: 'Dipankar', isSelected: false));
userList.add(Modal(name: 'Sajib', isSelected: false));
userList.add(Modal(name: 'Shanto', isSelected: false));
userList.add(Modal(name: 'Pranto', isSelected: false));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Demu'),
),
body: Center(
child: Container(
child: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(10),
height: 50,
width: MediaQuery
.of(context)
.size
.width * 0.8,
color: Colors.yellowAccent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
userList[index].name,
),
Positioned(
child: IconButton(
icon: _iconControl( userList[index].isSelected),
onPressed: () {
setState(() {
userList.forEach((element) {
element.isSelected = false;
});
userList[index].isSelected = true;
});
},
),
),
],
),
);
},
),
)),
);
}
_iconControl(bool like) {
if (like == false) {
return Icon(Icons.favorite_border);
} else {
return Icon(
Icons.favorite,
color: Colors.red,
);
}
}
}
class Modal {
String name;
bool isSelected;
Modal({this.name, this.isSelected = false});
}

List for a class clears out after making new widget

Im trying to learn flutter, but i have stumbled upon a problem i can't solve. I have a class MyApp/MyAppState that has a list of widgets (ovelser), that is used in a listVeiw.builder.
import './barbutton.dart';
import './ovelser.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
List<Widget> ovelser = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("progresjon"),
backgroundColor: Colors.blue,
actions: <Widget>[AddButton(nameOvelse)],
),
body: ListView.builder(
itemCount: ovelser.length,
itemBuilder: (context, index) {
final Widget ovelse = ovelser[index]; // lagrer bare ovelse objektet
return Dismissible(
// dismissible gjør det mulig å slette ting i listView
key: UniqueKey(),
onDismissed: (direction) {
//hva som skjer når man skal slette
setState(() {
ovelser.removeAt(index);
});
},
background: Container(
color: Colors.red,
),
//child er hva som skal være objektet som kan slettes
child: ovelse,
);
},
),
);
}
void addOvelse(String name) {
setState(() {
ovelser.add(Ovelser(name));
});
print(ovelser.length);
}
nameOvelse(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("new activity"),
content: TextField(
controller: custumcontroller,
),
actions: <Widget>[
FlatButton(
child: Text("create"),
onPressed: () {
String activityName = " " + custumcontroller.text;
addOvelse(activityName);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the list ovelser takes in Ovelser objects. these objects have a class that has a list that takes in integers (progresjonsList) that i can add to via an AlertDialog.
Code for the class with progresjonList in int:
import './ovleseraddbutton.dart';
class Ovelser extends StatefulWidget {
final String name;
Ovelser(this.name);
#override
OvelserState createState() => OvelserState();
}
class OvelserState extends State<Ovelser> {
List<int> progresjonList = [];
#override
Widget build(BuildContext context) {
return Container(
height: 80,
width: double.infinity,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
top: BorderSide(width: 0.5, color: Colors.grey),
bottom: BorderSide(width: 0.5, color: Colors.grey),
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Container(
child: Text(widget.name,
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 20,
fontWeight: FontWeight.bold)),
)),
OvelserAddbutton(addvalue)
]),
);
}
void insertValue(int value) {
setState(() {
this.progresjonList.add(value);
});
}
addvalue(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("add new value"),
content: TextField(
controller: custumcontroller,
keyboardType: TextInputType.number,
),
actions: <Widget>[
FlatButton(
child: Text("add"),
onPressed: () {
String stringnumber = custumcontroller.text;
int number = int.parse(stringnumber);
insertValue(number);
print(number);
print(progresjonList.length);
print(this.progresjonList);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the problem is every time i create a new widget in ovelser (the list that is used in ListView) the lists with integers (progresjonList) clears out so they are empty and dont retain the values previously added by the AlertDialog. I dont understand how i can keep that from happening, so that i keep the integers added. Can anyone help me? thank you in advance:)
there are tow other small files that only have icon widgets in them that i dont think are the problem, but if you need them here they are:)
class AddButton extends StatelessWidget {
final Function setInFunction;
AddButton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
import 'package:flutter/material.dart';
class OvelserAddbutton extends StatelessWidget {
final Function setInFunction;
OvelserAddbutton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
```
progessjonList is local to Ovelser class. You need to pass overserList to Ovelser class.
class Ovelser extends StatefulWidget {
final String name;
final List<int> list;
Ovelser(this.name, this.list);
#override
OvelserState createState() => OvelserState();
}
Then when you want to add to the list in OvelserState just use
widget.list.add(/*add int here*/);
Which I see is in your insertValue function
void insertValue(int value) {
setState(() {
widget.list.add(value);
});
}
The list you pass in will be a reference to the ovelser list from the original class.

Flutter - setState is not Updating inner Custom Stateful widget

I have created a Custom Segments widget which creates Multiple TABS according to List. I am updating selectionsList from homepage.dart but still, my segments are not updating runtime according to changed selectionsList
Segments.dart (Custom SegmentWidget which creates Cupertino tabs)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SegmentsWidget extends StatefulWidget {
#override
_SegmentsWidgetState createState() => _SegmentsWidgetState();
final List selectionsList;
final ValueChanged<int> onSelectTab;
final VoidCallback onTap;
final int selectedValue;
SegmentsWidget(
{this.selectionsList, this.onSelectTab, this.onTap, this.selectedValue});
}
class _SegmentsWidgetState extends State<SegmentsWidget> {
Map<int, Widget> tabWidget = Map<int, Widget>();
int selectedTab = 0;
#override
void initState() {
super.initState();
print("INit State ${widget.selectionsList}");
setState(() {
widget.selectionsList.asMap().forEach((index, value) {
tabWidget.addAll({
index: Container(
height: 40,
child: Center(
child: Text(
widget.selectionsList[index],
style: TextStyle(fontFamily: 'Exo2', fontSize: 12.0),
),
))
});
});
});
}
#override
void didUpdateWidget(SegmentsWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("Did update");
}
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
Expanded(
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: CupertinoSegmentedControl<int>(
padding: EdgeInsets.symmetric(vertical: 8),
children: tabWidget,
onValueChanged: (int index) {
setState(() {
selectedTab = index;
});
widget.onSelectTab(index);
},
groupValue: widget.selectedValue ?? selectedTab,
),
),
)
],
),
);
}
}
HomePage.dart
From Home Page, I am updating selection array but still my segments are not updating according to selectionList.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<String> selection;
int selectedTab = -1;
#override
void initState() {
super.initState();
selection = ["A", "B"];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Dynamic Segments")),
body: Container(
child: Column(
children: <Widget>[
SegmentsWidget(
selectionsList: selection,
onSelectTab: (selectTab) {
setState(() {
selectedTab = selectTab;
});
},
),
RaisedButton(child: Text("AB"),onPressed: (){
setState(() {
selection = ["A", "B"];
});
}),
RaisedButton(child: Text("ABC"),onPressed: (){
setState(() {
selection = ["A", "B", "C"];
});
}),
RaisedButton(child: Text("ABCD"),onPressed: (){
setState(() {
selection = ["A", "B", "C","D"];
});
})
],
),
),
);
}
}
I assume that initState of Segment Widget called once only. I even tried in didUpdateWidget but still not getting updated tabs.
Issue: How to update tabWidgets which is mentioned in my custom widget from another stateful widget?
I change some parts of code.
Instead of calling your code (that need to be called again on setState) in initState() function, call your code inside the widget with your own method.
see getTabChilds() function below of code.
class _SegmentsWidgetState extends State<SegmentsWidget> {
Map<int, Widget> tabWidget = Map<int, Widget>();
int selectedTab = 0;
#override
void initState() {
super.initState();
print("INit State ${widget.selectionsList}");
}
#override
void didUpdateWidget(SegmentsWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("Did update");
}
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
Expanded(
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: CupertinoSegmentedControl<int>(
padding: EdgeInsets.symmetric(vertical: 8),
children: getTabChilds(),
onValueChanged: (int index) {
setState(() {
selectedTab = index;
});
widget.onSelectTab(index);
},
groupValue: widget.selectedValue ?? selectedTab,
),
),
)
],
),
);
}
Map<int, Widget> getTabChilds() {
tabWidget = Map<int, Widget>();
widget.selectionsList.asMap().forEach((index, value) {
tabWidget.addAll({
index: Container(
height: 40,
child: Center(
child: Text(
widget.selectionsList[index],
style: TextStyle(fontFamily: 'Exo2', fontSize: 12.0),
),
))
});
});
return tabWidget;
}
}
It's tested and works fine.