Keep dropdown open when active checkbox - flutter

I have a code that is responsible for filtering by certain categories (I shortened it for ease of reading). When opening the filter window, the user sees these category names ('Select a brand', 'Select a operation system', 'Select a color' etc).
Next, the user can open the category (initially, the dropdown list is in the closed position.), and select the parameters from the drop-down list (and click the apply button). The next time you open the filter window, the checkboxes in front of the parameters remain, but the drop-down list collapses.
Tell me how to do it: if in any category there are options marked with a checkmark, so that the drop-down list will be open the next time the window with filters is opened.
class FilterDialog extends StatefulWidget {
final void Function(Map<String, List<String>?>) onApplyFilters;
final Map<String, List<String>?> initialState;
const FilterDialog({
Key? key,
required this.onApplyFilters,
this.initialState = const {},
}) : super(key: key);
#override
State<FilterDialog> createState() => _FilterDialogState();
}
class _FilterDialogState extends State<FilterDialog> {
// Temporary storage of filters.
Map<String, List<String>?> filters = {};
bool needRefresh = false;
// Variable for the ability to hide all elements of filtering by any parameter.
bool isClickedBrand = false;
List manufacturer = [];
#override
void initState() {
super.initState();
filters = widget.initialState;
}
// A function to be able to select an element to filter.
void _handleCheckFilter(bool checked, String key, String value) {
final currentFilters = filters[key] ?? [];
if (checked) {
currentFilters.add(value);
} else {
currentFilters.remove(value);
}
setState(() {
filters[key] = currentFilters;
});
}
// Building a dialog box with filters.
#override
Widget build(BuildContext context) {
return SimpleDialog(
// Window title.
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
)),
contentPadding: const EdgeInsets.all(16),
// Defining parameters for filtering.
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Here and in subsequent Column, there will be a definition of parameters for filtering,
// a title, the ability to hide/show the items of list
Column(children: [
InkWell(
onTap: () async {
manufacturer = await getManufacturerOptions();
setState(() {
isClickedBrand = !isClickedBrand;
});
},
child: Row(children: [
Text('Select a brand'.toString(),
style: const TextStyle(
fontSize: 18,
)),
const Spacer(),
isClickedBrand
? const Icon(Icons.arrow_circle_up)
: const Icon(Icons.arrow_circle_down)
])),
!isClickedBrand
? Container()
: Column(
children: manufacturer
.map(
(el) => CustomCheckboxTile(
value: filters['manufacturer']?.contains(el) ??
false,
label: el,
onChange: (check) =>
_handleCheckFilter(check, 'manufacturer', el),
),
)
.toList())
]),
const SizedBox(
height: 5,
),
// Building a button to apply parameters.
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
widget.onApplyFilters(filters);
needRefresh = true;
},
child:
const Text('APPLY', style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
// Building a button to reset parameters.
const SizedBox(
height: 5,
),
ElevatedButton(
onPressed: () async {
setState(() {
filters.clear();
});
widget.onApplyFilters(filters);
},
child: const Text('RESET FILTERS',
style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
],
),
],
);
}
}
For example: the user clicks on the filter box, selects the brands to search for, and clicks the apply button. My task is that the next time the user opens the filter window, the categories with active checkboxes (in this example, the brand) are in an expanded state

