I'm trying to build a basic chat feature where all of a users chat messages are stored as documents in a "chats" collection. I have successfully implemented pagination to ensure I am not overpulling data until the user scrolls.
However, even though I have a StreamBuilder, new chat documents are not appearing automatically like they normally would. Why is the streambuilder not registering and displaying these new messages?
Here is my code:
class MotivatorChat extends StatefulWidget {
#override
_MotivatorChatState createState() => _MotivatorChatState();
}
class _MotivatorChatState extends State<MotivatorChat> {
Firestore firestore = Firestore.instance;
List<DocumentSnapshot> chats = [];
bool isLoading = false;
bool hasMore = true;
int documentLimit = 10;
DocumentSnapshot lastDocument;
ScrollController _scrollController = ScrollController();
StreamController<List<DocumentSnapshot>> _controller = StreamController<List<DocumentSnapshot>>();
Stream<List<DocumentSnapshot>> get _streamController => _controller.stream;
#override
void initState() {
super.initState();
getChats();
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.20;
if (maxScroll - currentScroll <= delta) {
getChats();
}
});
}
getChats() async {
if (!hasMore) {
print('No More Chats');
return;
}
if (isLoading) {
return;
}
setState(() {
isLoading = true;
});
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot = await firestore
.collection('chats')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.getDocuments();
} else {
querySnapshot = await firestore
.collection('chats')
.orderBy('timestamp', descending: true)
.startAfterDocument(lastDocument)
.limit(documentLimit)
.getDocuments();
print(1);
}
if (querySnapshot.documents.length < documentLimit) {
hasMore = false;
}
lastDocument = querySnapshot.documents[querySnapshot.documents.length - 1];
chats.addAll(querySnapshot.documents);
_controller.sink.add(chats);
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Pagination with Firestore'),
),
body: Column(children: [
Expanded(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController,
builder: (sContext, snapshot) {
print(snapshot.connectionState);
if (snapshot.hasData && snapshot.data.length > 0) {
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
height: 20,
child: Text(snapshot.data[index].data['text']),
),
);
},
);
} else {
return Center(
child: Text('No Data...'),
);
}
},
),
),
isLoading
? Container(
width: MediaQuery
.of(context)
.size
.width,
padding: EdgeInsets.all(5),
color: Colors.yellowAccent,
child: Text(
'Loading',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
)
: Container(),
]),
);
}
}
Updated StreamBuilder
StreamBuilder<List<DocumentSnapshot>>(
stream: _streamController,
builder: (sContext, snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
return Text("None");
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
} else if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData && snapshot.data.length > 0) {
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
height: 20,
child: Text(snapshot.data[index].data['text']),
),
);
},
);
} else {
return Center(
child: Text('No Data...'),
);
}
} else {
return Text("return list");
}
},
),
I try your code and set document limit to 20, it work fine.
Example on DartPad
If hasMore is false, the stream will not sink new data.
don't check hasMore
// if (!hasMore) {
// print('No More Chats');
// return;
// }
and check documents
// if (querySnapshot.documents.length < documentLimit) {
// hasMore = false;
// }
if (querySnapshot.documents.isEmpty) {
print('No More Chats');
setLoading(false);
return;
}
I test on DartPad, It sends data every 3 seconds.
See if this code is helpful:
class _MessagesState extends State<Messages> {
ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.offset >=
(_scrollController.position.maxScrollExtent) &&
!_scrollController.position.outOfRange) {
_getChats();
}
});
}
final StreamController<List<DocumentSnapshot>> _chatController =
StreamController<List<DocumentSnapshot>>.broadcast();
List<List<DocumentSnapshot>> _allPagedResults = [<DocumentSnapshot>[]];
static const int chatLimit = 10;
DocumentSnapshot? _lastDocument;
bool _hasMoreData = true;
Stream<List<DocumentSnapshot>> listenToChatsRealTime() {
_getChats();
return _chatController.stream;
}
void _getChats() {
final CollectionReference _chatCollectionReference = FirebaseFirestore
.instance
.collection("ChatRoom")
.doc(widget.chatRoomId)
.collection("channel");
var pagechatQuery = _chatCollectionReference
.orderBy('createdAt', descending: true)
.limit(chatLimit);
if (_lastDocument != null) {
pagechatQuery = pagechatQuery.startAfterDocument(_lastDocument!);
}
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot) {
if (snapshot.docs.isNotEmpty) {
var generalChats = snapshot.docs.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists) {
_allPagedResults[currentRequestIndex] = generalChats;
} else {
_allPagedResults.add(generalChats);
}
var allChats = _allPagedResults.fold<List<DocumentSnapshot>>(
<DocumentSnapshot>[],
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1) {
_lastDocument = snapshot.docs.last;
}
_hasMoreData = generalChats.length == chatLimit;
}
},
);
}
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder<List<DocumentSnapshot>>(
stream: listenToChatsRealTime(),
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting ||
chatSnapshot.connectionState == ConnectionState.none) {
return chatSnapshot.hasData
? Center(
child: CircularProgressIndicator(),
)
: Center(
child: Text("Start a conversation."),
);
} else {
if (chatSnapshot.hasData) {
final chatDocs = chatSnapshot.data!;
final user = Provider.of<User?>(context);
return ListView.builder(
controller: _scrollController,
reverse: true,
itemBuilder: (ctx, i) {
Map chatData = chatDocs[i].data() as Map;
return MessageBubble(
username: chatData['username'],
message: chatData['text'],
isMe: chatData['senderId'] == user!.uid,
key: ValueKey(chatDocs[i].id));
},
itemCount: chatDocs.length,
);
} else {
return CircularProgressIndicator();
}
}
}),
);
}
}
I referred to this answer: Pagination in Flutter with Firebase Realtime Database
Related
I am getting a QuerySnapShot from a Firestore subcollection inside a StreamBuilder from another query, as follows:
//ver fuegos
var estePost = listaFiltrada[indexPost].post_id;
int numero_fuegos = 0;
FirebaseFirestore.instance
.collection('posts')
.doc(estePost)
.collection('fuegos')
.get()
.then((value) => {
if (value.size > 0)
{
setState(() {
numero_fuegos = value.size;
}),
print("tiene fuegos: " +
numero_fuegos.toString()),
}
else
{
setState(() {
numero_fuegos = value.size;
}),
}
});
There are 3 items for value.size.
Here you have the print output:
I/flutter (29797): tiene fuegos: 3
The issue is that putting the variable numero_fuegos inside a text widget, the output is always 0.
EDIT:
class ListaPosts extends StatefulWidget {
#override
_ListaPostsState createState() => _ListaPostsState();
}
class _ListaPostsState extends State<ListaPosts> {
String _miId = "";
#override
initState() {
// TODO: implement initState
super.initState();
FirebaseAuth.instance.idTokenChanges().listen((User user) {
if (user == null) {
} else {
setState(() {
_miId = user.uid;
});
}
});
}
#override
Widget build(BuildContext context) {
final postsProvider = Provider.of<PostsProvider>(context);
return Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.only(top: 0.0),
child: Column(
children: [
StreamBuilder<List<Post>>(
stream: postsProvider.posts,
builder: (context, snapshot) {
if (snapshot.data != null &&
snapshot.data.isNotEmpty &&
ConnectionState.done != null) {
List<Post> listaInicial = snapshot.data;
List<Post> listaFiltrada = [];
listaFiltrada = listaInicial;
return Padding(
padding: const EdgeInsets.all(0.0),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 140,
child: ListView.builder(
itemCount: listaFiltrada.length,
itemBuilder: (context, indexPost) {
bool es_ambassador = listaFiltrada[indexPost]
.post_autor_is_ambassador;
//ver fuegos
var estePost = listaFiltrada[indexPost].post_id;
int numero_fuegos = 0;
FirebaseFirestore.instance
.collection('posts')
.doc(estePost)
.collection('fuegos')
.get()
.then((value) => {
if (value.size > 0)
{
numero_fuegos = value.size,
print("tiene fuegos: " +
numero_fuegos.toString()),
print("tiene fuegos: " +
value.size.toString()),
}
else
{
setState(() {
numero_fuegos = value.size;
}),
}
});
And that is the Stream:
//Get Posts Entries
Stream<List<Post>> getPosts() {
return _db
.collection('posts')
.orderBy("post_fecha", descending: true)
.snapshots()
.map((snapshot) =>
snapshot.docs.map((doc) => Post.fromJson(doc.data())).toList());
}
I have problem with lazy loading. I tried may ways and packages like LazyLoadingScollview (example here), Pagewise etc.
What the problem is (probably easy to solve).
I have list of 50 events and I want to display only 10 of it at once, than add more (ex another 10) while reach the bottom of the list. (I cannot change limit from 50 to 10 and change it later because it's refreshing whole screen - need to fetch all at once).
To be more clear - need update count value dynamicly.
class DiscountTab extends DiscountsBaseTab {
#override
_DiscountTabState createState() => _DiscountTabState();
}
class _DiscountTabState extends DiscountsBaseTabState
with SnackBarMixin, TitleDescriptionTextMixin {
DiscountsBloc bloc;
PermissionStatus permissionStatus;
bool isError = false;
#override
void initState() {
super.initState();
bloc = DiscountsBloc(
DiscountsState.notProcessing(activeTab: DiscountsTabs.discount));
_onRefresh();
bloc.errors.listen((error) {
showSnackBarTextWithContext(context: context, text: error.message);
if (error.message ==
"Connection error, try again later")
isError = true;
});
}
void _onRefresh() => bloc.emitEvent(DiscountsListEventFetch(limit: 50)); //Here I'm fetching events
#override
Widget buildBody(BuildContext context) {
return StreamBuilder<List<DiscountsModel>>(
stream: bloc.dataField.stream,
builder: (BuildContext context,
AsyncSnapshot<List<DiscountsModel>> snapshot) {
if (!snapshot.hasData) {
return Container();
}
return RefreshIndicator(
onRefresh: () {
_onRefresh();
isError = false;
return Future.sync(() {
return;
});
},
color: LegionColors.primaryRedHigh,
child: buildView(context, snapshot.data));
});
}
buildView(BuildContext context, List<DiscountsModel> list) {
int count = 10;
return LazyLoadScrollView(
onEndOfPage: () => print('End of page'),
child: ListView.builder(
shrinkWrap: true,
itemCount: count + 1,
itemBuilder: (BuildContext context, int index) {
if (index == list.length) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator())),
);
}
return DiscountsWidget(model: list[index]);
}),
);
}
}
When I'm using regular ScrollController everything works fine since this moment. I mean my print statement works when i reach bottom, hovewer i cannot use loop inside loadMore.
class DiscountTab extends DiscountsBaseTab {
#override
_DiscountTabState createState() => _DiscountTabState();
}
class _DiscountTabState extends DiscountsBaseTabState
with SnackBarMixin, TitleDescriptionTextMixin {
DiscountsBloc bloc;
PermissionStatus permissionStatus;
bool isError = false;
int count = 10;
ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
bloc = DiscountsBloc(
DiscountsState.notProcessing(activeTab: DiscountsTabs.discount));
_onRefresh();
bloc.errors.listen((error) {
showSnackBarTextWithContext(context: context, text: error.message);
if (error.message ==
"Connection error")
isError = true;
});
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_loadMore();
}
});
}
_loadMore() {
print('End of page');
for (int i = count; i < count + 10; i++) {
//here i should add items but:
// 1. i have it fetched already (all 50)
// 2. cannot use list.add here because it's undefined
}
}
void _onRefresh() => bloc.emitEvent(DiscountsListEventFetch(limit: 50));
#override
Widget buildBody(BuildContext context) {
return StreamBuilder<List<DiscountsModel>>(
stream: bloc.dataField.stream,
builder: (BuildContext context,
AsyncSnapshot<List<DiscountsModel>> snapshot) {
if (!snapshot.hasData) {
return Container();
}
return RefreshIndicator(
onRefresh: () {
_onRefresh();
isError = false;
return Future.sync(() {
return;
});
},
color: LegionColors.primaryRedHigh,
child: buildView(context, snapshot.data));
});
}
buildView(BuildContext context, List<DiscountsModel> list) {
return ListView.builder(
shrinkWrap: true,
controller: _scrollController,
itemCount: count,
itemBuilder: (BuildContext context, int index) {
// if (index == list.length) {
// return Padding(
// padding: const EdgeInsets.all(10.0),
// child: Center(
// child: SizedBox(
// width: 20.0,
// height: 20.0,
// child: CircularProgressIndicator())),
// );
// }
return DiscountsWidget(model: list[index]);
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
My app was working fine yesterday and it was displaying the posts on Timeline page correctly. But now today when I opened by project again and wanted to continue working on it, when I run the Debug app, it generated an error on the Timeline page that "type int is not a subtype of type String".
Here is my Timeline.dart file:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/models/user.dart';
import 'package:fluttershare/pages/home.dart';
import 'package:fluttershare/pages/search.dart';
import 'package:fluttershare/widgets/header.dart';
import 'package:fluttershare/widgets/post.dart';
import 'package:fluttershare/widgets/progress.dart';
class Timeline extends StatefulWidget {
final User currentUser;
Timeline({this.currentUser});
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
List<Post> posts;
List<String> followingList = [];
// #override
// void initState() {
// super.initState();
// getTimeline().whenComplete(() {
// setState(() {});
// });
// getFollowing();
// }
Future<void> getTimeline() async {
QuerySnapshot snapshot = await timelineRef
.document(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.getDocuments();
List<Post> posts =
snapshot.documents.map((doc) => Post.fromDocument(doc)).toList();
setState(() {
this.posts = posts;
});
}
getFollowing() async {
QuerySnapshot snapshot = await followingRef
.document(currentUser.id)
.collection('userFollowing')
.getDocuments();
setState(() {
followingList = snapshot.documents.map((doc) => doc.documentID).toList();
});
}
buildTimeline() {
if (posts == null) {
return circularProgress();
} else if (posts.isEmpty) {
return buildUsersToFollow();
} else {
return ListView.builder(
itemCount: posts.length,
itemBuilder: (BuildContext ctxt, int index) {
return Text(posts[index].toString());
});
}
}
buildUsersToFollow() {
return StreamBuilder(
stream:
usersRef.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> userResults = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
final bool isAuthUser = currentUser.id == user.id;
final bool isFollowingUser = followingList.contains(user.id);
if (isAuthUser) {
return;
} else if (isFollowingUser) {
return;
} else {
UserResult userResult = UserResult(user);
userResults.add(userResult);
}
});
return Container(
color: Theme.of(context).accentColor.withOpacity(0.2),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.person_add,
color: Theme.of(context).primaryColor,
size: 30.0,
),
SizedBox(
width: 8.0,
),
Text(
"Users to Follow",
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 30.0,
),
),
],
),
),
Column(children: userResults),
],
),
);
},
);
}
#override
Widget build(context) {
return Scaffold(
appBar: header(context, isAppTitle: true),
body: RefreshIndicator(
onRefresh: () => getTimeline(),
child: FutureBuilder(
future: timelineRef
.document(widget.currentUser.id)
.collection('timelinePosts')
// .orderBy('timestamp', descending: true)
.getDocuments(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState != ConnectionState.waiting) {
print("${widget.currentUser.id}");
print("${snapshot.data.documents.length}");
var posts = snapshot.data.documents
.map((doc) => Post.fromDocument(doc))
.toList();
if (posts.length > 0)
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
// posts[index] gives you the post item.
return posts[index];
});
// else
// return
} else
return CircularProgressIndicator();
})),
);
}
}
Debug Console:
In
setState(() {
followingList = snapshot.documents.map((doc) => doc.documentID).toList();
});
Do This
setState(() {
followingList = snapshot.documents.map((doc) => doc.documentID).toList().toString();
});
My timeline page is not displaying any posts of users which I'm following. The posts are working fine on the user's profile page but not showing up on timeline. Here is the code of my timeline, and I don't see any debug errors too, so how to identify what's wrong here? Did I miss something? However, for the new user sign up, it does show users to follow on the timeline page.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/models/user.dart';
import 'package:fluttershare/pages/home.dart';
import 'package:fluttershare/pages/search.dart';
import 'package:fluttershare/widgets/header.dart';
import 'package:fluttershare/widgets/post.dart';
import 'package:fluttershare/widgets/progress.dart';
class Timeline extends StatefulWidget {
final User currentUser;
Timeline({this.currentUser});
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
List<Post> posts;
List<String> followingList = [];
#override
void initState() {
super.initState();
getTimeline();
getFollowing();
}
getTimeline() async {
QuerySnapshot snapshot = await timelineRef
.document(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.getDocuments();
List<Post> posts =
snapshot.documents.map((doc) => Post.fromDocument(doc)).toList();
setState(() {
this.posts = posts;
});
}
getFollowing() async {
QuerySnapshot snapshot = await followingRef
.document(currentUser.id)
.collection('userFollowing')
.getDocuments();
setState(() {
followingList = snapshot.documents.map((doc) => doc.documentID).toList();
});
}
buildTimeline() {
if (posts == null) {
return circularProgress();
} else if (posts.isEmpty) {
return buildUsersToFollow();
} else {
return ListView(children: posts);
}
}
buildUsersToFollow() {
return StreamBuilder(
stream:
usersRef.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> userResults = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
final bool isAuthUser = currentUser.id == user.id;
final bool isFollowingUser = followingList.contains(user.id);
if (isAuthUser) {
return;
} else if (isFollowingUser) {
return;
} else {
UserResult userResult = UserResult(user);
userResults.add(userResult);
}
});
return Container(
color: Theme.of(context).accentColor.withOpacity(0.2),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.person_add,
color: Theme.of(context).primaryColor,
size: 30.0,
),
SizedBox(
width: 8.0,
),
Text(
"Users to Follow",
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 30.0,
),
),
],
),
),
Column(children: userResults),
],
),
);
},
);
}
#override
Widget build(context) {
return Scaffold(
appBar: header(context, isAppTitle: true),
body: RefreshIndicator(
onRefresh: () => getTimeline(),
child: buildTimeline(),
),
);
}
}
Update your code
buildTimeline() {
if (posts == null) {
return circularProgress();
} else if (posts.isEmpty) {
return buildUsersToFollow();
} else {
return ListView.builder(
itemCount: posts.length,
itemBuilder: (BuildContext ctxt, int index) {
return Text(posts[index].toString);
});
}
}
ListView takes Widgets as a children. posts is not a widgets of any kind.
I have a list populated with a Future builder. The items are loaded correctly in the list from API.
Following is the relevant part of the code. I have a textfield in an appbar, which I want to use to filter the list.
List newList = List();
List originalList = List();
bool _showSearchBox = false;
TextEditingController _textController = TextEditingController();
Future _future;
#override
void initState() {
_future = commonApiProvider.fetchUserList(offset, widget.selectedDate);
super.initState();
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
loadMoreNewStatus = ItemLoadMoreStatus.LOADING;
return Scaffold(
backgroundColor: Color(0xfff0f0f0),
appBar: AppBar(
automaticallyImplyLeading: _showSearchBox == true ? false : true,
backgroundColor: CustomColors.absentTileColor,
elevation: 1,
title:
_showSearchBox == true ? _buildSearchWidget() : Text("Absent List"),
actions: <Widget>[
_showSearchBox == false ? _buildSearchIcon() : Container(),
],
),
body: FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == null) {
return Text("Records not found for selected date.");
} else if (snapshot.hasData) {
return _buildListChild(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
},
future: _future,
),
);
}
Widget _buildListChild(AsyncSnapshot snapshot) {
var data = snapshot.data.d;
newList = json.decode(data.userList);
originalList = json.decode(data.userList);
return RefreshIndicator(
key: _refreshIndicatorKey,
child: NotificationListener(
onNotification: onNotificationHandler,
child: ListView.builder(
padding: EdgeInsets.only(top: size.getSizePx(10)),
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemCount: newList.length,
controller: scrollContainer,
itemBuilder: (context, index) {
if (index == newList.length) {
return _buildProgressIndicator();
} else {
loadMoreNewStatus = ItemLoadMoreStatus.STABLE;
animationController.forward();
return cardView(newList[index]);
}
}),
),
onRefresh: _refreshStuffs,
);
}
Widget cardView(userList){
//build list items here.
}
bool onNotificationHandler(ScrollNotification notification){
//stuffs here
}
_refreshStuffs(){
//code to refresh list.
}
Widget _buildSearchWidget(){
return Container(
child: TextField(
controller: _textController,
style: TextStyle(fontSize: 14.0, color: Colors.grey[800]),
onChanged: onSearchTextChanged,
);
);
}
onSearchTextChanged(String text) async {
List tempSearchList = List();
tempSearchList.addAll(originalList);
if (text.isNotEmpty) {
List tempListData = List();
tempSearchList.forEach((item) {
String empName = item["empname"];
if (empName.toLowerCase().contains(text.toLowerCase())) {
tempListData.add(item);
}
});
setState(() {
newList.clear();
newList.addAll(tempListData);
});
return;
} else {
setState(() {
newList.clear();
newList.addAll(originalList);
});
}
}
Problem
The problem is that above code is not working, the list doesn't change at all. If I debug method onSearchTextChanged it works very well. I have cleared newList on this method as well, but doesn't seem to work. Can anybody help how to achieve filter?
The idea here is: Once FutureBuilder completes, it doesn't get rebuild.
I hope the code below helps. Let me know if your problem exists.
class _MyHomePageState extends State<MyHomePage> {
var items = [];
#override
void initState() {
callApi();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: YourFilteringTextField(),
),
body: ListView.builder(
itemBuilder: (context, position) {
return Text(items[position]);
},
itemCount: items.length,
),
);
}
callApi() {
//call api to get your latest items
setState(() {
// items= itemsFetchedFromApi;
});
}
filter(query) {
//applyFilter
setState(() {
// items= itemsAfterFiltering;
});
}
}