I've made some test widgets to illustrate a point that I'm having difficulty with in a much more complicated widget.
I have the following widget:
class TestListWidget extends StatefulWidget {
Widget child;
TestListWidget({Widget child}) {
this.child = child;
}
#override
State<StatefulWidget> createState() {
return TestListWidgetState();
}
}
class TestListWidgetState extends State<TestListWidget>
{
Widget child;
int buttonCount = 0;
#override initState() {
child = widget.child;
}
_clickedCountButton()
{
setState(() {
buttonCount++;
});
}
#override
Widget build(BuildContext context) {
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
child
]);
}
}
The above widget is being used inside the following widget:
class TestList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new TestListState();
}
}
class TestListState extends State<TestList> {
String _testStr = "not clicked";
_clickButton()
{
setState(() {
_testStr = "CLICKED";
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
primarySwatch: Colors.blue
),
home:Scaffold(
body : testListWidget(child: Row(children: [
Text(_testStr),
ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
]))));
}
}
The issue I'm having is when the "Click me!" button is clicked, the function is called, but the text on the screen is not updated to "CLICKED!". The "Update Count" button works as intended though.
If I make the TestListWidget a stateless widget (and remove the update count button functionality) then the "Click Me!" button works as expected. Is there any way to make a child widget rebuild when passed to a stateful widget?
Your problem is quite simple. You have two variables, one is TestListWidget.child, we'll call it stateful widget's child. The second is TestListWidgetState.child, we'll call it state's child.
You make state's child to be equal to stateful widget's child on initState, but initState only runs when you first create a state, so updating the stateful widget's child will not update the state's child because initState won't run again.
To fix this, I believe you can just completely remove state's child, and use widget.child instead:
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
widget.child
]);
Full example:
import 'package:flutter/material.dart';
void main() => runApp(TestList());
class TestListWidget extends StatefulWidget {
Widget child;
TestListWidget({required this.child});
#override
State<StatefulWidget> createState() {
return TestListWidgetState();
}
}
class TestListWidgetState extends State<TestListWidget>
{
int buttonCount = 0;
_clickedCountButton()
{
setState(() {
buttonCount++;
});
}
#override
Widget build(BuildContext context) {
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
widget.child
]);
}
}
class TestList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new TestListState();
}
}
class TestListState extends State<TestList> {
String _testStr = "not clicked";
_clickButton()
{
setState(() {
_testStr = "CLICKED";
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
primarySwatch: Colors.blue
),
home:Scaffold(
body : TestListWidget(child: Row(children: [
Text(_testStr),
ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
]))));
}
}
Related
I am trying to update the notifier value from parent widget whereas ValueListenableBuilder is defined in a child widget but the builder is not calling after changing the value.
Here is the parent widget code in which I have declared two child widgets as StatefulWidget and also declared a static object of Notifier class. I am calling the method updateMenuItemList from secondChild() widget like this HotKeysWidget.of(context)!.updateMenuItemList(currentCat!['items']); to update the list of firstChild() widget :
class HotKeysWidget extends StatefulWidget {
static HotKeysWidgetState? of(BuildContext context) =>
context.findAncestorStateOfType<HotKeysWidgetState>();
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HotKeysWidgetState();
}
}
class HotKeysWidgetState extends State<HotKeysWidget> {
static DealsNotifier appValueNotifier = DealsNotifier();
updateMenuItemList(List<Food> list) {
appValueNotifier.updateMenuList(list);
}
#override
Widget build(BuildContext context) {
return Container(child: Column(children: [
firstChild(),
secondChild(),
],
),
);
}
}
Here is my Notifier class:
class DealsNotifier {
ValueNotifier<List<Food>> dealList = ValueNotifier([]);
ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
ValueNotifier<List<Map<String,dynamic>>> categoryList = ValueNotifier([]);
void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
void updateCategoryList(List<Map<String,dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
List<Food> getDealList() {
return dealList.value;
}
List<Food> getMenuitemList() {
return menuitemList.value;
}
List<Map<String,dynamic>> getCategoryList() {
return categoryList.value;
}
}
And this is the child widget named as firstChild() in parent code. Here the ValueListenerBuilder is declared:
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: HotKeysWidgetState.appValueNotifier.menuitemList,
builder: (context, List<Food> value, widget)
{
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu:currentCat != null ? value : [],
);
},
);
}
}
class secondChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return secondChildState();
}
}
class secondChildState extends State<secondChild> {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
HotKeysWidget.of(context)!.updateMenuItemList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
Anyone help me with this issue please.
Thanks in advance
While there's still not enough code shared to fully reproduce your situation, I can offer some suggestions.
The state portion of StatefulWidgets are private by default for a reason. You shouldn't make them public just to access variables that are inside there are several other to access outside classes within widgets.
So anytime you're doing something like this
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
...
Just stick to the default syntax of a StatefulWidget and also classes should be in UpperCamelCase with the first letter capitalized.
class FirstChild extends StatefulWidget {
const FirstChild({Key? key}) : super(key: key);
#override
State<FirstChild> createState() => _FirstChildState();
}
class _FirstChildState extends State<FirstChild> {
#override
Widget build(BuildContext context) {
...
If you find yourself wanting to edit this default syntax its a clue that you need to find a better way to achieve whatever it is you're trying to do. If you're need to access a function that is declared in a Widget from outside that Widget, then that function should be declared somewhere else.
All that being said, unless you need setState, initState or another of the lifecycle functions, then you don't need a StatefulWidget to begin with. All those classes can be Stateless.
An easy way to make that DealsNotifier class globally accessible without a full on state management solution is to make it a static class.
class DealsNotifier {
static ValueNotifier<List<Food>> dealList = ValueNotifier([]);
static ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
static ValueNotifier<List<Map<String, dynamic>>> categoryList =
ValueNotifier([]);
static void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
static void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
static void updateCategoryList(List<Map<String, dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
static List<Food> getDealList() {
return dealList.value;
}
static List<Food> getMenuitemList() {
return menuitemList.value;
}
static List<Map<String, dynamic>> getCategoryList() {
return categoryList.value;
}
}
Then when you need to pass in the valueListenable you access via DealsNotifier.menuitemlist and its always the same instance.
return ValueListenableBuilder(
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
Here's the Stateless version of all those classes and wherever you need the UI update you can use ValueListenableBuilder and pass in DealsNotifier.whicheverVariableYouWantToListenTo in the valueListenable. Then call whichever relevant method from the DealsNotifier class ie. DealsNotifier.updateMenuList([]).
And you didn't share your HotkeysMenuItemsWidget but if that's where you're looking to see the change in the UI, then that is where the ValueListenableBuilder should be. Its currently too high up in the widget tree all it needs to do is re-render the list in that Widget, you don't need/want an entire re-build of the HotkeysMenuItemsWidget from a parent widget.
class FirstChild extends StatelessWidget {
const FirstChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder( // this should be inside HotkeysMenuItemsWidget
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
}
}
class SecondChild extends StatelessWidget {
const SecondChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
DealsNotifier.updateMenuList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
class HotKeysWidget extends StatelessWidget {
const HotKeysWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
FirstChild(),
SecondChild(),
],
),
);
}
}
I have a Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}
is there a best practice for this? (Im using this Todo example since its easier to explain my problem here)
TodoOverviewPage (Shows all todos)
TodoAddPage (Page to add todos)
Each page has an own Bloc.
Steps:
From the TodoOverviewPage I navigate wuth pushNamed to TodoAddPage.
In TodoAddPage I add several Todos.
Using the Navigation Back Button to go back to TodoOverviewPage
Question: How should I inform TodoOverviewPage that there are new Todos?
My approaches which Im not sure if this is the right way.
Solutions:
Overwriting the Back Button in TodoAddPage. To add a "refresh=true" property.
Adding the Bloc from TodoOverviewPage to TodoAddPage. And setting the State to something that the TodoOverviewPage will reload todos after building.
Thank you for reading.
EDIT1:
Added my temporary solution till I find something which satisfies me more.
You can achieve by different way
InheritedWidget
ValueCallback in TodoAddPage
For Example:
class Item {
String reference;
Item(this.reference);
}
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
#override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;
}
}
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
#override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
#override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
class MyTree extends StatefulWidget {
#override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
#override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
Temporary solution:
Each (root) Page which has a Bloc now always reloads when build.
The Bloc takes care for caching.
Widget build(BuildContext context) {
final PageBloc pBloc = BlocProvider.of<PageBloc >(context);
bool isNewBuild = true;
return Scaffold(
...
body: BlocBuilder<PageBlocEvent, PageBlocState>(
if (isNewBuild) {
pBloc.dispatch(PageBlocEvent(PageBlocEventType.GETALL));
isNewBuild = false;
return CircularProgressIndicator();
} else {
// Draw data
...
...
}
Good day. I've watched a video about Flutter's InheritedModel and got interested on it. Unfortunately, I can't seems to make it work properly.
Summary: Need help how to properly implement InheritedModel.
Expected Code Output: Widget CountText should not be updated when updating count parameter in CountModel.
Actual Code Output: CountText still updates (I think this is due to that the parent widget is a StatefulWidget)
Details
I am trying to implement a Counter app using InheritedModel. Code below is my code
import 'package:flutter/material.dart';
class CountModel extends InheritedModel<String> {
final int count;
CountModel({ this.count, child }) : super(child: child);
#override
bool updateShouldNotify(CountModel oldWidget) {
if (oldWidget.count != count) {
return true;
}
return false;
}
#override
bool updateShouldNotifyDependent(InheritedModel<String> oldWidget, Set<String> dependencies) {
if (dependencies.contains('counter')) {
return true;
}
return false;
}
static CountModel of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<CountModel>(context, aspect: aspect);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter',
theme: Theme.of(context),
home: Counter(),
);
}
}
class Counter extends StatefulWidget {
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// title: Text("Counter"),
),
body: CountModel(
count: count,
child: CounterText()
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
++count;
});
},
child: Icon(Icons.add),
),
);
}
}
class CounterText extends StatelessWidget {
#override
Widget build(BuildContext context) {
CountModel model = CountModel.of(context, 'test');
return Text('Count: ${model.count}');
}
}
I have a CountModel as InheritedModel and a CountText widget which consumes the data from the CountModel. As you can see in the implementation of the CountText, it pass test when getting the CountModel. In my understanding, it should not be updated when the count value is updated in the CountModel. Unfortunately, this does not happen.
In short, you should use const.
Add const to CounterText constructor
class CounterText extends StatelessWidget {
const CounterText();
...
}
and use const when you create instance of CounterText() (const CounterText())
class CounterState extends State<Counter> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: CountModel(..., child: const CounterText()),
...
);
}
}
And voila 🎉
I have described why this is happening here in details
I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}