The concept is, you need to check filter data with while opening dialog, To simplify the process I am using ExpansionTile. You can check this demo and customize the behavior and look.
Run on dartPad, Click fab to open dialog and touch outside the dialog to close this.
class ExTExpample extends StatefulWidget {
ExTExpample({Key? key}) : super(key: key);
#override
State<ExTExpample> createState() => _ExTExpampleState();
}
class _ExTExpampleState extends State<ExTExpample> {
// you can use map or model class or both,
List<String> filter_data = [];
List<String> brands = ["Apple", "SamSung"];
List<String> os = ["iOS", "Android"];
_showFilter() async {
await showDialog(
context: context,
builder: (c) {
// you can replace [AlertDialog]
return AlertDialog(
content: StatefulBuilder(
builder: (context, setSBState) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ExpansionTile(
title: const Text("Brand"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in brands) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...brands.map(
(brandName) => CheckboxListTile(
value: filter_data.contains(brandName),
title: Text(brandName),
onChanged: (v) {
if (filter_data.contains(brandName)) {
filter_data.remove(brandName);
} else {
filter_data.add(brandName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
ExpansionTile(
title: const Text("select OS"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in os) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...os.map(
(osName) => CheckboxListTile(
value: filter_data.contains(osName),
title: Text(osName),
onChanged: (v) {
if (filter_data.contains(osName)) {
filter_data.remove(osName);
} else {
filter_data.add(osName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
],
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FloatingActionButton(
onPressed: () {
_showFilter();
},
),
),
);
}
}

Whenever you open filter the isClickedBrand is False so it won't showed you a list. So the solution is :
After selecting option from list, change the state of isClickedBrand state. I mean if it's true then it will show the list otherwise show container.
Hope you get my point.

Related

Choice chip value does not update in flutter

i'm trying to store a value coming from the selected choice chip in a variable called etatLabel, so I can send it alongside other values in a form. The problem is that once I first do the selection, the value gets stored successfully, but then the variable's value do not get settled to "" again. Which means if I hit the add button again it will add the value from the choice chip that was last selected.
here is my code:
String etatLabel = "";
class MyOptions extends StatefulWidget {
final ValueNotifier<String?> notifier;
const MyOptions({super.key, required this.notifier});
#override
State<MyOptions> createState() => _MyOptionsState();
}
class _MyOptionsState extends State<MyOptions> {
static const String failedString = "Echec";
int? _value;
List<String> items = ["Succés", failedString];
#override
Widget build(BuildContext context) {
return Column(
children: [
Wrap(
children: List<Widget>.generate(
items.length,
(int index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Container(
width: 100,
child: ChoiceChip(
backgroundColor: Colors.deepPurple,
selectedColor: _value == 1 ? Colors.red : Colors.green,
label: Align(
alignment: Alignment.center,
child: Text(
'${items[index]}',
style: TextStyle(color: Colors.white),
),
),
selected: _value == index,
onSelected: (bool selected) {
setState(() {
_value = selected ? index : null;
etatLabel = items[index];
});
widget.notifier.value = items[index];
},
),
),
);
},
).toList(),
),
],
);
}
}
I couldn't find a way to solve this, and I appreciate your suggestions/help.

How to save edited todo item?

For the application, I create the functionality of editing todo item. Item has two fields: title and description. When you click on an item, it opens a modal where you can edit it. How to correctly write a function to the save button so that it saves the changes and displays them?
My Provider:
class ListModel extends ChangeNotifier {
List<EventModel> eventList = [];
void addEventToList() {
EventModel eventModel = EventModel(
title: 'Event title ${eventList.length + 1}',
detail: 'Event text ${eventList.length + 1}',
id: '${eventList.length + 1}',
);
eventList.add(eventModel);
notifyListeners();
}
EventModel? getEvent(String? id) {
return eventList.firstOrNullWhere((event) => event.id == id);
}
}
My modal window:
class EditEventBottomSheet extends StatelessWidget {
final EventModel event;
const EditEventBottomSheet({Key? key, required this.event}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 300,
color: Colors.amber,
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Change Event',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
TextFormField(
initialValue: event.title,
),
const SizedBox(height: 10),
TextFormField(
initialValue: event.detail,
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text('Save Edits'),
onPressed: () {},
),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
),
);
}
}
You should have an EditingControllers for each textField,
so, define the following before your build method
late final titleController = TextEditingController(text: event.title);
late final detailController = TextEditingController(text: event.detail);
and inside your build method
final provider = Provider.of<ListModel>(context);
TextFormField(
controller: titleController,
),
const SizedBox(height: 10),
TextFormField(
controller: detailController,
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text('Save Edits'),
onPressed: () {
provider.editTodoItem(event.id, titleController.text, detailController.text);
},
),
And your editTodoItem should be something like:
void editTodoItem(final String id, final String title, final String details){
//Add a validation if you want, for example title & details must not be empty
if(valid){
final indexOfOld = eventList.map((e) => e.id).toList().indexOf(id);
if (indexOfOld == -1) return;
eventList.removeAt(indexOfOld);
eventList.insert(indexOfOld, EventModel(
title: title,
detail: details,
id: '$indexOfOld',
));
notifyListeners();
}
}
That is one of the ways of editing, (Replace the object)
Also Instead of removing the object and creating a new one with the new values and insert it in old's one index, you can do the following
if(valid){
final event = eventList.firstWhere((e) => e.id == id);
event.title = title;
event.detail = details;
notifyListeners();
}
but to be honest, the second way might not update your UI, It's been a long time since I used providers
Hope this answer helps.

Why does my filter using DropDownMenu only work the first time and how can I get it to work consistently?

I am trying to build a filter using a dynamically populated DropDownMenu that gets "categories" from an API endpoint and then upon one of these categories being selected, filters and only displays the organizations that fall into the selected category. This list of organizations is built using either OrganizationListWidget or OrganizationByCategoryListWidget depending on whether the user has currently selected some category or not yet, respectively. The issue is, this only works successfully the first time a user picks a category, beyond this, anytime the user picks a different category from the dropdown the organization list remains unchanged and I am unsure why this is occurring. I am brand new to Flutter so I apologize for poor code quality/if this is trivial. If anyone could explain why this is happening and a possible fix it would be much appreciated.
Edit: The API is working, the organizations initially load in and build into the list fine, and they are filtered correctly the first time a category is selected.
import 'package:flutter/material.dart';
import 'package:myteam_app/categoryform.dart';
import 'package:myteam_app/screens/organization_form.dart';
import 'package:myteam_app/models/organization.dart';
import 'package:myteam_app/resources/size_resource.dart';
import 'package:myteam_app/utility/myteam_event_listener.dart';
import 'package:myteam_app/widgets/myteam_screen.dart';
import 'package:myteam_app/widgets/myteam_search_bar.dart';
import 'package:myteam_app/widgets/organization_by_category_list_widget.dart';
import 'package:myteam_app/widgets/organization_list_widget.dart';
import 'package:myteam_app/api/myteam/api.dart';
/// HomeScreen is the home page or starting page of the app when the user is logged in
///
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _currentSelection = "";
static const String ADD_ORGANIZATION_CHOICE = 'Add Organization';
static const String ADD_CATEGORY_CHOICE = 'Add Category';
//var widg = OrganizationByCategoryListWidget(_currentSelection);
// reference: how to programmatically open drawer https://stackoverflow.com/a/57748219/17836168
final GlobalKey<ScaffoldState> _key = GlobalKey();
final List<String> items = [ADD_ORGANIZATION_CHOICE, ADD_CATEGORY_CHOICE];
final Future<List<String>> categories = API().getCategories();
final myteamEventListener<void, void> onDrawerOpenEvent =
myteamEventListener();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => myteamScreen(
key: _key,
onOpenDrawerEvent: onDrawerOpenEvent,
body: Column(
// padding: EdgeInsets.symmetric(vertical: 30.0),
children: <Widget>[
myteamSearchBar(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Add..."),
items: items.map(buildMenuItem).toList(),
onChanged: (value) {
switch (value) {
case ADD_ORGANIZATION_CHOICE:
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrganizationForm()),
);
break;
}
case ADD_CATEGORY_CHOICE:
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryForm()),
);
break;
}
}
},
),
),
FutureBuilder<List<String>>(
future: API().getCategories(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//print("we have called anew.");
//print(snapshot.data);
}
return snapshot.hasData
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Select a category"),
items: snapshot.data?.map((item) {
return DropdownMenuItem<String>(
value: item, child: Text(item));
}).toList(),
onChanged: (value) {
setState(() {
print(
"cur selection is + " + _currentSelection);
_currentSelection = value.toString();
//status += 1;
//print(_currentSelection);
//OrganizationByCategoryListWidget(
// _currentSelection);
});
},
value: _currentSelection == ""
? null
: _currentSelection,
))
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: SizeResource.standardMargin,
vertical: SizeResource.smallStandardMargin,
),
child: DropdownButton<String>(
hint: Text("Select a category"),
items: snapshot.data?.map((item) {
return DropdownMenuItem<String>(
value: item, child: Text(item));
}).toList(),
onChanged: (value) {
setState(() {
//print("ok");
_currentSelection = value.toString();
//status += 1;
//OrganizationByCategoryListWidget(
//_currentSelection);
});
},
value: _currentSelection,
));
}),
_currentSelection == ""
? OrganizationListWidget()
: OrganizationByCategoryListWidget(
_currentSelection, UniqueKey())
// homeContent(),
],
),
);
Widget createCard(Organization organization) {
return Card(
margin: const EdgeInsets.all(SizeResource.cardMargin),
child: Padding(
padding: const EdgeInsets.all(SizeResource.cardInnerPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(organization.name),
Text("Description"),
],
),
));
}
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
);
}

