Flutter Search bar filter issues - flutter

I have a project that I have been working on and have been stumped on this for the last month and can't seem to figure out how to solve this. I have a screen that shows a list of contacts and a search bar that is used to show specific contacts. I have been trying to add a filter feature where the use clicks on filters and chooses a couple options that will show. The way the button widget works I couldn't get it to be in the same class so it's in another file and being used. Here is the code, I am still learning Flutter so if you have an recommendations to fix other bits of the code I would love to hear it.
Here is my first file with the screen that include the search bar and the the major chunk of code.
import 'package:myapp/widgets/cards.dart';
import 'package:flutter/material.dart';
import 'package:myapp/widgets/icon_buttons.dart';
class ResourceScreen extends StatefulWidget {
const ResourceScreen({Key? key}) : super(key: key);
#override
_ResourceScreen createState() => _ResourceScreen();
}
class _ResourceScreen extends State<ResourceScreen> {
bool filtered = false;
final TextEditingController _filter = TextEditingController();
String _searchText = "";
Widget customSearchBar = const Text('Resources');
Icon customIcon = const Icon(Icons.search);
List filterCardInformation = [],
tempList = [],
filterButtons = [],
cardInformation = phoneList;
void _getCards() async {
setState(() {
filterCardInformation = cardInformation;
});
}
//TODO There seems to be some lag when the list goes from nothing to the whole list. Not sure what is impacting this or how hard it could be on the users experience as list grows
_ResourceScreen() {
_filter.addListener(() {
filterCardInformation =
cardInformation; // Might need in every else statement, not sure if there is a memory leak with it being here
//Had "tempList = []" only here and caused the program to run out of memory. This is why I believe this could be an issue
if (_filter.text.isEmpty) {
if (filtered) {
setState(() {
_searchText = "";
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if (filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
});
} else {
setState(() {
_searchText = "";
});
}
} else {
if (filtered) {
setState(() {
_searchText = _filter.text;
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if (filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
});
} else {
setState(() {
_searchText = _filter.text;
});
}
}
});
}
//This is where the list is built as input comes in from the keyboard
Widget _buildList(context) {
if (_searchText.isNotEmpty && !filtered) {
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if ((filterCardInformation[i].title.toString())
.toLowerCase()
.contains(_searchText.toLowerCase())) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
} else if (_searchText.isNotEmpty && filtered) {
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if ((filterCardInformation[i].title.toString())
.toLowerCase()
.contains(_searchText.toLowerCase()) &&
filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
}
return ListView.builder(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: filterCardInformation.length,
itemBuilder: (BuildContext context, int index) {
return MyExpandingCard(
title: filterCardInformation[index].title,
information: filterCardInformation[index].information,
phoneList: filterCardInformation[index].phoneList,
website: filterCardInformation[index].website,
contactList: filterCardInformation[index].contactList,
);
},
);
}
#override
void initState() {
_getCards();
super.initState();
}
//Trigger filter list to show from the bottom
Widget filterButton(double height) {
return ElevatedButton.icon(
onPressed: () => {filterBottomSheet(height)},
//TODO Change out this icon for something different
icon: const Icon(Icons.filter),
label: const Text("Filter"),
);
}
//Row with buttons for bottomSheet
Widget doubleBottomButtonRow(String dataOne, dataTwo) {
Icon firstPersonalIcon = const Icon(Icons.add),
secondPersonalIcon = const Icon(Icons.add);
void _changeFirstIcon() {
setState(() {
if (firstPersonalIcon.icon == Icons.add) {
firstPersonalIcon = const Icon(Icons.check);
} else {
firstPersonalIcon = const Icon(Icons.add);
}
});
}
void _changeSecondIcon() {
setState(() {
if (secondPersonalIcon.icon == Icons.add) {
secondPersonalIcon = const Icon(Icons.check);
} else {
secondPersonalIcon = const Icon(Icons.add);
}
});
}
return Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
//TODO issue here with icon not changing on click. I am not sure exactly why, I just need to figure out how to trigger feedback from when button in different file is used
onPressed: () {
_changeFirstIcon();
},
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(dataOne),
icon: firstPersonalIcon),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
onPressed: () {
_changeSecondIcon();
},
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(dataTwo),
icon: secondPersonalIcon),
),
],
);
}
Widget singleBottomButtonRow(String dataOne) {
return Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IButton(firstName: dataOne)),
],
);
}
//BottomSheet filter
Future filterBottomSheet(double height) {
return showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return SizedBox(
height: height,
child: SingleChildScrollView(
child: Column(
children: [
doubleBottomButtonRow(
"filter 1", "filter 2"),
],
),
));
});
}
//TODO Design this
//Widget that keeps track of how many visible options are shown
#override
Widget build(BuildContext context) {
//Grab size of screan for the bottom sheet
Size size = MediaQuery.of(context).size;
double height = size.height;
return Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xff1c1d4b),
title: customSearchBar,
automaticallyImplyLeading: true,
actions: [
IconButton(
onPressed: () {
setState(() {
if (customIcon.icon == Icons.search) {
customIcon = const Icon(Icons.cancel);
//search bar
customSearchBar = ListTile(
leading: const Icon(
Icons.search,
color: Colors.white,
size: 28,
),
title: TextField(
controller: _filter,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Type in search...',
hintStyle:
TextStyle(color: Colors.white, fontSize: 18),
border: InputBorder.none,
),
style: const TextStyle(color: Colors.white),
),
);
//Reset the variables
} else {
customIcon = const Icon(Icons.search);
customSearchBar = const Text('Resources');
}
});
},
icon: customIcon)
],
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Row(
children: [
filterButton(height * .30),
],
),
Expanded(
child: SingleChildScrollView(
physics: const ScrollPhysics(),
child: _buildList(context),
),
),
],
),
),
);
}
}
//TODO This is not working but what I was thinking of how the function would look
List filterPhrase = [];
void _addPhrase(String phrase) {
filterPhrase.add(phrase);
}
void _deletePhrase(String phrase) {
filterPhrase.remove(phrase);
}
List phoneList = [
const MyExpandingCard(
title: 'example 1',
phoneList: ['123456789'],
metadata: "filter 1",
),
const MyExpandingCard(
title: 'example 2',
phoneList: ['123456789'],
metadata: "filter 2",
),
];
And here is the other file that I am using for the button in the filter
import 'package:flutter/material.dart';
import 'package:myapp/secondary_screens/resource_screen.dart';
class IButton extends StatefulWidget {
final String firstName;
const IButton({Key? key, required this.firstName}) : super(key: key);
#override
State<IButton> createState() => _IButtonState();
}
class _IButtonState extends State<IButton> {
Icon personalIcon = const Icon(Icons.add);
void _changeIcon() {
setState(() {
if (personalIcon.icon == Icons.add) {
personalIcon = const Icon(Icons.check);
//add to filter list
} else {
personalIcon = const Icon(Icons.add);
//remove from filter list
}
});
}
#override
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: _changeIcon,
icon: personalIcon,
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(widget.firstName),
);
}
}
If you have any resources to improve skills in flutter I would love to hear them. I am always looking to improve.
Thank you all

