Flutter multiple ExpansionTile's collapses simultaneously - flutter

I have a custom Card widget with ExpansionTile as a child which displays multiple Dropdownbuttons according to data fetched from an API.
But when I use ListView.builder to build N amount of said custom widgets they all behave simultaneously, for example when I collapse the ExpansionTile all open ExpansionTiles collapse simultaneously and reset the data inside Dropdownbuttons (resetting the data expected outcome when ExpansionTile collapsed but only the collapsed ExpansionTile should reset its children Dropdownbuttons, not all open ExpansionTiles children).
Here is my builder.
var items = ["Apartment 1", "Apartment 2", "Apartment 3", "Apartment 4"];
class MapPage extends StatelessWidget {
const MapPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
key: ValueKey(items),
scrollDirection: Axis.vertical,
itemCount: items.length,
padding: const EdgeInsets.only(top: 8),
itemBuilder: (context, index) {
return MapCard(
building: items[index],
floor: 4,
key: Key(items[index].toString()),
);
}),
);
}
}
and my CustomCard
class MapCard extends StatefulWidget {
final String building;
final int floor;
const MapCard({super.key, required this.building, required this.floor});
#override
State<MapCard> createState() => _MapCardState();
}
class _MapCardState extends State<MapCard> {
#override
Widget build(BuildContext context) {
PageStorageKey key = PageStorageKey('${widget.key}');
return Center(
child: Consumer<MapCardViewModel>(
builder: (context, mapCardViewModel, child) => Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ExpansionTile(
key: key,
onExpansionChanged: (changed) {
if (!changed) {
mapCardViewModel.setAreaVisibility(false);
mapCardViewModel.setButtonVisibility(false);
mapCardViewModel.setIsFloorChosen(false);
mapCardViewModel.setAreaVisibility(false);
mapCardViewModel.area = mapCardViewModel.areas[0];
mapCardViewModel.floorNumber = mapCardViewModel.floors[0];
}
},
title: Row(
children: [
Container(
padding:
const EdgeInsets.only(top: 8, bottom: 8, right: 8),
child: Image.asset(
"assets/images/example.png",
height: 80,
width: 80,
)),
Flexible(
child: Container(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: Column(
children: [
Text("${widget.building} Apartment \n"
"Floor Count ${widget.floor} ")
],
),
),
)
],
),
children: [
const Text("Choose Floor"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: mapCardViewModel.isFloorChosen == false
? mapCardViewModel.floors[0]
: mapCardViewModel.floorNumber,
items: mapCardViewModel.floors
.map<DropdownMenuItem<int>>((int i) {
return DropdownMenuItem<int>(
value: i,
child: Text(i.toString()),
);
}).toList(),
onChanged: (int? value) {
mapCardViewModel.setFloorNumber(value!);
mapCardViewModel.setIsFloorChosen(true);
mapCardViewModel.setAreaVisibility(true);
}),
),
Visibility(
visible: mapCardViewModel.isAreaVisible,
child: Column(
children: [
const Text("Choose an Area to map"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: mapCardViewModel.isAreaChosen == false
? mapCardViewModel.areas[0]
: mapCardViewModel.area,
items: mapCardViewModel.areas
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
mapCardViewModel.setArea(value!);
mapCardViewModel.setIsAreaChosen(true);
mapCardViewModel.setButtonVisibility(true);
}),
),
],
),
),
Visibility(
visible: mapCardViewModel.isButtonsVisible,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return CustomDialog(
title: "Mapping Status",
content:
"This area hasn't been mapped yet",
page: Container(),
buttonColor: MainColors().mainBlue);
});
},
child: const Text("Show Area Map")),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MappedPage(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: MainColors().mainBlue),
child: const Text(
"Map The Area",
style: TextStyle(color: Colors.white),
))
],
),
)
],
),
)),
),
));
}
}
I tried to assign keys to each ExpansionTile and custom MapCard widgets with StatefulWidget but I couldn't solve my problem.