bottom sheet is showing repeatedly once event triggred using bloc

I am a newbie, learning how to use bloc with freezed. i created a bottom sheet when the user click to the float action button, the bottom sheet appears. Bottom sheet contains text field and three radio groups when i click to select a radio the bottom sheet popup again like this GIF.
https://drive.google.com/file/d/1iU06adGcwjEaw9z2LsmO5xS24CC6OQfT/view?usp=sharing
the bloc is:
class NoteBloc extends Bloc<NoteEvent, NoteState> {
NoteBloc() : super(NoteState.initial());
#override
Stream<NoteState> mapEventToState(
NoteEvent event,
) async* {
yield* event.map(
addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
yield state.copyWith(
isAddNewNoteButtonClickedState: e.isClickedEvent,
);
},
radioEvent: (RadioEvent e) async* {
yield state.copyWith(radioGroupState: e.value);
},
textInputEvent: (TextInputEvent e) async* {
yield state.copyWith(textInputState: Note(value: e.value));
},
);
}
}
the event is:
class NoteEvent with _$NoteEvent {
const factory NoteEvent.addNewNoteButtonEvent(
{required bool isClickedEvent}) = AddNewNoteButtonEvent;
const factory NoteEvent.radioEvent({required int value}) = RadioEvent;
const factory NoteEvent.textInputEvent({required String value}) =
TextInputEvent;
}
the state is:
class NoteState with _$NoteState {
const factory NoteState({
// required bool showSaveIconState,
required int radioGroupState,
required bool isAddNewNoteButtonClickedState,
required Note textInputState,
}) = _NoteState;
// initialize every state
factory NoteState.initial() => NoteState(
radioGroupState: 1,
isAddNewNoteButtonClickedState: false,
textInputState: Note(value: ''),
);
}
the home page code is:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
return BlocConsumer<NoteBloc, NoteState>(
listener: (context, state) {
// if (state is AddNoteClickedState) {
if (state.isAddNewNoteButtonClickedState) {
// show navigator
_scaffoldKey.currentState!
.showBottomSheet((context) => const AddNewNoteBottomSheet())
.closed
.then(
(value) {
// Change state
// to tell the bloc that
// the bottom sheet is closed
context.read<NoteBloc>().add(
const NoteEvent.addNewNoteButtonEvent(
isClickedEvent: false, // !state.isAddNewNoteClickedState,
),
);
},
);
}
},
builder: (context, state) {
return DefaultTabController(
length: 3,
child: Scaffold(
key: _scaffoldKey,
// add note float action button
floatingActionButton: InkWell(
onTap: () {
// make button clicked
// to open the bottom sheet
context.read<NoteBloc>().add(
const NoteEvent.addNewNoteButtonEvent(
isClickedEvent: true, //!state.isAddNewNoteClickedState,
),
);
// print(state);
},
// for float action button
// icon shape
child: Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
height: 11.h,
width: 10.h,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(1.w)),
color: const Color(accentColor),
),
// add note icon
child: Icon(
state.isAddNewNoteButtonClickedState ? Icons.save : Icons.add,
color: const Color(whiteColor),
size: 9.h,
),
),
);
},
);
}
}
AddNewNoteBottomSheet code is:
// For adding new note
class AddNewNoteBottomSheet extends StatelessWidget {
const AddNewNoteBottomSheet();
#override
Widget build(BuildContext context) {
TextEditingController _textEditingController = TextEditingController();
return Container(
// Contains all
// bottom sheet components
child: Column(
children: [
// bottom sheet body
Expanded(
child: SingleChildScrollView(
child: BlocBuilder<NoteBloc, NoteState>(
builder: (context, state) {
// print("Helllo ${state.maybeMap(orElse: (){},radioClickState: (stat)=>stat.value)}");
return Column(
children: [
// Today Radio
ListTile(
title: Text(
AppLocalizations.of(context)!.homeTodayTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
contentPadding: const EdgeInsets.all(0),
autofocus: true,
leading: Radio<int>(
value: 1,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context
.read<NoteBloc>()
.add(NoteEvent.radioEvent(value: value!));
},
),
),
// Tomorrow Radio
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Text(
AppLocalizations.of(context)!.homeTomorrowTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
leading: Radio<int>(
value: 2,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context.read<NoteBloc>().add(
NoteEvent.radioEvent(value: value!),
);
},
),
),
// Some Day Radio
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Text(
AppLocalizations.of(context)!.homeSomeDayTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
leading: Radio<int>(
value: 3,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context.read<NoteBloc>().add(
NoteEvent.radioEvent(value: value!),
);
},
),
),
],
);
},
),
),
),
],
),
);
}
}
Problem definition: This problem happens because you use state.copyWith on your bloc. When you use copyWith, even if you don't update isAddNewNoteButtonClickedState variable, your state persists that value (stays true after you set it, never changes if you don't change it) if you do not alter it. Because on copyWith method, update logic works like;
YourState copyWith(bool isAddNewNoteButtonClickedState) {
return YourState(isAddNewNoteButtonClickedState: isAddNewNoteButtonClickedState ?? this. isAddNewNoteButtonClickedState,)
}
And when you yield another state, this isAddNewNoteButtonClickedState stays true, and your listener shows another modal since your isAddNewNoteButtonClickedState did not change.
Solution: you can solve this problem by (adding isAddNewNoteButtonClickedState: false to other states):
class NoteBloc extends Bloc<NoteEvent, NoteState> {
NoteBloc() : super(NoteState.initial());
#override
Stream<NoteState> mapEventToState(
NoteEvent event,
) async* {
yield* event.map(
addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
yield state.copyWith(
isAddNewNoteButtonClickedState: e.isClickedEvent,
);
},
radioEvent: (RadioEvent e) async* {
yield state.copyWith(radioGroupState: e.value, isAddNewNoteButtonClickedState: false);
},
textInputEvent: (TextInputEvent e) async* {
yield state.copyWith(textInputState: Note(value: e.value), isAddNewNoteButtonClickedState: false);
},
);
}
}
Additional note:
Since you are not transforming events in bloc, or doing something trivial, you can use cubit to simplify your state management and business logic.