Related

The state of my object is not stored properly

I want to mark my object as favorite.
I have a list of object <RobotAnimation> which is displayed in a ListView. The class have two fields: title and isFavorite. Marking an object as a favorite works, but there is a problem when it comes to storing that state. When I perform a search of all items, after selecting an item as favorite, my favorite items are not being remembered. It seems like the state is being discarded.
What can I do to fix this problem?
Here's what's going on:
Here's my code:
class RobotAnimation {
String title;
bool isFavorite;
RobotAnimation({required this.title, this.isFavorite = false});
#override
String toString() {
return '{Title: $title, isFavortite: $isFavorite}';
}
}
class Animations extends StatefulWidget {
const Animations({super.key});
#override
State<Animations> createState() => _AnimationsState();
}
class _AnimationsState extends State<Animations> with TickerProviderStateMixin {
late TabController _tabController;
List<RobotAnimation> animations = [];
List<RobotAnimation> favoriteAnimations = [];
List<String> results = store.state.animations;
List<String> defaultFavorites = [];
List<RobotAnimation> getAnimationList(
List<String> animations, List<String> favorites) {
List<RobotAnimation> robotAnimations = [];
for (var animation in animations) {
bool isFav = false;
for (var favorite in favorites) {
if (favorite == animation) {
isFav = true;
}
}
robotAnimations.add(RobotAnimation(title: animation, isFavorite: isFav));
}
return robotAnimations;
}
List<RobotAnimation> filterFavorites() {
List<RobotAnimation> filtered = favoriteAnimations;
animations.where((element) => element.isFavorite == true).toList();
return filtered;
}
void filterSearchResults(String query) {
List<RobotAnimation> searchList =
getAnimationList(results, defaultFavorites);
log('query: $query');
List<RobotAnimation> filteredList = searchList
.where((element) =>
element.title.toLowerCase().contains(query.toLowerCase()))
.toList();
log(searchList.toString());
log(filteredList.toString());
setState(() => animations = filteredList);
}
#override
void initState() {
animations = getAnimationList(results, defaultFavorites);
super.initState();
}
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _Props>(
converter: (store) => _mapStateToProps(store),
builder: (_, props) {
return Scaffold(
body: TabBarView(
...
children: [
Container(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
onChanged: filterSearchResults,
decoration: const InputDecoration(
labelText: 'Search',
hintText: 'Search animation',
prefixIcon: Icon(Icons.search),
),
),
Expanded(
child: ListView.separated(
itemCount: animations.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
onTap: () {
props.socket?.animation(IAnimation(
animation: animations[index].title));
},
title: ExtendedText(
animations[index].title,
maxLines: 1,
overflowWidget: const TextOverflowWidget(
position: TextOverflowPosition.middle,
align: TextOverflowAlign.center,
child: Text(
'...',
overflow: TextOverflow.ellipsis,
),
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
setState(() {
animations[index].isFavorite
? animations[index].isFavorite = false
: animations[index].isFavorite = true;
});
},
icon: animations[index].isFavorite
? Icon(
Icons.favorite,
color: Colors.red.shade500,
)
: Icon(
Icons.favorite_border,
color: Colors.grey.shade500,
),
),
],
),
);
},
),
)
],
),
),
],
),
);
},
);
}
}
class _Props {
final Connection? socket;
final List<String> animations;
_Props({
required this.socket,
required this.animations,
});
}
_Props _mapStateToProps(Store<AppState> store) {
return _Props(
socket: store.state.socket,
animations: store.state.animations,
);
}
Try this inside your IconButton onPressed-Method:
setState(() {
if (animations[index].isFavorite) {
animations[index].isFavorite = false
defaultFavorites.remove(animations[index].title)
} else {
animations[index].isFavorite = true;
defaultFavorites.add(animations[index].title)
}
});
It seems like you always generate a new list of animations based on the two lists List<String>.