I remove all 3rd dependency and try to adjust your solution of MapCard widget.
var items = ["Apartment 1", "Apartment 2", "Apartment 3", "Apartment 4"];
final floors = ['Floor 1', 'Floor 2'];
final areas = ['Area 1', 'Area 2'];
class MapCard extends StatefulWidget {
final String building;
final int floor;
const MapCard({Key? key, required this.building, required this.floor}): super(key: key);
#override
State<MapCard> createState() => _MapCardState();
}
class _MapCardState extends State<MapCard> {
#override
Widget build(BuildContext context) {
PageStorageKey key = PageStorageKey('${widget.key}');
return Center(
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ExpansionTile(
key: key,
onExpansionChanged: (changed) {
if (!changed) {
print('!changed');
}
},
title: Row(
children: [
Container(
padding:
const EdgeInsets.only(top: 8, bottom: 8, right: 8),
child: Placeholder(
fallbackHeight: 80,
fallbackWidth: 80,
)),
Flexible(
child: Container(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: Column(
children: [
Text("${widget.building} Apartment \n"
"Floor Count ${widget.floor} ")
],
),
),
)
],
),
children: [
const Text("Choose Floor"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: floors.first,
items: floors
.map<DropdownMenuItem<String>>((String i) {
return DropdownMenuItem<String>(
value: i,
child: Text(i.toString()),
);
}).toList(),
onChanged: (String? value) {
// code here
}),
),
Visibility(
visible: true,
child: Column(
children: [
const Text("Choose an Area to map"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: areas.first,
items: areas
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
// code here
}),
),
],
),
),
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(content: Text('Alert dialog'),);
});
},
child: const Text("Show Area Map")),
ElevatedButton(
onPressed: () {
print('navigate');
},
child: const Text(
"Map The Area",
style: TextStyle(color: Colors.white),
))
],
),
)
],
),
)),
),
);
}
}
But I suppose your problem in the line
Consumer<MapCardViewModel>
because every time when you change it this will apply to each created card. So you have to put it above your ListView.builder and pass it as a parameter to your cards

Related

How to test this GestureDetector in Flutter with Patrol?

In my app, the widget tree is DeleteItem GestureDetector -> InvoiceTotal Widget -> ItemListWidget -> ListView -> EditInvoiceClass
I tried to use PatrolTest
await $(#InvoiceTotal).$(#DeleteItem).scrollTo().tap();
$.pumpAndSettle;
to test the DeleteItem GestureDetector but I got an Exception --
WaitUntilVisibleTimeoutException (TimeoutException after 0:00:10.000000: Finder exactly one widget with key \[\<'DeleteItem'\>\] that has ancestor(s) with key \[\<'InvoiceTotal'\>\] (ignoring all but first) (ignoring offstage widgets): GestureDetector-\[\<'DeleteItem'\>\](startBehavior: start, dependencies: \[MediaQuery, \_ScrollableScope\]) did not find any visible widgets)
I want to know why the DeleteItem GestureDetector is invisible? How can I fix the problem?
My Flutter code attached below.
// Code Segment of EditInvoice Class
return Consumer<ItemContents>(
builder: (context, value, child) {
return Consumer<DarkThemeProvider>(
builder: (context, value1, child1) {
return Expanded(
child: Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: myTheme.normalPadding),
child: const goBack(),
),
Expanded(
child: Scrollbar(
child: ListView(
padding: EdgeInsets.zero,
children: [
Padding(
padding: EdgeInsets.all(myTheme.normalPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
ItemListWidget(
key: const Key('ItemListWidget'),
context: context,
myTextTheme: myTextTheme,
myTextColor: myTextColor,
value: value,
myTheme: myTheme,
invoicesContents: vm.invoicesData(
vm,
newAddModel,
editData,
index,
),
),
vm.isEmptyInvoice
? RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "New Invoice",
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
)
: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "Edit ",
style: myTheme.textTheme(
"labelLarge", isDark),
),
TextSpan(
text: "#",
style: myTheme.textTheme(
"labelSmall", isDark),
),
TextSpan(
text: vm.selectedItem.id,
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 24.0),
child: Text(
'Bill From',
style: myTheme.textTheme("caption", isDark),
),
),
Form(
key: const Key('form'),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
// InvoiceFadeWidget(
// myTheme: myTheme,
// key: const Key('fadeWidget'),
// ),
InvoicesPageTextField(
class ItemListWidget extends StatelessWidget {
const ItemListWidget({
Key? key = const Key('ItemListWidget'),
required this.context,
required this.myTextTheme,
required this.myTextColor,
required this.value,
required this.myTheme,
required this.invoicesContents,
}) : super(key: const Key('ItemListWidget'),);
final BuildContext context;
final TextTheme myTextTheme;
final ThemeData myTextColor;
final ItemContents value;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (var i = 0; i < invoicesContents.items.length; i++)
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding * 2),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: InvoicesPageTextField(
key: const Key('ItemName'),
labelText: 'Item Name',
controller: TextEditingController(
text: invoicesContents.items[i].name,
),
onChanged: ((p0) {
invoicesContents.items[i].name = p0;
}),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: myTheme.normalPadding * 3 - 8,
child: InvoicesPageTextField(
key: const Key('Qty'),
labelText: 'Qty.',
controller: TextEditingController(
text: invoicesContents.items[i].quantity.toString(),
),
onChanged: (p0) {
int? qty = int.tryParse(p0);
if (qty != null && qty > 0) {
invoicesContents.items[i].quantity = qty;
invoicesContents.items[i].total =
// ignore: unnecessary_cast
(qty *
invoicesContents.items[i].price.toDouble());
invoicesContents.total = invoicesContents.items
.map((e) => e.total)
.reduce((a, b) => a + b);
context.read<ItemContents>().refresh();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).canvasColor,
title: Text(
'Ops! =v=',
style: myTheme.textTheme(
"bodyMedium", myTheme.darkTheme),
),
content: Text(
'Natural Number Required !',
style: myTheme.textTheme(
"displayLarge", myTheme.darkTheme),
),
actionsPadding: EdgeInsets.symmetric(
horizontal: myTheme.normalPadding / 2 - 2,
vertical: myTheme.normalPadding / 2 - 2,
),
actions: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(myTheme.normalPadding),
),
color: myTextColor.highlightColor,
),
width: myTheme.normalPadding * 4 - 7,
height: myTheme.normalPadding * 2,
child: GestureDetector(
key: const Key('EnterNaturalNum'),
behavior: HitTestBehavior.opaque,
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
child: Text(
'OK',
style: myTheme.textTheme(
"headlineMedium", myTheme.darkTheme),
),
),
),
),
],
),
);
}
},
),
),
InvoicePriceWidget(
key: const Key('InvoicePrice'),
context: context,
i: i,
invoicesContents: invoicesContents,
myTheme: myTheme),
InvoiceTotalWidget(
key: const Key('InvoiceTotal'),
myTextTheme: myTextTheme,
i: i,
context: context,
myTheme: myTheme,
invoicesContents: invoicesContents),
],
)
],
),
),
],
);
}
}
class InvoiceTotalWidget extends StatelessWidget {
const InvoiceTotalWidget({
Key? key = const Key('InvoiceTotal'),
required this.myTextTheme,
required this.i,
required this.context,
required this.myTheme,
required this.invoicesContents,
}) : super(
key: const Key('InvoiceTotal'),
);
final TextTheme myTextTheme;
final int i;
final BuildContext context;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
#override
Widget build(BuildContext context) {
return Expanded(
key: const Key('TotalWidget'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: Text(
'Total',
style: myTheme.textTheme("captionSmall", myTheme.darkTheme),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.read<ItemContents>().parseTotal(
invoicesContents.items[i].total.toDouble(),
showCurrency: false),
style: myTheme.textTheme("displayLarge", myTheme.darkTheme),
),
GestureDetector(
key: const Key('DeleteItem'),
onTap: () {
//const Key('DeleteItemOnTap');
invoicesContents.total = invoicesContents.total -
invoicesContents.items[i].quantity *
invoicesContents.items[i].price;
invoicesContents.items[i].quantity = 0;
invoicesContents.items[i].price = 0;
context.read<ItemContents>().refresh();
invoicesContents.items.removeAt(i);
context
.read<ItemContents>()
.data[context.read<ItemContents>().data.length - 1] =
invoicesContents;
context.read<ItemContents>().refresh();
},
child: Padding(
key: const Key('DeleteIcon'),
padding: EdgeInsets.only(right: myTheme.normalPadding * 0.9),
child: SvgPicture.asset(
'assets/images/icon-delete.svg',
),
),
),
],
),
],
),
);
}
}
I have tried to move the position of this button to the front of the ListView to ensure that the button has been generated. But this still does not make the button visible.

