Empty container consuming space in gridview builder when searching through a list - flutter

Code :
class MobileCourses extends StatefulWidget { const MobileCourses({ Key? key }) : super(key: key);
#override _MobileCoursesState createState() =>
_MobileCoursesState(); }
class _MobileCoursesState extends State<MobileCourses> { String searchCourse = ""; TextEditingController searchController = TextEditingController(); #override Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
searchBar(),
Expanded(
child: Container(
padding: EdgeInsets.all(15),
child : FutureBuilder(
future: Networking.getAllCourses(),
builder: (_,snapshot)
{
if(snapshot.hasData)
{
List<SpecificCourse>? specificCourse = snapshot.data as List<SpecificCourse>?;
return GridView.builder(
physics: BouncingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
crossAxisSpacing: 15
),
itemCount: specificCourse![0].courses.length ,
itemBuilder: (_,index)
{
return specificCourse[0].courses[index].course.toLowerCase().contains(searchCourse) ? MobileContainer(course: specificCourse[0].courses[index].course):Container(color: Colors.red,),
// over here i am searching for my required course
},
);
}
else
{
return CircularProgressIndicator();
}
}
),
),
),
],
),
);
}
}
Search Bar :-
Widget searchBar() {
return Padding(
padding: const EdgeInsets.only(left : 15.0,right: 15.0 , top: 10.0 ,bottom: 10.0),
child: Container(
child: TextField(
decoration: InputDecoration(
hintText: "Search...",
prefixIcon: Icon(Icons.search)
),
onChanged: (value){
setState(() {
searchCourse = value.toLowerCase();
});
},
controller: searchController,
),
),
);
}
I want to implement search function inside my gridview and i have tried to implement it in the above code and the ui is looking something like this
but when i search "m" it should return me only MBA but it is returning container too i do not want these container(colored in red) i only want MBA .......I have explicity given container red for better understanding
Container without red color
i only want to return MBA but the empty container is consuming space. Please help ! and sorry for these big images i do not know how to decrease their size

Try to below code its working for me for ListView.Builder Please try to change it Gridview hope it helps you:
Create one class with your JSON Data :
class User {
String name;
int id;
int totalLeads;
User({
this.id,
this.name,
this.totalLeads,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
id: int.parse(json['userId']),
totalLeads: int.parse(json['total']),
);
}
}
Create one class that you declare voidCallback function:
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({this.milliseconds});
run(VoidCallback action) {
if (null != _timer) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
Create Stateful Widget Class
class AllAgents extends StatefulWidget {
AllAgents() : super();
#override
AllAgentsState createState() => AllAgentsState();
}
class AllAgentsState extends State<AllAgents> {
final _debouncer = Debouncer(milliseconds: 500);
List<User> users = [];
List<User> filteredUsers = [];
static String url = 'your API url here';
static Future<List<User>> getAllLeagentsList() async {
try {
final response = await http.get(url);
if (response.statusCode == 200) {
List<User> list = parseAgents(response.body);
return list;
} else {
throw Exception('Error');
}
} catch (e) {
throw Exception(e.toString());
}
}
static List<User> parseAgents(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
#override
void initState() {
super.initState();
getAllLeagentsList().then((usersFromServer) {
setState(() {
users = usersFromServer;
filteredUsers = users;
});
});
}
// Declare Your Widget here
Column(children:[
//Search Bar to List of typed User
Container(
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
child: TextField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.grey,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide(
color: Colors.blue,
),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
contentPadding: EdgeInsets.all(15.0),
hintText: 'Search ',
),
onChanged: (string) {
_debouncer.run(() {
setState(() {
filteredUsers = users
.where((u) => (u.name
.toLowerCase()
.contains(string.toLowerCase())))
.toList();
});
});
},
),
),
//Lists of Agents
Expanded(
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
itemCount: filteredUsers.length,
itemBuilder: (BuildContext context, int index) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: Colors.grey[300],
),
),
child: Padding(
padding: EdgeInsets.all(5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
leading: Container(
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.white,
border: Border.all(color: Colors.blue),
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 2.0,
spreadRadius: 1.0,
offset: Offset(
1.0,
1.0,
), // shadow direction: bottom right
)
],
),
child: Text(filteredUsers[index].name[0]),
),
title: Text(
filteredUsers[index].name,
style: TextStyle(fontSize: 16),
),
)
],
),
),
);
},),),
],),
}
here is my screen without search ->
here is my screen with search ->