type '_Type' is not a subtype of type 'String'

So i am a beginner in flutter and am trying to learn via tutorials, so here I am trying to make todo app using sqflite and everything is perfect and no error is shown in the editor but on clicking floating action button in notelist file it shows this error-
The following _TypeError was thrown building Builder:
type '_Type' is not a subtype of type 'String'
heres my main.dart file
void main() {
runApp(MaterialApp(
home: NoteList(),
));
}
here notelist
class NoteList extends StatefulWidget {
const NoteList({Key? key}) : super(key: key);
#override
_NoteListState createState() => _NoteListState();
}
class _NoteListState extends State<NoteList> {
int count = 0;
DatabaseHelper databaseHelper = DatabaseHelper();
late List<Note> noteList;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Note List'),
),
body: getNoteListView(),
floatingActionButton: FloatingActionButton(
onPressed: () {
debugPrint('fab clicked');
navigateToDetail(Note('', '', 2 ,''),'Add Note');
},
child: Icon(Icons.add),
),
);
}
ListView getNoteListView(){
return ListView.builder(
itemCount: count,
itemBuilder: (context, index){
return Card(
color: Colors.white,
elevation: 2.0,
child: ListTile(
leading: CircleAvatar(
backgroundColor: getPriorityColor(this.noteList[index].priority),
child: getPriorityIcon(this.noteList[index].priority),
),
title: Text(this.noteList[index].title!,),
subtitle: Text(this.noteList[index].date!),
trailing: IconButton(onPressed: (){
_delete(context, noteList[index]);
},
icon: Icon(Icons.delete),
),
onTap: (){
debugPrint('tapped');
navigateToDetail(noteList[index],'Edit Note');
},
),
);
}
);
}
void navigateToDetail(Note note, String title) async{
bool result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return NoteDetail(appBarTitle: Title, note: note);
}));
if (result == true) {
updateListView();
}
}
// Returns the priority color
Color getPriorityColor(int? priority) {
switch (priority) {
case 1:
return Colors.red;
break;
case 2:
return Colors.yellow;
break;
default:
return Colors.yellow;
}
}
// Returns the priority icon
Icon getPriorityIcon(int? priority) {
switch (priority) {
case 1:
return Icon(Icons.play_arrow);
break;
case 2:
return Icon(Icons.keyboard_arrow_right);
break;
default:
return Icon(Icons.keyboard_arrow_right);
}
}
void _delete(BuildContext context, Note note) async {
int? result = await databaseHelper.deleteNote(note.id);
if (result != 0) {
_showSnackBar(context, 'Note Deleted Successfully');
updateListView();
}
}
void _showSnackBar(BuildContext context, String message) {
final snackBar = SnackBar(content: Text(message));
Scaffold.of(context).showSnackBar(snackBar);
}
void updateListView() {
final Future<Database> dbFuture = databaseHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Note>> noteListFuture = databaseHelper.getNoteList();
noteListFuture.then((noteList) {
setState(() {
this.noteList = noteList;
this.count = noteList.length;
});
});
});
}
}
and heres notedetail file
class NoteDetail extends StatefulWidget {
final Note note;
final appBarTitle;
NoteDetail( {Key? key,required this.appBarTitle, required this.note}) : super(key: key);
#override
_NoteDetailState createState() => _NoteDetailState(this.note, this.appBarTitle);
}
class _NoteDetailState extends State<NoteDetail> {
static var _priorities = ['High', 'Low'];
DatabaseHelper helper = DatabaseHelper();
TextEditingController titleController = TextEditingController();
TextEditingController descController = TextEditingController();
String appBarTitle;
Note note;
_NoteDetailState(this.note , this.appBarTitle);
#override
Widget build(BuildContext context) {
titleController.text = note.title!;
descController.text = note.description!;
return Scaffold(
appBar: AppBar(
title: Text(appBarTitle),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
ListTile(
title: DropdownButton(
items: _priorities.map((dropDownStringItem) {
return DropdownMenuItem (
value: dropDownStringItem,
child: Text(dropDownStringItem),
);
}).toList(),
value: getPriorityAsString(note.priority),
onChanged: (valueSelectedByUser) {
setState(() {
debugPrint('User selected $valueSelectedByUser');
updatePriorityAsInt(valueSelectedByUser);
});
}
),
),
SizedBox(height: 10,),
Container(
child: TextField(
controller: titleController,
onChanged: (value) {
debugPrint('something changed in the title textfield ');
updateTitle();
},
decoration: InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
)
),
),
),
SizedBox(height: 10,),
Container(
child: TextField(
controller: descController,
onChanged: (value) {
debugPrint('something changed in the description textfield ');
updateDescription();
},
decoration: InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
)
),
),
),
Container(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 50,
padding: EdgeInsets.all(5),
child: ElevatedButton(onPressed: (){
debugPrint('add button clicked');
_save();
}, child: Text('Save',
style: TextStyle(
fontSize: 18
),
)
),
),
Container(
width: 120,
height: 50,
padding: EdgeInsets.all(5),
child: ElevatedButton(onPressed: (){
_delete();
debugPrint('Delete button clicked');
}, child: Text('Delete',
style: TextStyle(
fontSize: 18
),)),
),
],
),
)
],
),
),
);
}
// Convert int priority to String priority and display it to user in DropDown
String getPriorityAsString(int? value) {
String priority = '';
switch (value) {
case 1:
priority = _priorities[0]; // 'High'
break;
case 2:
priority = _priorities[1]; // 'Low'
break;
}
return priority;
}
// Convert the String priority in the form of integer before saving it to Database
void updatePriorityAsInt(var value) {
switch (value) {
case 'High':
note.priority = 1;
break;
case 'Low':
note.priority = 2;
break;
}
}
// Update the title of Note object
void updateTitle(){
note.title = titleController.text;
}
// Update the description of Note object
void updateDescription() {
note.description = descController.text;
}
void _delete() async {
moveToLastScreen();
// Case 1: If user is trying to delete the NEW NOTE i.e. he has come to
// the detail page by pressing the FAB of NoteList page.
if (note.id == null) {
_showAlertDialog('Status', 'No Note was deleted');
return;
}
// Case 2: User is trying to delete the old note that already has a valid ID.
int? result = await helper.deleteNote(note.id);
if (result != 0) {
_showAlertDialog('Status', 'Note Deleted Successfully');
} else {
_showAlertDialog('Status', 'Error Occured while Deleting Note');
}
}
void moveToLastScreen() {
Navigator.pop(context, true);
}
void _showAlertDialog(String title, String message) {
AlertDialog alertDialog = AlertDialog(
title: Text(title),
content: Text(message),
);
showDialog(
context: context,
builder: (_) => alertDialog
);
}
// Save data to database
void _save() async {
moveToLastScreen();
note.date = DateFormat.yMMMd().format(DateTime.now());
int? result;
if (note.id != null) { // Case 1: Update operation
result = await helper.updateNote(note);
} else { // Case 2: Insert Operation
result = await helper.insertNote(note);
}
if (result != 0) { // Success
_showAlertDialog('Status', 'Note Saved Successfully');
} else { // Failure
_showAlertDialog('Status', 'Problem Saving Note');
}
}
}
This looks like a spelling mistake.
void navigateToDetail(Note note, String title) async{
...
// change Title into title
return NoteDetail(appBarTitle: title, note: note);
...

Flutter-- How to add a search bar to search in the list

I have a countries list which shows the time for the countries. Please let me know how to add the search bar.You can see the WorldTime list in the code. So it's displaying the countries name with the image. The code is as follows.
Choose_Location.dart
class Chooselocation extends StatefulWidget {
#override
_ChooseLocationState createState() => _ChooseLocationState();
}
class _ChooseLocationState extends State<Chooselocation> {
List<WorldTime> locations =[
WorldTime(url:'Europe/London', location: 'London', flag: 'England.png'),
WorldTime(url:'Europe/Berlin', location: 'Berlin', flag: 'Germany.jpg'),
WorldTime(url:'Africa/Cairo', location: 'Cairo', flag: 'Egypt.jpg'),
WorldTime(url:'Africa/Nairobi', location: 'Nairobi', flag: 'Kenya.jpg'),
WorldTime(url:'Asia/Jakarta', location: 'Seoul', flag: 'Indonesia.jpg'),
WorldTime(url:'Asia/Qatar', location: 'Qatar', flag: 'Qatar.png'),
WorldTime(url:'Africa/Khartoum', location: 'Sudan', flag: 'Sudan.jpg'),
WorldTime(url:'Asia/Karachi', location: 'Pakistan', flag: 'Pakistan.png'),
WorldTime(url:'America/New_York', location: 'USA', flag: 'USA.jpg'),
];
void updatetime(index) async{
WorldTime instance = locations[index];
await instance.getTime();
// mnavigate to home screen
Navigator.pop(context,{
'location':instance.location,
'flag':instance.flag,
'time':instance.time,
'isDaytime':instance.isDaytime,
});
}
#override
Widget build(BuildContext context) {
print('Build state function');
return Scaffold(
backgroundColor: Colors.blueGrey[200],
appBar: AppBar(
title:Text('Choose a Location'),
centerTitle:true,
elevation:0,
),
body: ListView.builder(
itemCount:locations.length,
itemBuilder: (context,index){
return Padding(
padding: const EdgeInsets.symmetric(vertical:1.0, horizontal: 4.0),
child: Card(child: ListTile(
onTap: (){
updatetime(index);
print(locations[index].location);
},
title:Text(locations[index].location),
leading: CircleAvatar(
backgroundImage: AssetImage('assets/${locations[index].flag}')),
),
),
);
}
),
);
}
}
You can see the image of the UI below.
In my case, i don't have WorldTime model class, so i used List<String> for filter functionality. You can use your model class for filter your list.
class Chooselocation extends StatefulWidget {
#override
_ChooseLocationState createState() => _ChooseLocationState();
}
class _ChooseLocationState extends State<Chooselocation> {
List<String> locations = [
'London',
'Berlin',
'Cairo',
'Nairobi',
'Seoul',
'Qatar',
'Sudan',
'Pakistan',
'USA'
];
List<String> locationList;
var locationDataList = List<String>();
final TextEditingController _filter = TextEditingController();
String _searchText = "";
Icon _searchIcon = new Icon(Icons.search);
Widget _appBarTitle;
void _searchPressed(String title) {
setState(() {
if (this._searchIcon.icon == Icons.search) {
this._searchIcon = new Icon(Icons.close);
this._appBarTitle = new TextField(
style: setTextStyle(),
controller: _filter,
decoration: new InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
prefixIcon: new Icon(
Icons.search,
color: Colors.white,
),
hintText: 'Search...',
hintStyle: setTextStyle()),
);
} else {
this._searchIcon = new Icon(Icons.search);
this._appBarTitle = new Text(title);
_filter.clear();
this._appBarTitle = null;
}
});
}
setTextStyle() {
return TextStyle(color: Colors.white);
}
#override
void initState() {
// TODO: implement initState
super.initState();
print("object");
_filter.addListener(() {
if (_filter.text.isEmpty) {
setState(() {
_searchText = "";
updateFilter(_searchText);
});
} else {
setState(() {
_searchText = _filter.text;
updateFilter(_searchText);
});
}
});
}
void updateFilter(String text){
print("updated Text: ${text}");
filterSearchResults(text);
}
void filterSearchResults(String query) {
List<String> dummySearchList = List<String>();
dummySearchList.addAll(locationList);
print("List size : " + dummySearchList.length.toString());
if(query.isNotEmpty) {
List<String> dummyListData = List<String>();
dummySearchList.forEach((item) {
if(item.toLowerCase().contains(query.toLowerCase())) {
dummyListData.add(item);
}
});
setState(() {
locationDataList.clear();
locationDataList.addAll(dummyListData);
});
return;
} else {
setState(() {
locationDataList.clear();
locationDataList.addAll(locations);
});
}
}
#override
Widget build(BuildContext context) {
if (locationList == null) {
locationList = List<String>();
locationList.addAll(locations);
locationDataList.addAll(locationList);
}
return Scaffold(
backgroundColor: Colors.blueGrey[200],
appBar: AppBar(
actions: <Widget>[
IconButton(
icon: _searchIcon,
onPressed: () {
_searchPressed("Choose Location");
},
tooltip: "Search",
)
],
title: _appBarTitle == null ? Text('Choose a Location') : _appBarTitle,
centerTitle: true,
elevation: 0,
),
body: ListView.builder(
itemCount: locationDataList.length,
itemBuilder: (context, index) {
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 1.0, horizontal: 4.0),
child: Card(
child: ListTile(
title: Text(locationDataList[index]),
leading: CircleAvatar(),
),
),
);
}),
);
}
}
Output of above code would be :
Hope this may help you :)
Create a TextField for enter searchKey by user
Use:
List<WorldTime> filteredLocations = locations.where(element => element.location == **searchKey**).toList();
Its return a list of all elements that contains searhchKey