DropDownButton doesn't work after using Navigator.push

I'm trying to create a clothes E-Shop app and I need the cart items to show the list of different colors for each product, I'm using a DropDownButton to show them, the slidable library to create the cart items, and a custom bottom bar. If I tap in the cart item to go to the product view and then go back to the cart pressing the back button, the dropdownbutton stops working, but if I go to the product through the bottom bar everything works.
class CartPage extends StatefulWidget {
const CartPage({Key? key}) : super(key: key);
#override
CartPageState createState() => CartPageState();
}
class CartPageState extends State<CartPage> {
List<String> dropDownValues = cart.itemList.keys.toList();
late String dropDownValueShow;
final textController = TextEditingController();
#override
Widget build(BuildContext context) {
debugPrint('$dropDownValues');
dropDownValueShow = cart.itemList.isEmpty ? '' : dropDownValues[0];
return Scaffold(
bottomNavigationBar: CustomBottomNavBar(
1,
key: ValueKey(cart.itemList),
),
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.black,
title: const Text(
"Shopping Cart",
style: TextStyle(color: Colors.white, fontSize: 17),
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: cart.empty
? [
SizedBox(
width: 200,
height: 200,
child: Center(
child: Text(
"Empty Cart",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey.shade500,
fontSize: 20,
),
),
))
]
: [
Expanded(
child: AnimatedList(
scrollDirection: Axis.vertical,
initialItemCount: cart.itemList.length,
itemBuilder: (context, index, animation) {
return Slidable(
key: UniqueKey(),
actionPane: const SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
actions: const [],
secondaryActions: const [],
child: _cartItem(),
);
},
),
),
],
),
);
}
Widget _cartItem() {
return GestureDetector(
key: UniqueKey(),
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
const ProductView(),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
).then((_) => setState(() {}));
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.shade200,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
),
child: Row(
children: <Widget>[
Text('prod1'),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 10),
DropdownButton(
isExpanded: true,
value: dropDownValueShow,
icon: const Icon(Icons.arrow_drop_down),
items: cart.itemList.keys.toList().map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
dropDownValueShow = newValue!;
});
},
),
const SizedBox(height: 10),
],
),
),
const SizedBox(
width: 10,
),
],
),
),
);
}
}
The whole code is in https://github.com/nicoacevedor/minimal.git