Related

Filter search filter data showing from Api using provider

how to implement search filter with API using provider? i have post Api when user hit Api it send back data to the user
My api
Future<List<Searchmodel>>Search(String keyword) async {
final response=await http.post(Uri.parse('https://example.com/api/library/search'),body: {
'Keyword': keyword,
});
if(response.statusCode==200){
final json=response.body..toString().replaceAll("\n","");
var data=jsonDecode(json);
final Iterable fetch = data["success"];
return fetch.map((category) => Searchmodel.fromJson(category)).toList();
}else{
throw Exception("Unable to perform request!");
}
}
//model class
class Searchmodel {
Searchmodel({
required this.id,
required this.name,
required this.file,
required this.categoryId,
required this.subcategoryId,
required this.userId,
required this.status,
required this.createdAt,
required this.updatedAt,});
Searchmodel.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
file = json['file'];
categoryId = json['category_id'];
subcategoryId = json['subcategory_id'];
userId = json['user_id'];
status = json['status'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
int? id;
String? name;
String? file;
int? categoryId;
int? subcategoryId;
String? userId;
String? status;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['file'] = file;
map['category_id'] = categoryId;
map['subcategory_id'] = subcategoryId;
map['user_id'] = userId;
map['status'] = status;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
//UI
import 'package:flutter/material.dart';
import 'package:livinghopegeetzaboor/Statemanagment/Library/LibraryProvider.dart';
import 'package:livinghopegeetzaboor/constant/AppColor.dart';
import 'package:livinghopegeetzaboor/sacreen/Geet/Geetsubcategory.dart';
import 'package:livinghopegeetzaboor/services/Services.dart';
import 'package:provider/provider.dart';
class Homepage extends StatefulWidget {
#override
State<Homepage> createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: Image.asset('images/logo.png')
),
// Note: Same code is applied for the TextFormField as well
// Note: Same code is applied for the TextFormField as well
// Note: Same code is applied for the TextFormField as well
Padding(
padding: EdgeInsets.all(40),
child:Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: AppColor.color,
),
height: 80,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
suffixIcon: Icon(Icons.search,
size: 40,
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.white, width:2),
),
filled: true, //<-- SEE HERE
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
hintText: 'Search for any songs',
),
),
),
SizedBox(width: 3,),
// Container(
// height: 59,
// width: 59,
// color:Colors.white,
// constraints: const BoxConstraints(
//
// ),
// // child: Icon(
// // Icons.search_sharp,
// // size: 40,
// // color: AppColor.color,
// // ),
// )
],
),
),
),
),
Consumer<LibraryProvider>(
builder:(BuildContext context, value, Widget? child) {
if(value.isloading){
return CircularProgressIndicator(
color: AppColor.color,
);
}else{
return GridView.builder(
itemCount:value.library.length,
scrollDirection: Axis.vertical,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0
),
itemBuilder: (BuildContext context, int index){
var subtitle=value.library[index].name2;
return Padding(
padding: EdgeInsets.all(10),
child: Material(
color: Colors.white.withOpacity(0.0),
child: InkWell(
splashColor: Colors.orange,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.black,
),
//color: Colors.black,
height:200,
width: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(5),
child: Center(
child: Text(value.library[index]!.name.toString(),style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18
),),
),
),
SizedBox(height: 4,),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Center(
child: subtitle!=null ? Text(value.library[index].name2.toString(),style: TextStyle(
color: Colors.white,
fontSize: 12
),):Text('',style: TextStyle(
color: Colors.white,
fontSize: 12
),)
),
),
],
),
),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Geetsubcategory(id: value.library[index].id,)),
);
},
),
),
);
},
);
}
},
)
],
),
// your main content
),
),
)
);
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
Provider.of<LibraryProvider>(context,listen: false).fetchlibary();
// Add Your Code here.
});
}
}
You need to add the api call from inside your provider function.
Like this
class SearchProvider with ChangeNotifier, DiagnosticableTreeMixin {
List<dynamic>? _items;
List<dynamic>? get items => _items;
void searchItems(String val) async {
_items = await ApiCall.search(val);
notifyListeners();
}
}
You can create a common object of provider on the page to use throughout your page like this.
SearchProvider _provider = SearchProvider();
and use it like this in your provider and consumer on the same page
ChangeNotifierProvider<SearchProvider>(
create: (_) => _provider,
child: Column(
children: [
... // other code
Consumer<SearchProvider>(
builder: (context, provider, w) {
if (provider.items != null && provider.items!.isNotEmpty) {
return SearchList(list: provider.items);
}
return const Center(child: Text("Nothing found"));
},
),
]),
),
Then setup one TextField to type & fire search query. You can setup onSubmitted callback of the Textfield to call your provider method.
TextField(
onSubmitted: (val) {
if(val.length > 0){
_provider.searchItems(val);
}
else {
_provider.getAll();
}
},
decoration: const InputDecoration(
labelText: 'Search',
suffixIcon: Icon(Icons.search),
),
Using this code will call the provider method when you press enter after typing some query in TextField. That provider method will then call the api & store the response in a List then call notifyListeners() from that method to notify all the consumers. Those consumer then try to read the list & create a listview if they find data in the list.
Here is an example of full working code:
https://dartpad.dev/?id=a5950f2da95d58e32dc9176433d9bcb3
I have also added the onload list which can be filtered from api itself & it user delete search query it will again call the full list api for data.
Hope this will help you understand things better using provider.

Flutter with Firebase, Search by multiple queries

I am trying to create a search feature that allows users to search by their username, email, designation or company. I intend to let users have the flexibility of typing any of those into the search bar and search for their target. However, what i am facing is that my code below only calls on the first function and displays results only for username (username is the first function here, can be interchanged with other functions and it will call according to that). my code is below and thanks for all the help in advance.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:ib_club/services/database.dart';
import 'package:ib_club/widgets/widget.dart';
class SearchScreen extends StatefulWidget {
#override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
DatabaseMethods databaseMethods = new DatabaseMethods();
TextEditingController searchTextEditingController =
new TextEditingController();
QuerySnapshot<Map<String, dynamic>> searchSnapshot;
initiateUsernameSearch() {
databaseMethods
.getUserByUsername(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateEmailSearch() {
databaseMethods
.getUserByEmail(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateDesignationSearch() {
databaseMethods
.getUserByDesignation(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateCompanySearch() {
databaseMethods
.getUserByCompany(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateSearch() {
initiateUsernameSearch();
initiateEmailSearch();
initiateDesignationSearch();
initiateCompanySearch();
}
// Create chatroom, send user to conversation screen, pushreplacement
/*createChatroomAndStartConversation(String userUsername) {
List<String> users = [
userUsername,
];
databaseMethods.createChatRoom();
}*/
Widget searchList() {
return searchSnapshot != null
? ListView.builder(
itemCount: searchSnapshot.docs.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return SearchTile(
userUsername: searchSnapshot.docs[index].data()["username"],
userEmail: searchSnapshot.docs[index].data()["email"],
userDesignation:
searchSnapshot.docs[index].data()["designation"],
userCompany: searchSnapshot.docs[index].data()["company"],
);
})
: Container(
/*child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
const Color(0XffFBD24F))))*/
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBarMain(context),
body: Container(
child: Column(children: [
SizedBox(
height: 16,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
const Color(0x36FFFFFF),
const Color(0x0FFFFFF)
]),
borderRadius: BorderRadius.circular(40)),
child: TextField(
controller: searchTextEditingController,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
hintText: "Search User",
hintStyle: TextStyle(color: Colors.white54),
border: InputBorder.none),
),
)),
GestureDetector(
onTap: () {
initiateSearch();
},
child: Container(
height: 45,
width: 45,
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
const Color(0x36FFFFFF),
const Color(0x0FFFFFF)
]),
borderRadius: BorderRadius.circular(45)),
child: Icon(Icons.search,
size: 30, color: const Color(0XffFBD24F))),
),
],
),
),
searchList()
]),
));
}
}
class SearchTile extends StatelessWidget {
final String userUsername;
final String userEmail;
final String userDesignation;
final String userCompany;
SearchTile(
{this.userUsername,
this.userEmail,
this.userDesignation,
this.userCompany});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [const Color(0x36FFFFFF), const Color(0x0FFFFFF)]),
borderRadius: BorderRadius.horizontal()),
child: Row(children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userUsername,
style: mediumWhiteTextStyle(),
),
Text(
userEmail,
style: mediumWhiteTextStyle(),
),
Text(
userDesignation,
style: mediumWhiteTextStyle(),
),
Text(
userCompany,
style: mediumWhiteTextStyle(),
)
],
),
),
Spacer(),
GestureDetector(
onTap: () {},
child: Container(
decoration: BoxDecoration(
color: const Color(0XffFBD24F),
borderRadius: BorderRadius.circular(30)),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(
"Message",
style: mediumTextStyle(),
),
),
)
]),
);
}
}
I could not give a straight answer base on your code but I have a search concept, which I am hoping to help you in any way.
In my case, I always fetch all my item data from firebase and put it in "overallItems" (which is a List of Item Model). From there, I can now start to filter/search my list and return the result.
The concept I use is like this:
List<Item> filterItems() {
//first, I would want to have a temporary holder for a copy of my original items
final List<Item> itemHolder = List<Item>.from(overallItems);
//If user has title input on the search bar
if (titleKeyword.text != '') {
//then this for loop will iterate to all my data
for (final item in List<Item>.from(itemHolder)) {
//will check each title from the list
if (!item.title.toLowerCase().contains(titleKeyword.text.toLowerCase())) {
//elimate items that did not qualify
itemHolder.remove(item);
}
}
}
//so after the first if, itemHolder will be left with the result....
//now, I would also like to search the seller name.
//then, I would just have to repeat the process above
//Searching for Seller Name
if (sellerKeyword.text != '') {
for (final item in List<Item>.from(itemHolder)) {
if (!item.sellerName
.toLowerCase()
.contains(sellerKeyword.text.toLowerCase())) {
itemHolder.remove(item);
}
}
}
//the itemHolder list will have the result
return itemHolder;
}
In the end, we will have the search result after the elimination process.