How to access all of child's state from Parent Widget in flutter?

I have a parent widget called createRoutineScreen and it has 7 similar children widget called RoutineFormCard. RoutineFormCard is a form and which has a state _isPostSuccesful of boolean type to tell whether the form is saved to database or not. Now, I have to move to the other screen from createRoutine only when all of it's 7 children has _isPostSuccesful true. How can I access all of children's state from createRoutineScreen widget?
My Code is:
class CreateRoutineScreen extends StatefulWidget {
final String userID;
CreateRoutineScreen({this.userID});
//TITLE TEXT
final Text titleSection = Text(
'Create a Routine',
style: TextStyle(
color: Colors.white,
fontSize: 25,
)
);
final List<Map> weekDays = [
{"name":"Sunday", "value":1},
{"name":"Monday", "value":2},
{"name":"Tuesday", "value":3},
{"name":"Wednesday", "value":4},
{"name":"Thursday", "value":5},
{"name":"Friday", "value":6},
{"name":"Saturday", "value":7},
];
#override
_CreateRoutineScreenState createState() => _CreateRoutineScreenState();
}
class _CreateRoutineScreenState extends State<CreateRoutineScreen> {
Routine routine;
Future<List<dynamic>> _exercises;
dynamic selectedDay;
int _noOfRoutineSaved;
List _keys = [];
Future<List<dynamic>>_loadExercisesData()async{
String url = BASE_URL+ "exercises";
var res = await http.get(url);
var exercisesList = Exercises.listFromJSON(res.body);
//var value = await Future.delayed(Duration(seconds: 5));
return exercisesList;
}
#override
void initState(){
super.initState();
_exercises = _loadExercisesData();
_noOfRoutineSaved = 0;
for (int i = 0; i< 7; i++){
_keys.add(UniqueKey());
}
}
void _changeNoOfRoutineSaved(int a){
setState(() {
_noOfRoutineSaved= _noOfRoutineSaved + a;
});
}
#override
Widget build(BuildContext context) {
print(_noOfRoutineSaved);
return Scaffold(
appBar: AppBar(
title:Text("Create a Routine"),
centerTitle: true,
actions: <Widget>[
FlatButton(
child: Text("Done"),
onPressed: (){
},
),
],
),
body: Container(
color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top:5.0,left: 10,right: 10,bottom: 10),
child: FutureBuilder(
future: _exercises,
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: widget.weekDays.length,
itemBuilder: (context,index){
return RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key:_keys[index]
);
},
);
}
else if(snapshot.hasError){
return SnackBar(
content: Text(snapshot.error),
);
}
else{
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
)
);
}
},
)
),
);
}
}
And my child widget is:
class RoutineFormCard extends StatefulWidget {
final Function createRoutineState;
final String weekDay;
final List<dynamic> exerciseList;
final String userID;
final Function changeNoOfRoutineSaved;
RoutineFormCard({this.createRoutineState,
this.weekDay, this.exerciseList, this.changeNoOfRoutineSaved,
this.userID, Key key}):super(key:key);
#override
_RoutineFormCardState createState() => _RoutineFormCardState();
}
class _RoutineFormCardState extends State<RoutineFormCard> {
bool _checkBoxValue= false;
List<int> _selectedExercises;
bool _inAsyncCall;
bool _successfulPost;
#override
void initState(){
super.initState();
_selectedExercises = [];
_inAsyncCall = false;
_successfulPost= false;
}
void onSaveClick()async{
setState(() {
_inAsyncCall = true;
});
String url = BASE_URL + "users/routine";
List selectedExercises = _selectedExercises.map((item){
return widget.exerciseList[item].value;
}).toList();
String dataToSubmit = jsonEncode({
"weekDay":widget.weekDay,
"userID": widget.userID==null?"5e9eb190b355c742c887b88d":widget.userID,
"exercises": selectedExercises
});
try{
var res =await http.post(url, body: dataToSubmit,
headers: {"Content-Type":"application/json"});
if(res.statusCode==200){
print("Succesful ${res.body}");
widget.changeNoOfRoutineSaved(1);
setState(() {
_inAsyncCall = false;
_successfulPost = true;
});
}
else{
print("Not succesful ${res.body}");
setState(() {
_inAsyncCall = false;
});
}
}catch(err){
setState(() {
_inAsyncCall = false;
});
print(err);
}
}
Widget saveAndEditButton(){
if(_inAsyncCall){
return CircularProgressIndicator();
}
else if(_successfulPost)
{
return IconButton(
icon: Icon(Icons.edit, color: Colors.black,),
onPressed: (){
widget.changeNoOfRoutineSaved(-1);
setState(() {
_successfulPost = false;
});
},
);
}
else{
return FlatButton(child: Text("Save"),
onPressed: !_checkBoxValue&&_selectedExercises.length==0?null:onSaveClick,);
}
}
//Card Header
Widget cardHeader(){
return AppBar(
title: Text(widget.weekDay, style: TextStyle(
fontFamily: "Raleway",
fontSize: 20,
color: Colors.black,),
),
actions: <Widget>[
saveAndEditButton()
],
backgroundColor: Colors.lime[400],
);
}
Widget cardBody(){
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Rest Day"),
Checkbox(
value: _checkBoxValue,
onChanged: (value){
setState(() {
_checkBoxValue = value;
});
},
)
],
),
),
_checkBoxValue?Container():
SearchableDropdown.multiple(
hint: "Select Exercise",
style: TextStyle(color: Colors.black),
items: widget.exerciseList.map<DropdownMenuItem>((item){
return DropdownMenuItem(
child: Text(item.name), value: item
);
}).toList(),
selectedItems: _selectedExercises,
onChanged: (values){
setState(() {
_selectedExercises = values;
});
},
isExpanded: true,
dialogBox: true,
),
],
);
}
#override
Widget build(BuildContext context) {
print("<><><><><><><><><><><>${widget.weekDay} called");
return Card(
elevation: 8.0,
child: Form(
key: GlobalKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
cardHeader(),
_successfulPost?Container():cardBody()
],
),
),
);
}
}
As you can see, I've tried callBack from parent widget which increases or decrease no of form saved from each of the child widget. It does the work but, when one form is saved, parent state is modified and all other children got rebuild which is unnecessary in my opionion. What's the best way to do it?
Try to use GlobalKey instead of UniqueKey for each RoutineFormCard. It will help you to access the state of each RoutineFormCard. You can do it like this :
// 1. In the top of your CreateRoutineScreen file, add this line (make your RoutineFormCardState class public before)
final List<GlobalKey<RoutineFormCardState>> routineFormCardKeys = <GlobalKey<RoutineFormCardState>>[
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
];
// 2. Then construct your RoutineFormCard using the right key
RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key: routineFormCardKeys[index]
);
// 3. Now you can create a method in CreateRoutineScreen which will check the state of all RoutineFormCard
bool _allRoutineFormCardsCompleted() {
bool result = true;
for (int i = 0; i < 7; i++)
result = result && routineFormCardKeys[i].currentState.isPostSuccessful;
return result;
}
// 4. Finally use the result of the previous method where you want to move on another page
I'm sharing a quick idea to solve your problem, I've not tested it, but I'm ready to improve the answer if needed
Hope this will help!