Flutter || Checkbox on hover doesn't give on tap cursor permission

I am working on dropdownmenu items where in the drop-down menu item there are several checkboxes but any of the checkboxes on hover don't give on tap cursor permission.
This is a very strange thing I found out as I have already used the checkbox before but this type of error I didn't receive.
I think maybe the problem is in dropdownmenu.
I have also included the video for better understanding of my problem.
my code :-
Container(
width: 160,
//margin: const EdgeInsets.only(top: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5), color: Colors.white),
child: ListTileTheme(
contentPadding: EdgeInsets.all(0),
dense: true,
horizontalTitleGap: 0.0,
minLeadingWidth: 0,
child: ExpansionTile(
iconColor: primaryBackgroundLightGrey,
title: Text(
listOFSelectedItem.isEmpty
? "Project type"
: listOFSelectedItem[0],
style: t5O40),
children: <Widget>[
Container(
height: 10,
color: primaryBackgroundLightGrey,
),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.listOFStrings.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: [
Container(
height: 10,
),
Container(
margin: const EdgeInsets.only(bottom: 8.0),
child: _ViewItem(
item: widget.listOFStrings[index],
selected: (val) {
selectedText = val;
if (listOFSelectedItem.contains(val)) {
listOFSelectedItem.remove(val);
} else {
listOFSelectedItem.add(val);
}
widget.selectedList(listOFSelectedItem);
setState(() {});
},
itemSelected: listOFSelectedItem
.contains(widget.listOFStrings[index])),
),
],
);
},
),
],
),
),
),
class _ViewItem extends StatelessWidget {
String item;
bool itemSelected;
final Function(String) selected;
_ViewItem(
{required this.item, required this.itemSelected, required this.selected});
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Padding(
padding: EdgeInsets.only(
left: size.width * .015,
),
child: Row(
children: [
SizedBox(
height: 2,
width: 2,
child: Checkbox(
value: itemSelected,
onChanged: (val) {
selected(item);
},
hoverColor: Colors.transparent,
checkColor: Colors.white,
activeColor: Colors.grey),
),
SizedBox(
width: size.width * .010,
),
Text(item, style: t3O60),
],
),
);
}
}
You can adapt the example to your own code
dropdownBuilder: _customDropDownExample,
popupItemBuilder: _customPopupItemBuilderExample,
Widget _customDropDownExample(
BuildContext context, UserModel? item, String itemDesignation) {
if (item == null) {
return Container();
}
return Container(
child: (item.avatar == null)
? ListTile(
contentPadding: EdgeInsets.all(0),
leading: CircleAvatar(),
title: Text("No item selected"),
)
: ListTile(
contentPadding: EdgeInsets.all(0),
leading: CircleAvatar(
// this does not work - throws 404 error
// backgroundImage: NetworkImage(item.avatar ?? ''),
),
title: Text(item.name),
subtitle: Text(
item.createdAt.toString(),
),
),
);
After that
Widget _customPopupItemBuilderExample(
BuildContext context, UserModel item, bool isSelected) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 8),
decoration: !isSelected
? null
: BoxDecoration(
border: Border.all(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.circular(5),
color: Colors.white,
),
child: ListTile(
selected: isSelected,
title: Text(item.name),
subtitle: Text(item.createdAt.toString()),
leading: CircleAvatar(
// this does not work - throws 404 error
// backgroundImage: NetworkImage(item.avatar ?? ''),
),
),
);
I am using this package https://pub.dev/packages/dropdown_button2
Multiselect Dropdown with Checkboxes
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}

How to use Gesture control and change color on chip selection in Flutter

