Timeline Posts are not being displayed - flutter

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.

Related

make a pagination with flutter and bloc

ServiceRepo
import 'package:service_youtube/data/service_model.dart';
import 'package:http/http.dart';
class ServicesRepo {
final _url = "http://osamastartup.osamamy-class.com/api/services";
Future<List<Datum>> getServices(int page) async {
final response = await get(Uri.parse("$_url?page=$page"));
final services = serviceFromJson(response.body);
return services.data;
}
}
the _url and everything is correct and return data without any problem
ServiceBloc:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_event.dart';
import 'package:service_youtube/blocs/service_bloc/service_state.dart';
import '../../data/service_repo.dart';
class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
final ServicesRepo repo;
int page = 1;
bool isFetching = false;
ServiceBloc(this.repo) : super(ServiceLoadingState()) {
on<LoadServicesEvent>((event, emit) async {
emit(ServiceLoadingState());
try {
final services = await repo.getServices(page);
emit(ServiceLoadedState(services: services));
page++;
} catch(e) {
emit(ServiceErrorState(msg: e.toString()));
}
});
}
}
surly i have all states and events , and there are no problem with them,
ServiceView
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:service_youtube/blocs/internet_bloc/internet_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_event.dart';
import 'package:service_youtube/data/service_model.dart';
import '../blocs/internet_bloc/internet_state.dart';
import '../blocs/service_bloc/service_state.dart';
class ServiceView extends StatelessWidget {
ServiceView({Key? key}) : super(key: key);
final List<Datum> _services = [];
final ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Services App"),),
body: Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(5),
child: Center(
child: BlocListener<ConnectedBloc, ConnectedState>(
listener: (context, state) {
if (state is ConnectedSucessState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Internet Connected')));
} else if (state is ConnectedFailureState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Internet Lost')));
}
},
child: BlocBuilder<ServiceBloc, ServiceState>(
builder: (context, state) {
if(state is ServiceLoadingState && _services.isEmpty) {
return const CircularProgressIndicator();
} else if (state is ServiceLoadedState) {
_services.addAll(state.services);
context.read<ServiceBloc>().isFetching = false;
return ListView.separated(
controller: _scrollController
..addListener(() {
if (_scrollController.offset ==
_scrollController.position.maxScrollExtent &&
!context.read<ServiceBloc>().isFetching) {
context.read<ServiceBloc>()
..isFetching = true
..add(LoadServicesEvent());
}
}),
itemCount: _services.length,
separatorBuilder: (context, index) => const SizedBox(height: 50),
itemBuilder: (context, index) {
return ListTile(
title: Text(_services[index].name,),
subtitle: Text(_services[index].info,),
leading: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(_services[index].image),
),
);
},
);
} else if (state is ServiceErrorState && _services.isEmpty) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () {
context.read<ServiceBloc>()
..isFetching = true
..add(LoadServicesEvent());
},
icon: const Icon(Icons.refresh),
),
const SizedBox(height: 15),
Text(state.msg, textAlign: TextAlign.center),
],
);
} else {
return Container();
}
},
),
),
),
),
);
}
}
the data is appear without any problem , but after i scroll show me just a blank screen, without any error message? where is the problem?

Flutter pagination loading the same data as in page one when scrolling

I'm building a list of news from an api that has next page results as in the image attached.
The api has only two pages with 10 list items each page.
Data is being passed to the widget. My problem is that when I scroll down the view, it loads the same 10 list items from page one.
This is the api I'm using enter link description here
Rest API
//newsModal.dart
class NewsNote {
String banner_image;
String title;
String text;
String sport;
NewsNote(this.banner_image, this.title, this.text, this.sport);
NewsNote.fromJson(Map<String, dynamic> json) {
banner_image = json['banner_image'];
title = json['title'];
text = json['text'];
sport = json['sport'];
}
}
//page news
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:jabboltapp/models/newsModal.dart';
class JabNews extends StatefulWidget {
#override
_JabNewsState createState() => _JabNewsState();
}
class _JabNewsState extends State<JabNews> {
ScrollController _scrollController = ScrollController();
bool isLoading = false;
String url = "https://jabbolt.com/api/news";
List<NewsNote> _newsNotes = List<NewsNote>();
Future<List<NewsNote>> fetchNewsNotes() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var response = await http.get(url);
var newsNotes = List<NewsNote>();
if (response.statusCode == 200) {
url = jsonDecode(response.body)['next'];
var newsNotesJson = json.decode(response.body)["results"];
for (var newsNoteJson in newsNotesJson) {
newsNotes.add(NewsNote.fromJson(newsNoteJson));
}
setState(() {
isLoading = false;
_newsNotes.addAll(newsNotes);
});
} else {
setState(() {
isLoading = false;
});
}
return newsNotes;
}
}
#override
void initState() {
fetchNewsNotes().then((value) {
setState(() {
_newsNotes.addAll(value);
});
});
this.fetchNewsNotes();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
fetchNewsNotes();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildProgressIndicator() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Opacity(
opacity: isLoading ? 1.0 : 00,
child: CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == _newsNotes.length) {
return _buildProgressIndicator();
} else {
return Padding(
padding: EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text((_newsNotes[index].title)),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(_newsNotes[index])));
},
),
),
);
}
},
controller: _scrollController,
itemCount: _newsNotes.length,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: dGrey,
appBar: AppBar(
title: Text(
"News",
style: TextStyle(
color: textGrey,
fontFamily: 'bison',
fontSize: 32.0,
letterSpacing: 1.2,
),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Container(
child: _buildList(),
),
);
}
}
You need to add the page number concatenation in the URL
https://jabbolt.com/api/news?page=2