Why does image picker in stateless widget with bloc causes late initialization error?

While using ImagePicker https://pub.dev/packages/image_picker, picking the image works.
But when I put this into the sink it crashes with the following error:
LateInitializationError: Field '_textSeparators#411091221' has already been initialized.
When I remove the FormBuilder it works. So maybe has to do something with the FormBuilder.
Would be great if anyone can point me into the right direction here.
Widget:
class CreatePost extends StatelessWidget {
final GlobalKey<FormBuilderState> _formkey =
GlobalKey<FormBuilderState>(debugLabel: 'GlobalFormKey #SignIn ');
const CreatePost({required Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<PostBloc, PostState>(
builder: (context, state) {
if (state is AddImagePost) {
return Scaffold(
appBar: AppBar(
iconTheme: const IconThemeData(color: Colors.black,),
),
body: GestureDetector(
onTap: () {
ImagePicker().pickImage(
source: ImageSource.gallery,)
.then((im) =>context.read<PostBloc().add(SetImagePost(im!)));
},
child:
Column(
children: [
FormBuilder(key: _formkey, "content")
_imageWidget(state.memoryImage),
)],
),);
}
},
);
}
}
Event:
class SetImagePost extends PostEvent {
const SetImagePost(this.image);
final XFile image;
}
State:
class AddImagePost extends PostState {
final MemoryImage memoryImage;
AddImagePost(this.image) : super();
}
BloC:
class PostBloc extends Bloc<PostEvent, PostState> {
final XFile image;
PostBloc({required this.image})
:super(CreatePostInit()){
on<SetImagePost>(_setImagePost);
}
void _setImagePost(SetImagePost event, Emitter<PostState> emit) async {
var memoryImage await apiRepository.postImage(event.image);
emit(AddImagePost(memoryImage));
}
}
Adding the different widgets to the FormBuilder step by step, it turned out that exactly the following widget is causing the error:
TextFieldTags(
textfieldTagsController: tagsControllerAnswers,
initialTags: const ["yes","no","maybe"],
textSeparators: const [' ', ','],
letterCase: LetterCase.small,
validator: (String tag) {
if (tagsController.getTags!.contains(tag)) {
return 'you already entered that';
}
if(tagsControllerAnswers.getTags!.length > 4){
return 'Please enter no more than 5 tags.';
}
return null;
},
inputfieldBuilder:
(context, tec, fn, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelete) {
return TextField(
controller: tec,
focusNode: fn,
decoration: InputDecoration(
isDense: true,
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: askTextFieldBorderFocused, width: 3.0),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: askTextFieldBorder, width: 2.0),
),
errorText: error,
prefixIconConstraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.74),
prefixIcon: tags.isNotEmpty
? SingleChildScrollView(
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(
children: tags.map((String tag) {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(3.0),
),
color: tagColor,
),
margin: const EdgeInsets.symmetric(
horizontal: 5.0),
padding: const EdgeInsets.symmetric(
horizontal: 5.0, vertical: 5.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
child: Text(
tag,
style: CustomTextStyle.bodyText1(context)?.copyWith(color: tagFont ),
),
onTap: () {
// print("$tag selected");
},
),
const SizedBox(width: 3.0),
InkWell(
child: const Icon(
Icons.close_rounded,
size: 20.0,
color: tagFont,
),
onTap: () {
onTagDelete(tag);
},
)
],
),
);
}).toList()),
)
: null,
),
onChanged: onChanged,
onSubmitted: onSubmitted,
);
});
},
),
Moving the "TextFieldTags" out of the FormBuilder solved the issue :)