context :
I want to click on a particular services person/product and open a page where details can be shown more like a details page
Problem
I think that Gesture control should work but I'm not able to put it the right way in my code
How to change chip color on selection the data is getting filtered but i want to add 2 more things
highlight the chip that is selected
so a text message stating Not Available if there is nothing
available, rather then leaving blank and
My Code
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
int _selectedCategoryIndex = -1;
String _selectedUserType = "Maid";
PageController pageController = PageController();
List array = ["Maid", "Driver", "Engineer", "Gardener", "Pilot","carpainter", "guard", "plumber"];
List data = [
{
"name": "Sachin Rajput",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Maid", "Engineer"],
"rating": 5,
"bg": Colors.red
},
{
"name": "Sachin Tendulkar",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Gardener", "Pilot", "Engineer"],
"rating": 5,
"bg": Colors.amberAccent
},
{
"name": "Sachin Test",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["carpainter", "guard", "plumber"],
"rating": 5,
"bg": Colors.blue
}
];
List product_data = [
{
"name": "P1",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Dusting"],
"rating": 5,
"bg": Colors.red
},
{
"name": "P2",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Mopping"],
"rating": 5,
"bg": Colors.amberAccent
},
{
"name": "P3",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["cleaning"],
"rating": 5,
"bg": Colors.blue
}
];
List filteredData = [];
void onTapped(int index) {
setState(() {
_selectedIndex = index;
});
pageController.jumpToPage(index);
}
void tappedCategory(int index) {
_selectedCategoryIndex = index;
_selectedUserType = array[index];
_filterData();
}
#override
void initState() {
super.initState();
_filterData();
}
_filterData() {
print(_selectedUserType);
filteredData = data.where((element) => element["category"].contains(_selectedUserType)).toList();
print(filteredData);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: pageController,
children: [
SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Container(
// color: Colors.purple,
// margin: const EdgeInsets.only(top: 45, bottom: 15),
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Bengaluru",
style: TextStyle(
color: APP_COLOR.mainColor,
),
),
Text("R.T Nagar")
]),
),
customContainer(iconData: Icons.search,),
SizedBox(width: 10,),
customContainer(iconData: Icons.notifications,),
])),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 10,horizontal: 10),
child: Align(
alignment: Alignment.centerLeft,
child:
Text(
'Popular Services',
),
),
),
Container(
height: MediaQuery.of(context).size.height / 12,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: List<Widget>.generate(
array.length, // place the length of the array here
(int index) {
return Container(
margin: const EdgeInsets.all(2.0),
child: GestureDetector(
onTap: () {
tappedCategory(index);
},
child: Chip(label: Text(array[index])),
),
);
}).toList(),
),
),
Container(
height: MediaQuery.of(context).size.height / 6,
child: ListView.builder(
scrollDirection: Axis.horizontal,
// physics: NeverScrollableScrollPhysics(),
itemCount: filteredData.length,
itemBuilder: (context, index) {
var item = filteredData[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(item["name"].toString()),
),
),
),
);
},
// This next line does the trick.
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10,horizontal: 10),
child: Row(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Text(
'Popular Products',
),
Align(
alignment: Alignment.centerRight,
child:
Text(
'View All',
),
),
],
),
),
Container(
height: MediaQuery.of(context).size.height / 6,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: product_data.length,
itemBuilder: (context, index) {
var item = product_data[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 60),
child: Text(item["name"].toString()),
),
),
),
);
},
// This next line does the trick.
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10,horizontal: 10),
child: Row(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Text(
'Our Products',
),
Align(
alignment: Alignment.centerRight,
child:
Text(
'View All',
),
),
],
),
),
Container(
height: MediaQuery.of(context).size.height / 6,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: product_data.length,
itemBuilder: (context, index) {
var item = product_data[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 60),
child: Text(item["name"].toString()),
),
),
),
);
},
// This next line does the trick.
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 10,horizontal: 10),
child: Row(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
Text(
'New Products',
),
Align(
alignment: Alignment.centerRight,
child:
Text(
'View All',
),
),
],
),
),
Container(
height: MediaQuery.of(context).size.height / 6,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: product_data.length,
itemBuilder: (context, index) {
var item = product_data[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 60),
child: Text(item["name"].toString()),
),
),
),
);
},
),
),
],
),
),
)
]),
),
Container(color: Colors.blue,),
Container(color: Colors.white,),
Container(color: Colors.yellow,),
Container(color: Colors.blue,),
Container(color: Colors.white,),
Container(color: Colors.yellow,)
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.cleaning_services), label: 'Services'),
BottomNavigationBarItem(
icon: Icon(Icons.local_convenience_store), label: 'Store'),
BottomNavigationBarItem(
icon: Icon(Icons.account_balance_wallet), label: 'Wallet'),
BottomNavigationBarItem(
icon: Icon(Icons.bookmarks), label: 'Bookmarked'),
BottomNavigationBarItem(
icon: Icon(Icons.assessment), label: 'Current Orders'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
currentIndex: _selectedIndex,
selectedItemColor: APP_COLOR.mainColor,
unselectedItemColor: Colors.grey,
onTap: onTapped,
));
}
Widget customContainer({required IconData iconData}){
return Container(
width: 45,
height: 45,
padding: const EdgeInsets.only(left: 0, right: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: APP_COLOR.mainColor,),
child: Icon(
iconData,
color: Colors.white,
),
);
}
}
I've recently started the flutter journey so i might have asked a very basic question so it will be great if you can explain the changes as well taht you made so i can understand it better
Hope this helps
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController pageController = PageController();
int _selectedIndex = 0;
void onTapped(int index) {
setState(() {
_selectedIndex = index;
});
pageController.jumpToPage(index);
}
#override
void dispose() {
pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: pageController,
children: [
const HomePage(),
Container(
color: Colors.blue,
),
Container(
color: Colors.white,
),
Container(
color: Colors.yellow,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.white,
),
Container(
color: Colors.yellow,
)
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.cleaning_services), label: 'Services'),
BottomNavigationBarItem(
icon: Icon(Icons.local_convenience_store), label: 'Store'),
BottomNavigationBarItem(
icon: Icon(Icons.account_balance_wallet), label: 'Wallet'),
BottomNavigationBarItem(
icon: Icon(Icons.bookmarks), label: 'Bookmarked'),
BottomNavigationBarItem(
icon: Icon(Icons.assessment), label: 'Current Orders'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.red,
unselectedItemColor: Colors.grey,
onTap: onTapped,
));
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _selectedCategoryIndex = 0;
String _selectedUserType = "Maid";
List array = [
"Maid",
"Driver",
"Engineer",
"Gardener",
"Pilot",
"carpainter",
"guard",
"plumber"
];
List data = [
{
"name": "Sachin Rajput",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Maid", "Engineer"],
"rating": 5,
"bg": Colors.red
},
{
"name": "Sachin Tendulkar",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Gardener", "Pilot", "Engineer"],
"rating": 5,
"bg": Colors.amberAccent
},
{
"name": "Sachin Test",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["carpainter", "guard", "plumber"],
"rating": 5,
"bg": Colors.blue
}
];
List product_data = [
{
"name": "P1",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Dusting"],
"rating": 5,
"bg": Colors.red
},
{
"name": "P2",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["Mopping"],
"rating": 5,
"bg": Colors.amberAccent
},
{
"name": "P3",
"profilePic":
"https://lh3.googleusercontent.com/a-/AAuE7mCfQn-gP_FJZUUU4GC4aSU1km9t_e5PL6zsV-NwdA=k-s48",
"category": ["cleaning"],
"rating": 5,
"bg": Colors.blue
}
];
List filteredData = [];
void tappedCategory(int index) {
_selectedCategoryIndex = index;
_selectedUserType = array[index];
_filterData();
}
#override
void initState() {
super.initState();
_filterData();
}
#override
void dispose() {
super.dispose();
}
_filterData() {
filteredData = data
.where((element) => element["category"].contains(_selectedUserType))
.toList();
setState(() {});
}
Widget customContainer({required IconData iconData}) {
return Container(
width: 45,
height: 45,
padding: const EdgeInsets.only(left: 0, right: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.red,
),
child: Icon(
iconData,
color: Colors.white,
),
);
}
Widget customProductsList({required String title, required List dataList}) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title),
const Align(
alignment: Alignment.centerRight,
child: Text(
'View All',
),
),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height / 6,
child: dataList.isNotEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: dataList.length,
itemBuilder: (context, index) {
var item = dataList[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: Text(item["name"].toString()),
),
),
),
);
},
// This next line does the trick.
)
: const Center(
child: Text('No data found.'),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Container(
// color: Colors.purple,
// margin: const EdgeInsets.only(top: 45, bottom: 15),
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Bengaluru",
style: TextStyle(
color: Colors.red,
),
),
const Text("R.T Nagar")
]),
),
customContainer(
iconData: Icons.search,
),
const SizedBox(
width: 10,
),
customContainer(
iconData: Icons.notifications,
),
])),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Popular Services',
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height / 12,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: List<Widget>.generate(
array
.length, // place the length of the array here
(int index) {
return Container(
margin: const EdgeInsets.all(2.0),
child: GestureDetector(
onTap: () {
tappedCategory(index);
},
child: Chip(
label: Text(array[index]),
backgroundColor:
_selectedCategoryIndex == index
? Colors.green
: Colors.grey,
),
),
);
}).toList(),
),
),
SizedBox(
height: MediaQuery.of(context).size.height / 6,
child: filteredData.isNotEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
// physics: NeverScrollableScrollPhysics(),
itemCount: filteredData.length,
itemBuilder: (context, index) {
var item = filteredData[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10),
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DetailPage(
user: item)));
},
child: Container(
color: item['bg'],
child: Center(
child: Padding(
padding: const EdgeInsets
.symmetric(
horizontal: 20),
child: Text(
item["name"].toString()),
),
),
),
),
);
},
// This next line does the trick.
)
: const Center(
child: Text(
"No records available for the selected category"),
),
),
customProductsList(
title: 'Popular Products',
dataList: product_data),
customProductsList(
title: 'Our Products', dataList: product_data),
customProductsList(
title: 'New Products', dataList: product_data)
],
),
),
)
]),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({Key? key, this.user}) : super(key: key);
final dynamic user;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(user["name"]),
),
body: Container(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(child: Image.network(user["profilePic"]), minRadius: 60,),
SizedBox(height: 10,),
Text("${user["name"]}"),
],
),
)
);
}
}
You can use: <true/false expressions> ? <if true> : <if false>
highlight the chip that is selected
Add an backgroundColor to Chip. index == _selectedCategoryIndex will check that chip is selected or not by index then set the color you want. In this case, that ex mean If the chip is selected chip, colored it!
- child: Chip(label: Text(array[index])),
+ child: Chip(
+ label: Text(array[index]),
+ backgroundColor: index == _selectedCategoryIndex ? APP_COLOR.mainColor : null),
+ )
so a text message stating Not Available if there is nothing available, rather then leaving blank and
Check filteredData have data then render what you want. In this case, ex mean If the filtered list is EMPTY, then render the Text insteads.
Detailer:
- Container(
- height: MediaQuery.of(context).size.height / 6,
- child: ListView.builder(...),
- ),
+ Container(
+ height: MediaQuery.of(context).size.height / 6,
+ child: filteredData.isNotEmpty
+ ? ListView.builder(...)
+ : Text('YOUR FILTERED LIST IS EMPTY'),
+ ),
Chip(
label: "label",
backgroundColor: _selectedCategoryIndex == index ? Colors.blue : Colors.green,
),
do not forgot to change tappedCategory function
replace your function with this,
void tappedCategory(int index) {
_selectedCategoryIndex = index;
_selectedUserType = array[index];
_filterData();
setState(() {});
}