How to change items of a gridview in flutter

I am new to flutter and i current have an app that has a grid view that gets its list from an api. Some of the grid view items have child nodes in them, so what i want to achieve is to set a click function that checks if there is a child node and if that is true; i would want to re-populate the same grid view but with only members of the child node. is this possible in flutter?
import 'package:bringam/network/Models/ProductGroupModel.dart';
import 'package:bringam/network/sharedpreferences/SharedPreferences.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
class Product_Category extends StatefulWidget {
#override
_Product_CategoryState createState() => _Product_CategoryState();
}
class _Product_CategoryState extends State<Product_Category> {
Future<List<ProductGroupModel>> _getChildrenCategories(String tag) async {
List<ProductGroupModel> categories = [];
SharedPref sharedPref = SharedPref();
var cacheCategories =
json.decode(await sharedPref.read('PRODUCT_CATEGORY'));
// FILTERING THE LIST STARTS
var filteredJson =
cacheCategories.where((i) => i["ParentGroupId"] == tag).toList();
// FILTERING THE LIST ENDS
for (var u in filteredJson) {
ProductGroupModel productCat = ProductGroupModel(
u["Description"],
u["IconURL"],
u["ProductGroup"],
u["ParentGroupId"],
u["HasChildNode"],
u["Order"]);
categories.add(productCat);
}
print(categories);
return categories;
}
Future<List<ProductGroupModel>> _getCategories() async {
List<ProductGroupModel> categories = [];
SharedPref sharedPref = SharedPref();
var cacheCategories =
json.decode(await sharedPref.read('PRODUCT_CATEGORY'));
if (cacheCategories.isEmpty) {
var data = await http.get(
'PRIVATE API ENDPOINT PLEASE');
var jsonData = json.decode(data.body);
// FILTERING THE LIST STARTS
var filteredJson =
jsonData.where((i) => i["ParentGroupId"] == '0').toList();
// FILTERING THE LIST ENDS
for (var u in filteredJson) {
ProductGroupModel productCat = ProductGroupModel(
u["Description"],
u["IconURL"],
u["ProductGroup"],
u["ParentGroupId"],
u["HasChildNode"],
u["Order"]);
categories.add(productCat);
}
} else {
// FILTERING THE LIST STARTS
var filteredJson =
cacheCategories.where((i) => i["ParentGroupId"] == '0').toList();
// FILTERING THE LIST ENDS
for (var u in filteredJson) {
ProductGroupModel productCat = ProductGroupModel(
u["Description"],
u["IconURL"],
u["ProductGroup"],
u["ParentGroupId"],
u["HasChildNode"],
u["Order"]);
categories.add(productCat);
}
return categories;
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getCategories(),
builder: (BuildContext context,
AsyncSnapshot<List<ProductGroupModel>> snapshot) {
if (snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:
2),
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 0,
color: Colors.transparent,
child: Hero(
tag: snapshot.data[index].ProductGroup,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
bool hasChild = snapshot.data[index].HasChildNode;
if (hasChild == true) {
setState(() {
_getChildrenCategories(
snapshot.data[index].ProductGroup);
});
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: new Text("Nothing found!"),
duration: const Duration(milliseconds: 500)));
}
},
child: GridTile(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar(
backgroundImage:
NetworkImage(snapshot.data[index].IconURL),
radius: 75.0,
),
Text(
snapshot.data[index].Description,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
color: Colors.white,
),
),
],
),
),
),
),
),
);
});
}
},
);
}
}
//THE MODEL CLASS
class ProductGroupModel {
final String Description;
final String IconURL;
final String ProductGroup;
final String ParentGroupId;
final bool HasChildNode;
final int Order;
ProductGroupModel(
this.Description,
this.IconURL,
this.ProductGroup,
this.ParentGroupId,
this.HasChildNode,
this.Order,
);
}

type 'int' is not a subtype of type 'String' - Flutter Android

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();
});

Flutter StreamBuilder with Firestore Pagination

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