Filter in a listView.builder inside Future Builder flutter

I am trying to implement a simple search bar funcionality inside a FutureBuilder since I am fetching data from an API.
However, I am unable to retrieve data from the search funcionality even though I retrieve data from my future function fetchdata() and the I get all the data that contains a string in glossarListOnSearchwhich is plenty of data. The result of my search is empty. What am I missing?
Here is what I get in glossarListOnSearch List:
enter image description here
I want to filter by "_breed".
Here is the piece of code where I implement the search:
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
TextEditingController textController = TextEditingController();
List<dynamic> glossarListOnSearch = [];
bool _firstSearch = true;
final List<String> _filterList = [];
String _query = '';
#override
void initState() {
super.initState();
fetchData(AppConstants.APIBASE_URL);
}
_MainPageState() {
textController.addListener(() {
if (textController.text.isEmpty) {
setState(() {
_firstSearch = true;
_query = '';
});
} else {
setState(() {
_firstSearch = false;
_query = textController.text;
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blueAccent],
//colors: [Colors.blueAccent,Colors.lightBlue, Colors.blueGrey],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
)),
bottom: PreferredSize(
preferredSize: Size(0, 60),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
child: Container(
//height: 50,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.white60, Colors.white70],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
borderRadius: BorderRadius.circular(50),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
child: TextField(
textAlign: TextAlign.left,
onChanged: (value) {
setState(() {
_query = value;
});
},
controller: textController,
decoration: const InputDecoration(
border: InputBorder.none,
errorBorder: InputBorder.none,
enabledBorder: InputBorder.none,
contentPadding: EdgeInsets.all(0),
hintText: 'Search'),
),
),
),
),
),
),
backgroundColor: Colors.white,
body: Column(children: [
Container(
margin: const EdgeInsets.only(top: 60, bottom: 15),
padding: const EdgeInsets.only(left: 20, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(
child: Container(
width: MediaQuery.of(context).size.width -
(MediaQuery.of(context).size.width / 3.5),
height: 0,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(Dimensions.radius20),
color: Colors.white),
),
)
],
),
),
textController.text.isEmpty && _filterList.isEmpty
? const Expanded(
child: SingleChildScrollView(
child: BookPageBody(),
))
: FutureBuilder(
future: fetchData(AppConstants.APIBASE_URL),
builder: (context, snapshot) {
_filterList.clear();
glossarListOnSearch.clear();
for (int i = 0; i < BreedList.length; i++) {
var item = BreedList[i].breed;
//here I add to _filterList all the results that contains
//that letter
if ((item.toString().toLowerCase())
.contains(_query.toLowerCase())) {
_filterList.add(item.toString());
glossarListOnSearch = _filterList;
}
}
return ListView.builder(
itemCount: glossarListOnSearch.length,
itemBuilder: (BuildContext context, int index) {
if (glossarListOnSearch.isNotEmpty) {
return Column(children: [for ( var dog in glossarListOnSearch) Text(dog.toString())]
);
}
return const Expanded(
child:SingleChildScrollView(
child: BookPageBody()));
});
})
]));
}
How would you filter glossarListOnSearch??
I think your problem is that you set the glossarListOnSearch equal to the result every time in the for loop. So basically all changes until the last one get overwritten. Or am I missing something? It's really hard to tell what's going on here. Also aren't you fetching the data again and again everytime the text controller goes from empty to not empty and back?
Or are you adding the whole list to glossarListOnSearch in every iteration? In this case it would make sense to only do it once at the end. Can you show us the rest of the code? ie. the _filterList?
Maybe it would make sense to load the data beforehand and handle all of this in a Provider.