how to refresh state on Navigator.Pop or Push in flutter

Here I have two pages first is called BSP_signup_terms page and the second is Bsp_Service_page. when I am on BSP_signup_terms on that page I have to select some checkbox based on the selected checkbox it will show me some data. but problem is that it will show me the complete data but when I get back to the BSP_signup_terms from Bsp_signup_page and I am changing the checkbox and then again when I am pressing next button it will not change the result it same as the previous result.
Here is the Image of Output Page
In this image I've attached both screen output when I am selecting only one checkbox it will render some value in service page and when I am back to the Terms and Condition page and select one more checkbox then it will not updating service page
Here is the code I've tried.
BSP_Signup_Terms_Page
class BspLicensedSignupTermsPage extends StatefulWidget {
static const String routeName = "/bspLicensedSignupTerms";
final BspSignupCommonModel bspSignupCommonModel;
BspLicensedSignupTermsPage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspLicensedSignupTermsPageState createState() =>
_BspLicensedSignupTermsPageState();
}
class _BspLicensedSignupTermsPageState
extends State<BspLicensedSignupTermsPage> {
#override
void initState() {
super.initState();
}
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool _isWalkIn = false;
bool _isHome = false;
bool _isOnDemand = false;
Widget _buildselectcheckbox() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['selectcheck']
['translation'],
);
}
// Walkin
_onCustomerWalkin(value) {
setState(() {
_isWalkIn = value;
});
}
Widget _buildCustomerWalkIn() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerWalkIn']['translation'],
onChanged: (value) {
print(value);
_onCustomerWalkin(value);
},
validate: false,
);
}
// Home
_onCustomerInHome(value) {
setState(() {
_isHome = value;
});
}
Widget _buildCustomerInHome() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerInHome']['translation'],
onChanged: (value) {
_onCustomerInHome(value);
},
validate: false,
);
}
Widget _buildCustomerInHomeHelp() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['businesscheckhelp']
['translation'],
);
}
// On Demand
_onCustomerOnDemand(value) {
setState(() {
_isOnDemand = value;
});
}
Widget _buildBusinessOnDemand() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['BusinessOnDemand']['translation'],
onChanged: (value) {
_onCustomerOnDemand(value);
},
validate: false,
);
}
Widget _buildBusinessOnDemandHelp() {
return Text(AppConstantsValue.appConst['bsplicensedsignupterms']
['businessprovidehelp']['translation']);
}
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text("Bsp Licensed Signup Terms and Condition"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
centerTitle: true,
);
final bottomNavigationBar = Container(
height: 56,
//margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
_formKey.currentState.reset();
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
if (_formKey.currentState.validate()) {
if (_isHome == false &&
_isOnDemand == false &&
_isWalkIn == false) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('Select Service'),
content: Text(
'Please select atleast one service type to proceed next',
),
));
} else {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.isWalkin = _isWalkIn;
model.isHome = _isHome;
model.isOnDemand = _isOnDemand;
print(model.toJson());
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BspServicePage(bspSignupCommonModel: model),
),
);
}
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Stack(
children: <Widget>[
SingleChildScrollView(
child: SafeArea(
child: Form(
autovalidate: true,
key: _formKey,
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: new Container(
decoration: BoxDecoration(
borderRadius: new BorderRadius.circular(25)),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildselectcheckbox(),
_buildCustomerWalkIn(),
_buildCustomerInHome(),
_buildCustomerInHomeHelp(),
_buildBusinessOnDemand(),
_buildBusinessOnDemandHelp(),
],
),
),
),
),
),
),
),
],
),
),
);
}
}
BSP_Service_Page
class BspServicePage extends StatefulWidget {
static const String routeName = "/bspService";
final BspSignupCommonModel bspSignupCommonModel;
BspServicePage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspServicePageState createState() => _BspServicePageState();
}
class _BspServicePageState extends State<BspServicePage> {
List<int> servicesIds = [];
Map<String, bool> selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
bool refreshservices = true;
#override
void initState() {
super.initState();
}
void _showErrorDialog(String message) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('An Error Occurred!'),
content: Text(message),
),
);
}
void refresh() {
setState(() {
refreshservices = !refreshservices;
});
}
#override
Widget build(BuildContext context) {
var _bspServiceBloc = new BspServiceBloc();
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
},
defaultBar: AppBar(
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
refresh();
NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
final bottomNavigationBar = Container(
height: 56,
// margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
print('reseting the state');
setState(() {
selection = {};
servicesIds = [];
});
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.servicesIds = servicesIds;
model.services = selectedServices;
print('servicesIds at the next button');
print(servicesIds);
print(model.toJson());
if (servicesIds.length == 0) {
_showErrorDialog(
'You need to select at least one service to proceed next!');
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BusinessProfilePage(
bspSignupCommonModel: model,
),
),
);
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: new BspServiceScreen(
bspServiceBloc: _bspServiceBloc,
bspSignupCommonModel: widget.bspSignupCommonModel,
servicesIds: servicesIds,
selection: selection,
searchQuery: _searchText,
selectedServices: selectedServices,
refresh: refresh,
),
);
}
}
Bsp_service_screen
class BspServiceScreen extends StatefulWidget {
final BspServiceBloc _bspServiceBloc;
final String searchQuery;
final List<int> servicesIds;
final Map<String, bool> selection;
final BspSignupCommonModel bspSignupCommonModel;
final List<BspServices.Service> selectedServices;
final Function refresh;
const BspServiceScreen({
Key key,
#required BspServiceBloc bspServiceBloc,
#required this.bspSignupCommonModel,
#required this.servicesIds,
#required this.selection,
#required this.selectedServices,
#required this.refresh,
this.searchQuery,
}) : _bspServiceBloc = bspServiceBloc,
super(key: key);
#override
BspServiceScreenState createState() {
return new BspServiceScreenState(_bspServiceBloc);
}
}
class BspServiceScreenState extends State<BspServiceScreen> {
final BspServiceBloc _bspServiceBloc;
BspServiceScreenState(this._bspServiceBloc);
// Map<String, bool> _selection = {};
#override
void initState() {
super.initState();
bool isHome = widget.bspSignupCommonModel.isHome;
bool isWalkIn = widget.bspSignupCommonModel.isWalkin;
bool isOnDemand = widget.bspSignupCommonModel.isOnDemand;
this._bspServiceBloc.dispatch(LoadBspServiceEvent(
countryId: 1,
isHome: isHome,
isOnDemand: isOnDemand,
isWalkin: isWalkIn,
));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BspServiceBloc, BspServiceState>(
bloc: widget._bspServiceBloc,
builder: (
BuildContext context,
BspServiceState currentState,
) {
if (currentState is UnBspServiceState) {
return Center(child: CircularProgressIndicator());
}
if (currentState is ErrorBspServiceState) {
return new Container(
child: new Center(
child: new Text(currentState.errorMessage ?? 'Error'),
),
);
}
if (currentState is InBspServiceState) {
// print(
// 'in bsp service state, ${currentState.bspServices.servicesByCountry.length}');
if (currentState.bspServices.servicesByCountry.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
} else {
return new Container(
child:
_renderServices(currentState.bspServices.servicesByCountry),
);
}
}
return Container();
},
);
}
List<ServicesByCountry> finalList = new List();
ListView _renderServices(List<ServicesByCountry> lovCountryServices) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.searchQuery != '') {
finalList.clear();
lovCountryServices.forEach((ServicesByCountry data) {
if (data.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
} else {
data.services.forEach((ServiceList.Service services) {
if (services.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
}
});
}
});
} else {
setState(() {
finalList.clear();
finalList.addAll(lovCountryServices);
});
}
});
return ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: finalList.length,
itemBuilder: (BuildContext context, int index) {
ServicesByCountry item = finalList[index];
List itemsList = item.services;
return ExpansionTile(
title: Text(item.name),
children: List.generate(itemsList.length, (i) {
widget.selection[itemsList[i].name] =
widget.selection[itemsList[i].name] ?? itemsList[i].isSelected;
return CheckboxListTile(
title: Text(itemsList[i].name),
value: widget.selection[itemsList[i].name],
onChanged: (val) {
setState(() {
widget.selection[itemsList[i].name] = val;
if (val) {
widget.servicesIds.add(itemsList[i].id);
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
SubCategory subService = new SubCategory(
id: itemsList[i].id,
name: itemsList[i].name,
);
List<SubCategory> subCategories = [];
if (services.length == 0) {
subCategories.add(subService);
widget.selectedServices.add(
new BspServices.Service(
mainCategory: item.name,
mainCategoryId: item.id,
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
widget.servicesIds.removeWhere((service) {
return service == itemsList[i].id;
});
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == itemsList[i].id;
});
}
});
print('widget.servicesIds after set state');
print(widget.servicesIds);
},
);
}),
);
},
);
}
}
You can use setState() after return to the first page:
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2())).then((value) {
setState(() {
// refresh state
});
});
Please try below code:-
First you add one method async method:-
void redirectToNextScreen() async {
final Route route = MaterialPageRoute(
builder: (context) => BspServicePage(bspSignupCommonModel: model));
final result = await Navigator.push(mContext, route);
try {
if (result != null) {
if (result) {
//Return callback here.
}
}
} catch (e) {
print(e.toString());
}
}
Then Next you can call this method in "BSP_Signup_Terms_Page" on Next button Pressed event.
Second you can add below line in "BspServicePage" screen Next and Cancel Events.
Navigator.pop(mContext, true); //true means refresh back page and false means not refresh.