Flutter search bar with autocomplete

I'm looking for a search bar in flutter docs but can't find it, is there a widget for the search bar with autocomplete in appbar. For example, I have a search icon on my appbar. When one press it show's the search box, when you type it should show autocomplete from the dropdown with listtile. I managed to implement this but it's not easy to use because I need a dropdown to show suggestion autocomplete, then use the suggestion for a new route if selected.
Here the search action
You can use Stack to achieve the autocomplete dropdown box effect. Example below has 2 Containers - both hold ListView as child objects. One holds search results, other has some random text as content for the body. ListView (search result) is placed inside an Align Object and alignment property is set to Alignment.topCenter. This ensures that List appears at the top, just below the AppBar.
Updated the Post (accepted answer) mentioned in the comments for a complete a demo.
As explained above:
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new DefaultTabController(length: 5, child: mainTabView),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
//heightFactor: 0.0,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
Complete demo
class SearchList extends StatefulWidget {
SearchList({Key key, this.name}) : super(key: key);
final String name;
#override
_SearchListState createState() => new _SearchListState();
}
class _SearchListState extends State<SearchList> {
Widget appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
Icon actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
final key = new GlobalKey<ScaffoldState>();
final TextEditingController _searchQuery = new TextEditingController();
List<SearchResult> _list;
bool _IsSearching;
String _searchText = "";
String selectedSearchValue = "";
_SearchListState() {
_searchQuery.addListener(() {
if (_searchQuery.text.isEmpty) {
setState(() {
_IsSearching = false;
_searchText = "";
});
} else {
setState(() {
_IsSearching = true;
_searchText = _searchQuery.text;
});
}
});
}
#override
void initState() {
super.initState();
_IsSearching = false;
createSearchResultList();
}
void createSearchResultList() {
_list = <SearchResult>[
new SearchResult(name: 'Google'),
new SearchResult(name: 'IOS'),
new SearchResult(name: 'IOS2'),
new SearchResult(name: 'Android'),
new SearchResult(name: 'Dart'),
new SearchResult(name: 'Flutter'),
new SearchResult(name: 'Python'),
new SearchResult(name: 'React'),
new SearchResult(name: 'Xamarin'),
new SearchResult(name: 'Kotlin'),
new SearchResult(name: 'Java'),
new SearchResult(name: 'RxAndroid'),
];
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new Container(
child: ListView(
children: <Widget>[
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
],
),
),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
ListView searchList() {
List<SearchResult> results = _buildSearchList();
return ListView.builder(
itemCount: _buildSearchList().isEmpty == null ? 0 : results.length,
itemBuilder: (context, int index) {
return Container(
decoration: new BoxDecoration(
color: Colors.grey[100],
border: new Border(
bottom: new BorderSide(
color: Colors.grey,
width: 0.5
)
)
),
child: ListTile(
onTap: (){},
title: Text(results.elementAt(index).name,
style: new TextStyle(fontSize: 18.0)),
),
);
},
);
}
List<SearchResult> _buildList() {
return _list.map((result) => new SearchResult(name: result.name)).toList();
}
List<SearchResult> _buildSearchList() {
if (_searchText.isEmpty) {
return _list.map((result) => new SearchResult(name: result.name)).toList();
} else {
List<SearchResult> _searchList = List();
for (int i = 0; i < _list.length; i++) {
SearchResult result = _list.elementAt(i);
if ((result.name).toLowerCase().contains(_searchText.toLowerCase())) {
_searchList.add(result);
}
}
return _searchList
.map((result) => new SearchResult(name: result.name))
.toList();
}
}
Widget buildBar(BuildContext context) {
return new AppBar(
centerTitle: true,
title: appBarTitle,
actions: <Widget>[
new IconButton(
icon: actionIcon,
onPressed: () {
_displayTextField();
},
),
// new IconButton(icon: new Icon(Icons.more), onPressed: _IsSearching ? _showDialog(context, _buildSearchList()) : _showDialog(context,_buildList()))
],
);
}
String selectedPopupRoute = "My Home";
final List<String> popupRoutes = <String>[
"My Home",
"Favorite Room 1",
"Favorite Room 2"
];
void _displayTextField() {
setState(() {
if (this.actionIcon.icon == Icons.search) {
this.actionIcon = new Icon(
Icons.close,
color: Colors.white,
);
this.appBarTitle = new TextField(
autofocus: true,
controller: _searchQuery,
style: new TextStyle(
color: Colors.white,
),
);
_handleSearchStart();
} else {
_handleSearchEnd();
}
});
}
void _handleSearchStart() {
setState(() {
_IsSearching = true;
});
}
void _handleSearchEnd() {
setState(() {
this.actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
this.appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
_IsSearching = false;
_searchQuery.clear();
});
}
}
It is actually very simple. you can refer above answers for details. Let's follow these steps:
Create a list of items we want to have in the autofill menu, lets name it autoList
Create one more emptyList named filteredList
Add all the values of autoList to filterList
void initState() {
filteredList.addAll(autoList);
}
Create a custom search bar widget with a TextField in it
we will be getting a 'value' i.e. the text entered from this Textfield: eg. TextFiled(onchange(value){})
Assuming that we have strings in our autoList, write:
filteredList.removeWhere((i) => i.contains(value.toString())==false);
The complete TextField widget will look like:
TextField(
onChanged: (value) {
setState(() {
filteredList.clear(); //for the next time that we search we want the list to be unfilterted
filteredList.addAll(autoList); //getting list to original state
//removing items that do not contain the entered Text
filteredList.removeWhere((i) => i.contains(value.toString())==false);
//following is just a bool parameter to keep track of lists
searched=!searched;
});
},
controller: editingController,
decoration: InputDecoration(
border: InputBorder.none,
labelText: "Search for the filtered list",
prefixIcon: Icon(Icons.search),
),
),
Now, along the search bar, we just have to display filteredList with ListViewBuilder. done :)
this plugin will helpful for you,loader_search_bar
Flutter widget integrating search field feature into app bar, allowing to receive query change callbacks and automatically load new data set into ListView. It replaces standard AppBar widget and needs to be placed underneath Scaffold element in the widget tree to work properly.
Getting started
To start using SearchBar insert it in place of an AppBar element in the Scaffold widget. Regardless of the use case, defaultBar named argument has to be specified, which basically is a widget that will be displayed whenever SearchBar is not in activated state:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: SearchBar(
defaultBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _openDrawer,
),
title: Text('Default app bar title'),
),
...
),
body: _body,
drawer: _drawer,
);
}
Optional attributes
searchHint - hint string being displayed until user inputs any text,
initialQuery - query value displayed for the first time in search field,
iconified - boolean value indicating way of representing non-activated SearchBar:
true if widget should be showed as an action item in defaultBar,
false if widget should be merged with defaultBar (only leading icon of the default widget and search input field are displayed in such case),
autofocus - boolean value determining if search text field should get focus whenever it becomes visible,
autoActive - ,
attrs - SearchBarAttrs class instance allowing to specify part of exact values used during widget building (e.g. search bar colors, text size, border radius),
controller - SearchBarController object that provides a way of interacing with current state of the widget,
searchItem - defining how to build and position search item widget in app bar,
overlayStyle - status bar overlay brightness applied when widget is activated.
Query callbacks
To get notified about user input specify onQueryChanged and/or onQuerySubmitted callback functions that receive current query string as an argument:
appBar: SearchBar(
...
onQueryChanged: (query) => _handleQueryChanged(context, query),
onQuerySubmitted: (query) => _handleQuerySubmitted(context, query),
),
QuerySetLoader
By passing QuerySetLoader object as an argument one can additionally benefit from search results being automatically built as ListView widget whenever search query changes:
appBar: SearchBar(
...
loader: QuerySetLoader<Item>(
querySetCall: _getItemListForQuery,
itemBuilder: _buildItemWidget,
loadOnEachChange: true,
animateChanges: true,
),
),
List<Item> _getItemListForQuery(String query) { ... }
Widget _buildItemWidget(Item item) { ... }
querySetCall - function transforming search query into list of items being then rendered in ListView (required),
itemBuilder - function creating Widget object for received item, called during ListView building for each element of the results set (required),
loadOnEachChange - boolean value indicating whether querySetCall should be triggered on each query change; if false query set is loaded once user submits query,
animateChanges - determines whether ListView's insert and remove operations should be animated.
SearchItem
Specifying this parameter allows to customize how search item should be built and positioned in app bar. It can be either action or menu widget. No matter which of these two is picked, two constructor arguments can be passed:
builder - function receiving current BuildContext and returning Widget for action or PopupMenuItem for menu item,
gravity - can be one of SearchItemGravity values: start, end or exactly. If no arguments are passed, SearchBar will create default item which is search action icon with start gravity.
SearchItem.action
appBar: SearchBar(
// ...
searchItem: SearchItem.action(
builder: (_) => Padding(
padding: EdgeInsets.all(12.0),
child: Icon(
Icons.find_in_page,
color: Colors.indigoAccent,
),
),
gravity: SearchItemGravity.exactly(1),
),
)
SearchItem.menu
appBar: SearchBar(
// ...
searchItem: SearchItem.menu(
builder: (_) => PopupMenuItem(
child: Text("Search 🔍"),
value: "search",
),
gravity: SearchItemGravity.end,
),
)
Also, bear in mind that SearchBar will prevent built item widget from receiving tap events and will begin search action rather than that.
hope it will help you.
import 'package:flutter/material.dart';
class SearchText extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Searchable Text"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: DataSearch(),
);
})
],
),
drawer: Drawer(),
);
}
}
class DataSearch extends SearchDelegate<String> {
final cities = ['Ankara', 'İzmir', 'İstanbul', 'Samsun', 'Sakarya'];
var recentCities = ['Ankara'];
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
child: Card(
color: Colors.red,
child: Center(child: Text(query)),
),
),
);
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? recentCities
: cities.where((p) => p.startsWith(query)).toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
showResults(context);
},
leading: Icon(Icons.location_city),
title: RichText(
text: TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
),
],
),
),
),
itemCount: suggestionList.length,
);
}
}