mapping values to widgets in children property, flutter - flutter

this is portion of my code:
return BlocBuilder<receivedValuesBloc, receivedValuesState>(
bloc: _valueBloc,
builder: (context, state) {
if (state is ReceivedvaluesState) {
return GridView.count(
crossAxisCount: 2,
children: // code to put List of widgets based on values in state which in my code is **state.values**
, );
}
if (state is LoadingvaluesState) {
return Positioned(
child: Container(
color: Color.fromARGB(214, 229, 231, 219),
child: Center(
child: CircularProgressIndicator(
color: Color.fromARGB(255, 71, 47, 157),
),
),
),
);
}
return GridView.count(crossAxisCount: 8, children: [
Card(
child: Container(
width: 50,
color: unavailableData,
))
]);
});
}
What I am trying to do in the children of the widget GridView.count. (commented line) is to produce the children with different colours based on the values in state. I could't get the syntax right to map each value in the state.values list to a widget list that I can put in the children to build my GridView.count.
any ideas on how to do that?

Related

StatefulBuilder's setState function not changing the color of IconButton

I'm making an Instagram clone app and I'm trying to use StatefulBuilder but the setState
function of the builder does not change the color of the Container wrapped in GestureDetector ( onTap function ).
StatefulBuilder(
builder: (context, setState) {
var color = Color.fromRGBO(38, 38, 38, 1);
return Flexible(
flex: 1,
child: GestureDetector(
onTap: () {
setState(() {
color = Color.fromRGBO(0, 149, 246, 1);
});
},
child: Container(
alignment: Alignment.center,
height: 30,
// width: double.infinity,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
color: color),
child: const Text('Follow'),
),
),
);
},
)
I want the Follow button to change it's color but I don't want to use the setState of the Stateful Widget as I don't want to rebuild the whole screen.
Am I using StatefulBuilder incorrectly or is there any other solution to my problem?
var color = Color.fromRGBO(38, 38, 38, 1);
above line should be outside of StatefulBuilder
You define your color variable inside your StatefulBuilder, and every time you call setState it resets this value and set the default value to it, you need to define it out of StatefulBuilder, like this:
var color = Color.fromRGBO(38, 38, 38, 1);
StatefulBuilder(
builder: (context, setState) {
return Flexible(
...
)

How to remove item at the top of List View in Flutter?

I have List View and I have inside each item in the list a button called "Delete item". When I press that button inside each item I want to delete only that item from the list.
But it does not delete item, it just display Toast message that I have specified.
How I can solve this?
This is the code:
Widget build(BuildContext context) {
listItems = buildVCsFromAPI(context);
return Container(
child: ListView.builder(
itemBuilder: (context, index) =>
_buildListItem(context, listItems[index], index),
itemCount: listItems.length,
physics: AlwaysScrollableScrollPhysics()),
);
}
Widget _buildListItem(
BuildContext context, _VerifiableCredentialListItem cert, int index) {
return GestureDetector(
child: AnimatedAlign(
curve: Curves.ease,
duration: Duration(milliseconds: 500),
heightFactor: selectedPosition == index ? factorMax : factorMin,
alignment: Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)), //here
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
offset: Offset(0, -1),
blurRadius: 10.0)
]),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
HeadingRow(title: cert.fullTitle, appIcon: cert.appIcon),
displayListItem(index, selectedPosition, cert)
],
),
),
),
}
Column displayListItem(
int index, int selectedIndex, _VerifiableCredentialListItem cert) {
CredentialListGroupType groupType = cert.groupType;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: UIConstants.s2,
),
buildAnotherWidget(),
SizedBox(
height: UIConstants.s3,
),
buildDeleteAndExportButtons(),
],
);
}
Column buildDeleteAndExportButtons() {
return Column(
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: UIConstants.s1, horizontal: UIConstants.s2),
child: Row(
children: [
Expanded(
flex: 1,
child: BlueButtonWithIcon(
text: 'Delete item',
icon: 'assets/icons/delete-icon.svg',
onPressed: () {
setState(() {
AppToaster.pop(ToasterType.info, "Delete");
listItems.removeAt(0);
});
},
),
),
SizedBox(width: UIConstants.s1),
Expanded(
flex: 1,
child: BlueButtonWithIcon(
text: 'Export',
icon: 'assets/icons/export.svg',
onPressed: null,
),
)
],
),
),
SizedBox(height: UIConstants.s1)
],
);
}
Calling setState doesn't mean that flutter would actually full repaint the screen it means that it will check your widget tree with the last rendered widget tree and it will paint only the differences and it first compares widgets type and then widget keys to find that there is a difference between the current widget and the previous one and because of this when you remove an item from your list of items flutter checks your returned widgets to the currently rendered widget it doesn't found any difference and it won't repaint the screen and continues showing the last render
So for you to tell the flutter that one of the items in the listView is changed you could assign a uniqueKey key for each list item widget note that for this topic your keys should be unique to the data of that widget otherwise you will face performance issues because if your widget key is changed without any change in the representation of that widget in next time that builds method is called which could happen frequently flutter compares widgets key with the previous widgets key which is rendered to the screen and exist on the render tree and it founds that the keys are different and it repaints that widget which is a redundant operation because your widgets UI and representation are the same
For example, assign a unique id base on the index or content of your data to each data model in the listItems and use that to create a ValueKey() for the widget that is represented by that data
here is a working example of the list which when you click on the list item first list item will be removed
class ListItemDataModel {
final String id;
final Color color;
ListItemDataModel(this.id, this.color);
}
class _MyHomePageState extends State<MyHomePage> {
List<ListItemDataModel> items = [];
#override
void initState() {
super.initState();
items = [
ListItemDataModel("A", Colors.red),
ListItemDataModel("B", Colors.amber),
ListItemDataModel("C", Colors.green),
ListItemDataModel("D", Colors.lightBlueAccent),
ListItemDataModel("E", Colors.pink),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, index) {
return GestureDetector(
key: ValueKey(items[index].id),
//Tap to Remove first item from list
onTap: () {
items.removeAt(0);
setState(() {});
},
child: Container(
height: 60,
color: items[index].color,
child: Center(
child: Text(
"This is a unique item with id = ${items[index].id}"),
),
),
);
},
itemCount: items.length,
),
),
);
}
}
So,
We don't have acces to the code above.. so.. where does listItems came from?
Maybe you are retrieving the value of listItems after the init state? if so it's normal that you are retrieving always the same result..
What you should do is the following:
get listItems value from params, global vars, databse ecc
display the list
when you delete a single item you should update the original list
on state updated now the list will be loaded with updated values
If you delete an item from a list but the list is then reloaded in its original form your updates will be lost