Not able to change a value in one page with respect to the value from another page in flutter

i want to change the indexvalue (pictogramindex) of one page when we click nextbutton on another screen.I will explain briefly , I have 2 screens in my scenario the first screen contains an image and it's name , a textfield and nextbutton (i have provided a dummy data contains a list of image and it's names) the logic behind this is , when we complete the textfield box and click next button(after validate) the textfield value checks with the correctvalue which i was given in the dummy data and show it's synonym which also provided. when we click the next button we will go to another page which contains the correct answer(passed from first page) and a textfield in this the user can write about the correct answer ( validated) when click next button in this page (till this my applicationworks perfectly) i want to load the first page with it's index updated (+1) which i initialised as 0 (var pictogramindex=0). But in my case when coming back to first page the index is not updating it will automatically stores the initialised value. what i want is i want to update index on the first page when i click next button in the Second page .
my source code of first screen is shown here
class Pictogramscreen extends StatefulWidget {
final int length;
const Pictogramscreen({Key key, this.length}) : super(key: key);
#override
_PictogramscreenState createState() => _PictogramscreenState();
}
class _PictogramscreenState extends State<Pictogramscreen> {
#override
final _Key = GlobalKey<FormState>();
Color defaultcolor = Colors.blue[50];
Color trueColor = Colors.green;
Color falseColor = Colors.red;
Widget defcorrect = Text('');
var pictogramindex = 0;
TextEditingController usertitleInput = TextEditingController();
nextPictogram() {
setState(() {
pictogramindex++;
});
}
fillColor() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defaultcolor = trueColor
: defaultcolor = falseColor;
});
}
correctText() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defcorrect = Text(pictdata[pictogramindex]['pictsynonym'])
: defcorrect = Text(pictdata[pictogramindex]['pictcorrectword']);
});
}
reset() {
setState(() {
defaultcolor = Colors.blue[50];
defcorrect = Text('');
usertitleInput.clear();
});
}
void description(BuildContext ctx) {
Navigator.of(context).pushNamed('/user-description', arguments: {
'id': pictdata[pictogramindex]['pictid'],
'word': pictdata[pictogramindex]['pictcorrectword']
});
}
Widget build(BuildContext context) {
int length = pictdata.length;
return Scaffold(
body: pictogramindex < pictdata.length
? ListView(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Card(
margin: EdgeInsets.only(top: 20),
child: Image.network(
pictdata[pictogramindex]['pictimg']),
),
SizedBox(
height: 10,
),
Text(
pictdata[pictogramindex]['pictword'],
style: TextStyle(
fontSize: 25,
),
),
SizedBox(
height: 10,
),
//Card(
//color: Colors.blue,
// child: TextField(
// decoration: InputDecoration.collapsed(
// hintText: 'type here'),
//textAlign: TextAlign.center,
// onSubmitted: (value) {
// usertitleInput = value;
// print(usertitleInput);
// },
// ),
//),
Form(
key: _Key,
child: TextFormField(
controller: usertitleInput,
validator: (usertitleInput) {
if (usertitleInput.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
fillColor: defaultcolor,
),
onFieldSubmitted: (value) {
usertitleInput.text = value;
fillColor();
correctText();
print(usertitleInput.text);
}),
),
SizedBox(
height: 10,
),
defcorrect,
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Key.currentState.validate()) {
description(context);
// nextPictogram();
reset();
}
//
//if (_Key.currentState.validate() == correctText()) {
// nextPictogram;
// }
},
child: Text('Next'),
)
],
),
),
],
)
: Center(
child: Text('completed'),
));
}
}
my source code of the second screen is show here
class Userinputscreen extends StatefulWidget {
final String id;
final String word;
const Userinputscreen({Key key, this.id, this.word}) : super(key: key);
#override
_UserinputscreenState createState() => _UserinputscreenState();
}
class _UserinputscreenState extends State<Userinputscreen> {
final _Keey = GlobalKey<FormState>();
TextEditingController userdescription = TextEditingController();
var pictogramindex;
void nextpict(BuildContext context) {
Navigator.of(context).pushNamed('/main-screen');
}
// void nextpict(BuildContext context, int index) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (ctx) => Pictogramscreen(
// index: i = 0,
// )));
// }
#override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final correctWord = routeArgs['word'];
return MaterialApp(
home: Scaffold(
body: ListView(children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 50),
child: Center(
child: Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.only(top: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
correctWord,
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 10,
),
Form(
key: _Keey,
child: TextFormField(
controller: userdescription,
validator: (userdescription) {
if (userdescription.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
),
onFieldSubmitted: (value) {
userdescription.text = value;
print(userdescription.text);
}),
),
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Keey.currentState.validate()) {
nextpict(context);
}
},
child: Text('Next'),
)
],
),
),
),
),
])),
);
}
}
If I get it right, you basically want to tell the initial page that it's state is updated(the index) elsewhere. You basically need to make your app "reactive".
As is said in Google Developers Tutorial:
One of the advantages of Flutter is that it uses reactive views, which you can take to the next level by also applying reactive principles to your app’s data model.
Use some sort of state management. You need to choose from and use either Bloc, InheritedWidget and InheritedModel, Provider(ScopedModel), or the like.
Check this article on flutter about state management, or this for a complete list of approaches