I have a flutter grid view with multiple selection.
Each time I select an item, it goes inside the SelectedList and i can see a blue tick on each element.
But each time I add a new element, I update the the list and the Consumer receive the notification, I can see the new elements but I lost all the previous selected item.
Only the GridItemCustom is impacted for the CustomExercises.
Does someone has an idea, on how to keep the previous selected elements?
it look like that once the new list is updated, i have to check if the image has been selected or not..
In the video, I select 'Superman' and then add 'Test145', then I lost the selected item 'Superman'...
Future<void> updateOnceCustomExercisesList() async {
return this._memoizer.runOnce(() async {
List<ExerciseItem> newList = await dbHelper!
.findCustomExercises(widget.status == "cooldown" ? true : false);
exerciseLoader.updateList(newList); -> does nofify ExerciseLoader Consumer
});
}
Text('Custom Exercises'),
FutureBuilder(
future: updateOnceCustomExercisesList(),
builder:
(BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.hasError) {
print("ERROR\n");
}
switch (snapshot.connectionState) {
case ConnectionState.done:
return Container();
default:
return buildLoadingScreen();
}
},
),
Consumer<ExerciseLoader>(
builder: (context, customExercises, child) =>
GridView.builder(
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount:
customExercises.getCustomExercises().length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 0.56,
crossAxisSpacing: 2,
mainAxisSpacing: 2),
itemBuilder: (context, index) {
return GridItemCustom(
item: customExercises
.getCustomExercises()
.elementAt(index),
isSelected: (bool value) {
setState(() {
if (value) {
widget.selectedList.add(customExercises
.getCustomExercises()
.elementAt(index));
} else {
widget.selectedList.remove(customExercises
.getCustomExercises()
.elementAt(index));
}
});
print("$index : $value");
},
key: Key(customExercises
.getCustomExercises()
.elementAt(index)
.uniqueKey
.toString()));
}),
),
My GridCustomItem is like:
class GridItemCustom extends StatefulWidget {
final Key key;
final ExerciseItem item;
final ValueChanged<bool> isSelected;
GridItemCustom(
{required this.item, required this.isSelected, required this.key});
String get2FirstLetters(String str) {
String initial = "";
List<String> words = str.split(" ");
for (int i = 0; i < words.length; i++) {
initial += words[i].substring(0, 1);
}
return initial.toUpperCase();
}
#override
_GridItemCustomState createState() => _GridItemCustomState();
}
class _GridItemCustomState extends State<GridItemCustom> {
bool isSelected = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
isSelected = !isSelected;
widget.isSelected(isSelected);
});
},
child: Column(
children: <Widget>[
Stack(alignment: Alignment.bottomRight, children: <Widget>[
CircleAvatar(
backgroundColor: Colors.black.withOpacity(isSelected ? 0.9 : 0),
child: Text(widget.get2FirstLetters(widget.item.title)),
),
isSelected
? Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Icon(
Icons.check_circle,
color: Colors.blue,
)),
)
: Container(),
]),
SizedBox(height: 10),
Text(
widget.item.title,
style: TextStyle(
color: Colors.orange,
fontFamily: 'LibreBaskerville',
fontSize: 10),
),
//: Container()
],
),
);
}
}
Thanks for your time
Related
Trying to display cover image based on bookList which is RxList from getX but after hot reload it displays that particular book only. Need to load it instantly as soon as the page load.
Tried setstate too but UI is not updating the first time. Please help. Thank you.
Below is the screenshot images not getting displayed first time but after hot restart.
Here images problem:
HorizontalGrid Widget which based on horizontalGridTitle open the specific book
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:matab/constants/constants.dart';
import 'package:matab/controllers/cart_book_controller.dart';
import 'package:matab/models/book.dart';
import 'package:matab/ui/pages/styles.dart';
import '../../../controllers/book_controller.dart';
import '../../general_widgets/book_magazine_tapbar.dart';
import '../../general_widgets/my_network_image.dart';
import '../item_details/book_details_page.dart';
class HorizontalGrid extends StatefulWidget {
final String horizontalGridTitle;
const HorizontalGrid({Key? key, required this.horizontalGridTitle})
: super(key: key);
#override
State<HorizontalGrid> createState() => _HorizontalGridState();
}
class _HorizontalGridState extends State<HorizontalGrid> {
late RxList<dynamic> bookList;
final BookController bookController = Get.find(tag: 'bookController');
final CartBookController cartBookController =
Get.find(tag: 'cartBookController');
#override
void initState() {
bookList = bookController.getSpecifiedBooks(widget.horizontalGridTitle);
super.initState();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20.0, 20, 20, 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.horizontalGridTitle.tr,
style: TextStyle(
color: secondaryColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () {
Get.to(() => BookMagazineTapbar(
titleText: widget.horizontalGridTitle,
));
},
child: Row(
children: [
Text(
"seeAll".tr,
style: TextStyle(
color: mainColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Icon(Icons.arrow_forward, color: mainColor)
],
),
)
],
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 30, 20, 10),
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 140,
child: GridView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
),
itemCount: bookList.length,
itemBuilder: (context, index) {
return SizedBox(
child: GestureDetector(
onTap: () {
bookList[index].coverImage;
Get.to(BookDetailsPage(
type: Constants.bookConst,
index: index,
cartButtonText: 'addToCart'.tr,
));
},
child: Obx(
() => CachedNetworkImage(
key: widget.key,
imageUrl: bookList[index].coverImage,
),
)));
},
),
)
],
)),
],
),
);
}
}
This is called based on
getSpecifiedBooks(String title) {
// debugPrint("requested book is $title");
if (title == Constants.recommendedConst) {
debugPrint("recommended requested");
return recommendedBooksList;
} else if (title == Constants.latestByMatabConst) {
return latestBooksList;
} else if (title == Constants.onSaleConst) {
return onSaleList;
} else if (title == Constants.favoritesConst) {
return favoriteBookList;
} else if (title == Constants.allItemsConst) {
return bookList;
} else if (title == Constants.searchConst) {
return bookList;
} else {
return [];
}
}
Afterwards data coming from firebase perfectly in latestBooks:
RxList<dynamic> latestBooks = await databaseService.getLatestBooks();
Future<RxList> getLatestBooks() async {
RxList books = [].obs;
RxInt itemCount = 1.obs;
Query<Map<String, dynamic>> booksQuery = firestoreInstance
.collection('books')
.orderBy('createdAt', descending: true)
.limit(10);
await booksQuery.get().then(
(value) {
for (var doc in value.docs) {
Book bookItem = Book(
author: doc['author'],
title: doc['title'],
price: doc['price'].toDouble(),
coverImage: doc['coverImage'],
bookID: doc.id,
ratings: doc['ratings'].toDouble(),
description: doc['description'],
discountPercentage: doc['discountPercentage'],
isLiked: false,
itemCount: itemCount,
);
books.add(bookItem);
}
},
);
return books;
}
Finally latestBooks is assigned to latestBooksList:
final latestBooksList = [].obs;
latestBooksList.assignAll(latestBooks);
So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?
Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
in debug I see that the data is saved, but the UI is not updated with the data. strange thing is that if I press CMD S to update the simulator, the data pops out… so the data saves it for me. but i can't understand why i don't update the UI
class _GiornoSettimanaleState extends State<GiornoSettimanale> {
List<AperturaWrapper> aperture = [];
bool _isClosed = false;
Future _selectTimeApertura(BuildContext context, {int i = 0}) async {
final TimeOfDay? _pickedTime =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (_pickedTime != null) {
setState(() {
widget.orari[i].apertura = '${_pickedTime.hour}:${_pickedTime.minute}';
if (i > 0) aperture[i - 1].orario.apertura = widget.orari[i].apertura;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
AutoSizeText(
widget.giornoSettimanale!.toUpperCase(),
style: TextStyle(
fontWeight: Fonts.bold,
color: AppColors().black,
),
),
Checkbox(
value: _isClosed,
onChanged: (value) {
if (value != null) {
setState(() {
_isClosed = value;
aperture.forEach((element) => element.isClosed = value);
});
}
},
),
AutoSizeText('SHOPS_CLOSE_ALL'.tr),
],
),
AperturaWidget(
onTapApertura: _isClosed ? null : () => _selectTimeApertura(context),
onTapChiusura: _isClosed ? null : () => _selectTimeChiusura(context),
valoreApertura: widget.orari[0].apertura,
valoreChiusura: widget.orari[0].chiusura,
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView.separated(
itemCount: aperture.length,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, i) => aperture[i],
separatorBuilder: (context, index) {
return const SizedBox(height: 5);
},
),
),
MaterialButton(
onPressed: () {
final Orario nuovoOrario = Orario();
widget.orari.add(nuovoOrario);
final lastIndex = widget.orari.length - 1;
final apertura = AperturaWrapper(
key: UniqueKey(),
onTapApertura: () => _selectTimeApertura(context, i: lastIndex),
onTapChiusura: () => _selectTimeChiusura(context, i: lastIndex),
orario: widget.orari[lastIndex],
);
apertura.onRemove = () => _removeApertura(nuovoOrario, apertura);
setState(() {
aperture.add(apertura);
});
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.add),
SizedBox(
child: AutoSizeText(
'SHOP_ADD'.tr,
),
),
],
),
),
],
);
}
UPDATE the code with the widget build where i call ListView generate
class AperturaWrapper extends StatefulWidget {
AperturaWrapper({
required this.onTapApertura,
required this.onTapChiusura,
required this.orario,
Key? key,
this.onRemove,
this.isClosed = false,
}) : super(key: key);
VoidCallback? onRemove;
VoidCallback onTapApertura;
VoidCallback onTapChiusura;
Orario orario
bool? isClosed;
#override
_AperturaWrapperState createState() => _AperturaWrapperState();
}
class _AperturaWrapperState extends State<AperturaWrapper> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: AperturaWidget(
onTapApertura: widget.onTapApertura,
onTapChiusura: widget.onTapChiusura,
valoreApertura: widget.orario.apertura,
valoreChiusura: widget.orario.chiusura,
),
),
IconButton(
onPressed: widget.onRemove,
icon: const Icon(Icons.remove),
),
],
);
}
}
UPDATE WITH AperturaWidget class, class that is used by my list, which does not update the ui
the first portion, that is [0], the ui is always updated, while the rest is not, although in debug I see that the data is saved.the ui is not updated only for the new part of the list that I create, while for [0] it is updated immediately
I am implementation the list data from API to show in list view. There I am using Streambuiler and pull to refresh library for scrolling changes. I set the new data from API to local list inside StreamBuilder. Every time when I made setState for state changes. The StreamBuilder rebuild and setting data again. Then my list was duplicated, I do not know yet what's wrong with my code. I am developing the App with flutter recently. Please check my code what is wrong in there. 🙏🙏🙏
class OrderListScreen extends StatefulWidget {
#override
_OrderListScreenState createState() => _OrderListScreenState();
}
class _OrderListScreenState extends State<OrderListScreen> {
String showingFromDate, showingToDate;
OrderBloc _orderBloc;
OrderListOb _orderListOb = OrderListOb();
int paginationPage = 0;
DateTime fromDate, toDate;
List<Orders> _orderList = [];
var _refreshController = RefreshController();
#override
void initState() {
super.initState();
_orderBloc = OrderBloc();
initDate();
fetchAPI();
}
#override
void dispose() {
super.dispose();
_orderBloc.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: defaultAppBar("Order List Screen", showBackArrow: false),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
textView("From Date", isBold: true),
textView("To Date", isBold: true),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ActionChip(
padding: EdgeInsets.all(4.0),
avatar: Icon(
Icons.calendar_today_sharp,
color: Colors.white,
size: 18,
),
backgroundColor: PRIMARY_COLOR,
label: textView(showingFromDate, textColor: Colors.white),
onPressed: () {
_selectDate(context, isFromDate: true);
},
),
ActionChip(
padding: EdgeInsets.all(4.0),
avatar: Icon(
Icons.calendar_today_sharp,
color: Colors.white,
size: 18,
),
backgroundColor: PRIMARY_COLOR,
label: textView(showingToDate, textColor: Colors.white),
onPressed: () {
_selectDate(context, isFromDate: false);
},
),
],
),
StreamBuilder(
stream: _orderBloc.orderListStream(),
initialData:
BaseResponse(data: null, message: MsgState.loading),
builder: (BuildContext context, AsyncSnapshot snapshot) {
BaseResponse ob = snapshot.data;
if (ob.message == MsgState.loading) {
return Center(
child: Container(
child: CircularProgressIndicator(),
),
);
} else if (ob.message == MsgState.data ) {
_orderListOb = OrderListOb();
_orderListOb = ob.data;
_orderList.addAll(_orderListOb.result.orders);
return buildListView();
} else {
return handleErrorWidget(ob.data);
}
},
),
],
),
));
}
Widget buildListView() {
return Container(
height: 450,
child: SmartRefresher(
enablePullUp: _orderListOb.result.orders.length > 9,
enablePullDown: true,
onRefresh: () {
print("Pull To Refresh");
},
onLoading: () {
paginationPage = _orderListOb.result.pagination.nextId;
fetchAPI(); //Do pagination
},
controller: _refreshController,
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: _orderList.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
context.navigateName("order_detail", data: {
'isAcceptOrder': true,
'order_id': _orderList[index].id
});
},
child: orderItemWidget(_orderList[index],
isAcceptedOrder: false));
}),
),
);
}
void fetchAPI() {
_orderBloc.getOrderList(
serviceId: ServiceTypes.AIRCON_SERVICE.value,
fromDate: DateUtil.requestDateFormat(fromDate.toString()),
toDate: DateUtil.requestDateFormat(toDate.toString()),
page: paginationPage);
}
void initDate() {
showingFromDate = DateUtil.covertDate(DateUtil.getCurrentDate().toString());
showingToDate = DateUtil.covertDate(DateUtil.getCurrentDate().toString());
fromDate = DateUtil.getCurrentDate();
toDate = DateUtil.getCurrentDate();
}
void resetData() {
print("Clear");
paginationPage = 0;
_orderList.clear();
}
_selectDate(BuildContext context, {bool isFromDate = true}) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: isFromDate ? fromDate : toDate, // Refer step 1
firstDate: DateTime(2000),
lastDate: DateTime(2025),
);
if (picked != null)
setState(() {
if (isFromDate) {
showingFromDate = DateUtil.covertDate(picked.toString());
fromDate = picked;
} else {
showingToDate = DateUtil.covertDate(picked.toString());
toDate = picked;
}
resetData();
fetchAPI();
});
}
}
The problem is that you're doing _orderList.addAll(_orderListOb.result.orders); . Instead, you should clean the list before or just _orderList=_orderListOb.result.orders;
Check for existence the value before adding to list, if list doesnt contain the value then add like this:
if(!_orderList.contains(_orderListOb.result.orders))
_orderList.addAll(_orderListOb.result.orders);
I hope it will work!
I've a stateful widget called Emotion that displays an icon and text underneath it , and the icon turns green and the text is displayed after the icon has been tapped.
I also have a listview displaying 'Emotions' in my screen Container as follows :
Expanded(
child: ListView.builder(
itemExtent: 100,
itemCount: moodIcons.length,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Emotions(
icon: moodIcons[index],
textIcon: textIcons[index],
);
}),
)
class Emotions extends StatefulWidget {
final IconData icon;
final String textIcon;
const Emotions({Key key, this.icon, this.textIcon}) : super(key: key);
#override
_EmotionsState createState() => _EmotionsState();
}
class _EmotionsState extends State<Emotions> {
bool _isPressed = false;
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 350),
curve: Curves.easeIn,
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
_isPressed = !_isPressed;
});
},
child: Icon(
widget.icon,
size: 40,
color: _isPressed ? Colors.green : Colors.black,
),
),
Text(
widget.textIcon,
style: TextStyle(
color: _isPressed
? Colors.black.withOpacity(0.7)
: Colors.black.withOpacity(0.0)),
),
],
),
);
}
}
I've used the following 2 lists :
List<IconData> moodIcons = <IconData>[
Icons.mood_bad,
Icons.sentiment_very_dissatisfied,
Icons.sentiment_dissatisfied,
Icons.sentiment_satisfied,
Icons.sentiment_very_satisfied
];
List<String> textIcons = <String>[
"bad mood",
"very sad",
"sad",
"happy",
"very happy"
];
How can I make this list of Emotion widgets select only one item at a time?
If you are looking for an answer embedded on the flutter framework; I do not know any related to the ListView or any of it's constructors. The ListView Widget is made to display the children passed as parameter and nothing more like changing the state inside those elements.
My suggestion to you is have the same list of elements as objects instead of just plain strings. In those objects you could have the String, the icon and a boolean atributte that describes if the item is selected or not. On tap, you change the list to update the respective items on the boolean atribute and if you have that list on the state it will refresh the list and show only one item selected.
Object:
class MyObject {
IconData icon;
bool selected;
String text;
MyObject(this.icon, this.selected, this.text);
}
List:
List<MyObject> moodIcons = <MyObject>[
MyObject(Icons.mood_bad, "Bad mood", false),
MyObject(Icons.sentiment_very_dissatisfied, "Very sad", false),
];
ListView.builder:
ListView.builder(
itemCount: moodIcons.length,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
Color iconColor;
if(moodIcons.selected) {
iconColor = Colors.green;
} else {
iconColor = Colors.gray;
}
return GestureDetector(
child: Emotions(
icon: Icon(
moodIcons[index].icon,
color: iconColor,
),
textIcon: textIcons[index].text,
),
onTap: () {
for(int i = 0; i < moodIcons.length ; i++) {
if(i == index) {
moodIcons[i].selected = !moodIcons[i].selected;
} else if(moodIndex[i].selected) {
moodIcons[i].selected = false;
}
}
setState(() {});
}
);
}),