Flutter async method keeps running even after the corresponding widget is removed

I have a list of image paths and I am using the List.generate method to display images and a cross icon to remove image from list. Upload method is called on each image and when I remove the image from the list the method still keeps running until the image is uploaded. The behavior I am expecting is when I remove the image from the list the method should also stop running. I am using a future builder to display the circular progress bar and error icons while uploading an image.
What should I be doing to make sure the future method associated to the current widget also stops when I remove the widget from the list?
This is the code where I am creating a list
List.generate(
files.length,
(index) {
var image = files[index];
return Container(
height: itemSize,
width: itemSize,
child: Stack(
children: <Widget>[
Container(
getImagePreview(image, itemSize)
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
uploadHandler(image, field),
InkWell(
onTap: () => removeFileAtIndex(index, field),
child: Container(
margin: EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.7),
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: 22,
width: 22,
child: Icon(Icons.close, size: 18, color: Colors.white),
),
),
],
),
],
),
);
},
)
This is Upload Handler method.
Widget uploadHandler(file, field) {
return FutureBuilder(
future: upload(file),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.statusCode == 201) {
return doneUpload();
} else {
logger.d(snapshot.error);
return error();
}
} else {
return uploading();
}
},
);
}
The lifecycle of the widget isn't attached to the async functions invoked by the widget.
You can check the mounted variable to check if the widget still mounted from your async function.

Flutter ListViewBuilder with 2 different types of elements (eg. profile pic on top and profile details list after that)