Using TabBar like filter

i'm trying to make a Tabbar that appear in a new page when the search icon in pressed. The code works fine but i don't know how to implement this tabbar. I want to use the tabbar for splitting the search info, each icon has to show only specific info.
I guess each icon has a specific list?
This is my search_tool.dart this appear when the icon button at the main page is pressed
[EDIT] Now the result is shown correctly, but when I press the search box to write the error message contained in buildSuggestion always appears, instead it should only show the list with the relative records and if something not belonging to that category is searched then it must give the error message
import 'package:flutter/material.dart';
import 'package:solaris/lista_data.dart';
import 'constants.dart';
class LinkItemsSearch extends SearchDelegate<LinkItem>{
#override
PreferredSizeWidget buildBottom(BuildContext context) {
return PreferredSize(
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Apps';
this.showResults(context);
},
child: Text('Apps'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Movies';
this.showResults(context);
},
child: Text('Movies'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Games';
this.showResults(context);
},
child: Text('Games'),
),
),
],
),
),
),
preferredSize: Size.fromHeight(60),
);
}
#override
List<Widget> buildActions(BuildContext context) {
return [IconButton(icon: Icon(Icons.clear),onPressed: () { query=""; },)];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(onPressed: () { Navigator.pop(context); }, icon: Icon(Icons.arrow_back),);
}
#override
Widget buildResults(BuildContext context) {
var mylist = loadLinkItem().where((p) => p.description.contains(query)).toList();
return Container(
color: blue,
child: ListView.builder(
itemCount: mylist.length,
itemBuilder: (context,index){
final LinkItem listitem = mylist[index];
return Container(
color: blue,
child: ListTile(
title:InkWell(
onTap: () { Navigator.pushReplacement(context,MaterialPageRoute(builder: (context) => listitem.link)); },
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text(listitem.title, style: TextStyle(color: Colors.white),),
Text(listitem.description, style: TextStyle(color: Colors.white,fontSize: 14),),
Divider(color: white,),
],
),
),
),
);
}
),
);
}
#override
Widget buildSuggestions(BuildContext context) {
final mylist = query.isEmpty? loadLinkItem():loadLinkItem().where((p) => p.description.contains(RegExp(query, caseSensitive: false))).toList();
return mylist.isEmpty?
Container(
color: red,
child: Center(child: Text('No Result Found . . .', style: TextStyle(color: Colors.white,fontSize: 20,))),
):Container(
color: blue,
child: ListView.builder(
itemCount: mylist.length,
itemBuilder: (context,index){
final LinkItem listitem = mylist[index];
return Container(
color: blue,
child: ListTile(onTap: (){ showResults(context);},
title:InkWell(
onTap: () { Navigator.pushReplacement(context,MaterialPageRoute(builder: (context) => listitem.link)); },
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text(listitem.title, style: TextStyle(color: Colors.white),),
Text(listitem.description, style: TextStyle(color: Colors.white,fontSize: 14),),
Divider(color: white,),
],
),
),
),
);
}
),
);
}
}
Search icon
IconButton(onPressed:(){
showSearch(context: context, delegate: LinkItemsSearch());
}, icon: Icon(Icons.search),),
List
class LinkItem{
final String title;
final String description;
final link;
LinkItem({
required this.title,
required this.description,
required this.link,
});
}
List<LinkItem> loadLinkItem(){
var link = <LinkItem>[
LinkItem(
title: 'Title1',
description: 'Apps',
link: Title1(),
),LinkItem(
title: 'Title2',
description: 'Movies',
link: Title2(),
),LinkItem(
title: 'Title3',
description: 'Games',
link: Title3(),
),
];
return link;
}
You can override the buildBottom method in your LinkItemsSearch:
#override
PreferredSizeWidget buildBottom(BuildContext context) {
return PreferredSize(
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Apps';
mylist = loadLinkItem()
.where((p) => p.description.contains(query))
.toList();
this.showResults(context);
},
child: Text('Apps'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Movies';
mylist = loadLinkItem()
.where((p) => p.description.contains(query))
.toList();
this.showResults(context);
},
child: Text('Movies'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Games';
mylist = loadLinkItem()
.where((p) => p.description.contains(query))
.toList();
this.showResults(context);
},
child: Text('Games'),
),
),
],
),
),
),
preferredSize: Size.fromHeight(60),
);
}
For this to work, you have to create myList on the top of your LinkItemsSearch and reuse it when filtering everywhere.
Also, I just updated loadLinkItem method to have some input for filtering:
List<LinkItem> loadLinkItem() {
var link = <LinkItem>[
LinkItem(
title: 'Title1',
description: 'Movies',
link: '',
),
LinkItem(
title: 'Title2',
description: 'Games',
link: '',
),
LinkItem(
title: 'Title3',
description: 'Apps',
link: '',
),
];
return link;
}
Of course, I have not completely matched your style, so I did not style buttons as you need it, you might higher bottom bar than 60 as I used. I also have not attached any on press handlers since I am not sure what should they do, but it looks as it is expected: https://i.stack.imgur.com/osUkt.png
I wrapped them with a Column and SingleChildScrollView in case you have more of those items and they need to be scrollable: https://i.stack.imgur.com/FyWVX.png
You can even add some conditions in cases when you don't need this bottom bar to be displayed and in that case, you can just return null from the buildBottom method.
FIX
import 'package:flutter/material.dart';
import 'package:solaris/lista_data.dart';
import 'constants.dart';
class LinkItemsSearch extends SearchDelegate<LinkItem>{
#override
PreferredSizeWidget buildBottom(BuildContext context) {
return PreferredSize(
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Apps';
this.showResults(context);
},
child: Text('Apps'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Movies';
this.showResults(context);
},
child: Text('Movies'),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton(
onPressed: () {
query = 'Games';
this.showResults(context);
},
child: Text('Games'),
),
),
],
),
),
),
preferredSize: Size.fromHeight(60),
);
}
#override
List<Widget> buildActions(BuildContext context) {
return [IconButton(icon: Icon(Icons.clear),onPressed: () { query=""; },)];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(onPressed: () { Navigator.pop(context); }, icon: Icon(Icons.arrow_back),);
}
#override
Widget buildResults(BuildContext context) {
var mylist = loadLinkItem().where((p) => p.description.contains(query)).toList();
return Container(
color: blue,
child: ListView.builder(
itemCount: mylist.length,
itemBuilder: (context,index){
final LinkItem listitem = mylist[index];
return Container(
color: blue,
child: ListTile(
title:InkWell(
onTap: () { Navigator.pushReplacement(context,MaterialPageRoute(builder: (context) => listitem.link)); },
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text(listitem.title, style: TextStyle(color: Colors.white),),
Text(listitem.description, style: TextStyle(color: Colors.white,fontSize: 14),),
Divider(color: white,),
],
),
),
),
);
}
),
);
}
#override
Widget buildSuggestions(BuildContext context) {
final mylist = query.isEmpty? loadLinkItem():loadLinkItem().where((p) => p.description.contains(RegExp(query, caseSensitive: false))).toList();
return mylist.isEmpty?
Container(
color: red,
child: Center(child: Text('No Result Found . . .', style: TextStyle(color: Colors.white,fontSize: 20,))),
):Container(
color: blue,
child: ListView.builder(
itemCount: mylist.length,
itemBuilder: (context,index){
final LinkItem listitem = mylist[index];
return Container(
color: blue,
child: ListTile(onTap: (){ showResults(context);},
title:InkWell(
onTap: () { Navigator.pushReplacement(context,MaterialPageRoute(builder: (context) => listitem.link)); },
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text(listitem.title, style: TextStyle(color: Colors.white),),
Text(listitem.description, style: TextStyle(color: Colors.white,fontSize: 14),),
Divider(color: white,),
],
),
),
),
);
}
),
);
}
}