I'm aiming for a page that looks like this -
ListView
[Profile _ Image] {Swiper}
[SizedBox]
[Profile Detail-1 ]{Text}
[Profile Detail-2 ]{Text}
[Profile Detail-3 ]{Text}
[Profile Detail-N ] {Text}
I looked at the Flutter cookbook example of MultiList
The cookbook expects all items in the listview to implement the same class. What if this is not possible.
I have tried using index of ListViewBuilder to return Widget based on index.
Is that the right approach? Shall I be doing something completely different - like siglechildScrollView?
Thanks!
Edit1-
Current Code that I'm using -
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
this._feedBloc.loadMore();
}
return false;
},
child: ListView.builder(
padding: EdgeInsets.only(bottom: 72),
itemCount: this._postItems.length + 1,
itemBuilder: (context, index) {
if (this._postItems.length == index) {
if (this._isLoadingMore) {
return Container(
margin: EdgeInsets.all(4.0),
height: 36,
width: 36,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return Container();
}
}
if(index==0){
return WdgtProfileImage();}
else if(index==1){
return SizedBox(2.0);}
return WdgtUserPost(
model: this._postItems[index],
onPostClick: onPostClick,
);
//return postItemWidget(
// postItem: this._postItems[index], onClick: onPostClick);
}),
);
You can use a CustomScrollView instead of the normal Listview.builder. The CustomScrollView takes in a list of slivers to which you can pass/use a SliverList to build a list.
CustomScrollView(
slivers: <Widget>[
//A sliver widget that renders a normal box widget
SliverToBoxAdapter(
child: WdgtProfileImage(),
),
//A sliver list
SliverList(
//With SliverChildBuilderDelegate the items are constructed lazily
//just like in Listview.builder
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return WdgtUserPost(
model: _postItems[index],
onPostClick: onPostClick,
);
},
childCount: _postItems.length,
),
),
if (_isLoadingMore)
//your loading widget shown at the bootom of the list
SliverToBoxAdapter(
child: Container(
margin: EdgeInsets.all(4.0),
height: 36,
width: 36,
child: Center(
child: CircularProgressIndicator(),
),
),
),
],
)
Additional links to docs:
SliverList
SliverChildBuilderDelegate
SliverToBoxAdapter

SingleChildScrollView not keeping scroll position

I have a SingleChildScrollView widget in my app that contains a Column as a child.
The Column has many children and the last one in the very bottom of the scrolled screen is a StreamBuilder that I use to change a child Image
The issue is that when I tap on the image, the logic of the StreamBuilder works and the image is changed, but then the SingleChildScrollView scrolls a bit up so that the image is not visible and forces the user to scroll down again to be able to see the new loaded image.
Widget _buildScroll() => SingleChildScrollView(
child: Container(
width: 2080,
child: Column(
children: <Widget>[
_buildTopBar(),
_buildMainContent(),
SizedBox(height: 30),
Container(
child: Image.asset(
"assets/images/chart_legend.png",
width: 300,
),
),
SizedBox(height: 30),
Image.asset("assets/images/road_map.png", width: 600),
StreamBuilder<int>(
initialData: 1,
stream: _compareStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data == 1) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () => _compareSubject.add(2),
),
);
} else if (snapshot.data == 2) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare2.png"),
onTap: () => _compareSubject.add(3),
),
);
} else {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare3.png"),
onTap: () => _compareSubject.add(1),
),
);
}
}
return Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
child: Image.asset("assets/images/compare1.png"),
onTap: () {},
),
);
}),
],
),
),
);
However, even more weird is that, once I have done tap on all images, they will be showed as expected without scrolling up...meaning that, if there is the second time i tap on a image, the second time the image is replaced the scrolling up in not happening.
The problem here is that the size of your children changes and the SingleChildScrollView cannot handle that.
I think there could be two solutions that might work here:
If you know the sizes of your images before they are loaded, you should enforce it using a SizedBox. This way, the scroll position will stay the same:
SizedBox(
width: 300,
height: 120,
child: StreamBuilder<int>(...),
)
Use ensureVisible that you trigger once the stream builder is updated, which lets you control exactly where the image should be displayed.
You would need to assign a ScrollController to your SingleChildScrollView (controller parameter). Then, you also need a GlobalKey for your StreamBuilder that you want to show (key parameter).
If you have saved instances of the two to variables, you will be able to call the following once your image is loaded:
scrollController.position.ensureVisible(
globalKey.currentContext.findRenderObject(),
alignment: 0.5, // Aligns the image in the middle.
duration: const Duration(milliseconds: 120), // So it does not jump.
);