How to use pagination in flutter using FutureBuilder with ListView? - flutter

I'm new as a Flutter programmer and I'm having difficulty working with a list that has pagination, if I remove the pagination the list doesn't load. Need help. The state manager I'm using is Getx.
This is my DTO class.
class PaginationFilter {
int? page;
int? pagesize;
#override
String toString() {
return 'PaginationFilterAlbum{page: $page, pagesize: $pagesize}';
}
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is PaginationFilter &&
runtimeType == other.runtimeType &&
page == other.page &&
pagesize == other.pagesize;
#override
int get hashCode => page.hashCode ^ pagesize.hashCode;
}
Here is where I consume the API.
Future<List<Post>> getAlbum(PaginationFilter filter, {
bool isFavoritedPage = false,
bool isNewEdition = false,
}) async {
dio.options.headers['Cookie'] = 'ASP.NET_SessionId=${user.sessionID}';
final response = await dio.get(
isFavoritedPage ? AppConstants.apiFavoritedsPost : AppConstants.apiPosts,
queryParameters: {
'sessionId': user.sessionID,
'CodUserProfile': '${user.codUser!}',
'CodUserLogged': '${user.codUser!}',
'Page': '${filter.page}', /*Aqui fica a pagina*/
'pagesize': '${filter.pagesize}', /*Aqui a quantidade de registros em cada pagina*/
'myPostOnly': isFavoritedPage ? 'true' : 'false',
},
);
final body = response.data['ListPosts'] as List;
return body.map((post) => Post.fromJson(post)).toList();
}
Here is my controller class
final _posts = <Post>[].obs;
var response = null;
final _paginationFilterAlbum = PaginationFilter().obs;
int? get pagesize => _paginationFilterAlbum.value.pagesize;
int? get _page => _paginationFilterAlbum.value.page;
final _lastPageAlgum = false.obs;
bool get lastPageAlgum => _lastPageAlgum.value;
#override
void onInit(){
ever(_paginationFilterAlbum, (_) => getAlbum());
_changePaginationFilterAlgum(1, 10);
super.onInit();
}
void _changePaginationFilterAlgum(int page, int pagesize){
_paginationFilterAlbum.update((val) {
val?.page = page;
val?.pagesize = pagesize;
});
}
void changeTotalPerPage(int limitValue){
_posts.clear();
_lastPageAlgum.value = false;
_changePaginationFilterAlgum(1, limitValue);
}
void nextPage() => _changePaginationFilterAlgum(_page! + 1, pagesize!);
Future<List<Post>> getAlbum([bool isFavoritedPage = false]) async {
response =
await repository.getAlbum(_paginationFilterAlbum.value,isFavoritedPage: isFavoritedPage);
if(response.isEmpty){
_lastPageAlgum. value = true;
}
_posts.addAll(response);
return response;
}
here is my my list
Expanded(
child: FutureBuilder<List<Post>>(
future: controller.getAlbum(),
builder: (context, snapshot) {
final List<Post> posts = snapshot.data ?? [];
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CupertinoActivityIndicator());
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: posts.length,
itemBuilder: (context, index) {
return PostWidget(post: posts[index]);
},
);
},
),
),
Could you please help me how do I load the entire list using pagination?

Related

How to solve the given algorithm issue?

Basically, I am trying to fetch all comments through pagination and it's working nicely. But in case of reply, I am trying to fetch and show reply which comment has reply only and I am showing in listview builder. But problem is all reply is showing below every comment. I am leaving my code.
late bool isReplyBox = false;
final _numberOfCommentPerRequest = 5;
late List<ReplyTile> _replyList = [];
final PagingController<int, CommentModel> _pagingController =
PagingController(firstPageKey: 1);
Future<void> _fetchAllComments(int pageKey) async {
try {
final response = await getAllComments(widget.classId, pageKey);
late List<CommentModel> _commentList = [];
response['data']['results'].forEach((element) {
String userFName = element['creator_first_name'] ?? 'No Name';
String userLName = element['creator_last_name'];
String userName = "$userFName $userLName";
String userImage = element['avatar'] ?? '';
String commentMessage = element['comment'] ?? 'No comment';
int commentId = element['id'] ?? 0;
if(element['reply_comments'] != null){
setState(() {
isReplyBox = true;
});
var replyList = element['reply_comments'];
replyList.forEach((replyData) {
var replyMessage = replyData['comment'] ?? '';
String creatorFName = replyData['creator_first_name'] ?? 'No Name';
String creatorLName = replyData['creator_last_name'];
String creatorName = "$creatorFName $creatorLName";
String creatorImage = replyData['avatar'] ?? '';
setState(() {
_replyList.add(ReplyTile(
commentMessage: replyMessage,
userName: creatorName,
userImagePath: creatorImage));
});
});
}else{
_replyList;
print('No reply comments');
}
_commentList.add(CommentModel(commentId,userImage,userName,commentMessage));
});
final isLastPage = _commentList.length < _numberOfCommentPerRequest;
if (isLastPage) {
_pagingController.appendLastPage(_commentList);
} else {
final nextPageKey = pageKey + 1;
_pagingController.appendPage(_commentList, nextPageKey);
}
} catch (e) {
print("error --> $e");
_pagingController.error = e;
}
}
#override
void initState() {
_pagingController.addPageRequestListener((pageKey) {
_fetchAllComments(pageKey);
});
super.initState();
}
#override
void dispose() {
_pagingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height / 4,
child: RefreshIndicator(
onRefresh: () => Future.sync(() => _pagingController.refresh()),
child: PagedListView<int, CommentModel>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<CommentModel>(
itemBuilder: (context, item, index) =>
CommentTile(
replyList: isReplyBox == true ? replyChild() : const SizedBox.shrink(),
isReplyBox: isReplyBox,
classId: widget.classId,
commentId: item.commentId,
commentMessage: item.commentMessage,
userName: item.userName,
userImagePath: item.userImagePath)),
),
),
);
}
Widget replyChild() {
final double height = MediaQuery.of(context).size.height / 3.8;
return Container(
height: _replyList.length <= 1 ? 00 : height,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: _replyList.length,
itemBuilder: (BuildContext context, index) {
return _replyList[index];
}
),
);
}
Could anyone please tell how may I solve this issue? Thanks in advance.

RangeError index invalid value only valid value is empty 0 see also in Flutter

I'm in a Flutter project using Getx. Every time I enter the screen that lists the records I get an error message as you can see below;
I don't know where I'm going wrong, but I'll leave the main parts of the code. I need to find where I'm going wrong.
Class Repository
Future<List<Post>> getAlbum({
bool isFavoritedPage = false,
bool isNewEdition = false,
}) async {
dio.options.headers['Cookie'] = 'ASP.NET_SessionId=${user.sessionID}';
final response = await dio.get(
isFavoritedPage ? AppConstants.apiFavoritedsPost : AppConstants.apiPosts,
queryParameters: {
'sessionId': user.sessionID,
'CodUserProfile': '${user.codUser!}',
'CodUserLogged': '${user.codUser!}',
'Page': '${page}',
'pagesize': '10',
'myPostOnly': isFavoritedPage ? 'true' : 'false',
},
);
final body = response.data['ListPosts'] as List;
return body.map((post) => Post.fromJson(post)).toList();
}
Class Controller
var lstPost = List<Post>.empty(growable: true).obs;
var page = 1;
var isDataProcessing = false.obs;
// For Pagination
ScrollController scrollController = ScrollController();
var isMoreDataAvailable = true.obs;
#override
void onInit() async {
super.onInit();
// Fetch Data
getPost(page);
//For Pagination
paginateTask();
}
void getPost(var page) {
try {
isMoreDataAvailable(false);
isDataProcessing(true);
getAlbum(page).then((resp) {
isDataProcessing(false);
lstPost.addAll(resp);
}, onError: (err) {
isDataProcessing(false);
showSnackBar("Error", err.toString(), Colors.red);
});
} catch (exception) {
isDataProcessing(false);
showSnackBar("Exception", exception.toString(), Colors.red);
}
}
showSnackBar(String title, String message, Color backgroundColor) {
Get.snackbar(title, message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: backgroundColor,
colorText: Colors.white);
}
void paginateTask() {
scrollController.addListener(() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent) {
print("reached end");
page++;
getMoreTask(page);
}
});
}
void getMoreTask(var page) {
try {
getAlbum(page).then((resp) {
if (resp.length > 0) {
isMoreDataAvailable(true);
} else {
isMoreDataAvailable(false);
showSnackBar("Message", "Não existem registro", Colors.lightBlueAccent);
}
lstPost.addAll(resp);
}, onError: (err) {
isMoreDataAvailable(false);
showSnackBar("Error", err.toString(), Colors.red);
});
} catch (exception) {
isMoreDataAvailable(false);
showSnackBar("Exception", exception.toString(), Colors.red);
}
}
#override
void onClose() {
searchDrawerEC.dispose();
super.onClose();
}
Future<List<Post>> getAlbum(pagina,[bool isFavoritedPage = false]) async {
final response =
await repository.getAlbum(isFavoritedPage: isFavoritedPage);
return response;
}
Class Page
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == controller.lstPost.length - 1 &&
controller.isMoreDataAvailable.value == true) {
return Center(child: CircularProgressIndicator());
}
return PostWidget(post: controller.lstPost[index]);
}
),
),
I'm basing myself on this github project.
https://github.com/RipplesCode/FlutterGetXTodoAppWithLaravel/tree/master/lib/app/modules/home
I don't use getx, but I see something odd in your Listview.builder. It feels as if you're abusing it a little, to also show the "no data" case, and there's also no count. I think it should have a count, so something like this:
if (lstPost.isEmpty) {
return Center(child: CircularProgressIndicator());
} else {
return ListView.builder(
itemCount: lstPost.length,
itemBuilder: (BuildContext context, int index) {
return PostWidget(...);
}
);
}

StreamBuilder doesn't updating items when I get more

I have a list with all the items from that folder, and I'm retrieving the data with 10 items per time (first load 10, when user reach the list finish, it loads more 10). The problem Is, when list have to be updated, it is not.
It doesn't add the new items in the list.
This is the method I get data from firebase:
Future<void> loadnovo(
{String submenu,
int limit = 10,
bool cls = false,
bool initialLoad = false,
int lastIndex}) async {
if (cls) {
conteudo.clear();
hasMore = true;
}
if (_isLoading || !hasMore) {
return Future.value();
}
_isLoading = true;
var parts = submenu.split('/');
var pathSlashless = parts[0].trim();
var subPathSlashless = parts.sublist(1).join('/').trim();
var snapshot = await _storage.ref().child("/${submenu}");
var retorno = await snapshot.listAll();
if (subPathSlashless.isEmpty || subPathSlashless == null) {
retorno.prefixes.forEach((element) {
conteudo.add(
ItemLab(
tipo: 'PASTA',
elemento: element,
),
);
_streamController.add(conteudo);
});
}
for (int i = lastIndex; i < lastIndex + limit; i++) {
var url = await retorno.items[i].getDownloadURL();
conteudo.add(
ItemLab(
tipo: 'FILE',
elemento: retorno.items[i],
imageUrl: url,
),
);
print(conteudo);
print(conteudo.length);
_streamController.add(conteudo);
}
hasMore = true;
}
This is my Screen with the Stream builder, a gridView (which show the items) and the scrollListener:
LabController ctrlLab;
final lab = LabMdScreen();
inal scrollController = ScrollController();
int lastIndex = 0;
scrollListener() async {
if (scrollController.position.maxScrollExtent == scrollController.offset) {
lastIndex += 10;
ctrlLab.loadList(submenu: "ph/Res", lastIndex: lastIndex);
}
}
#override
void initState() {
ctrlLab = LabController();
ctrlLab.loadList(submenu: "ph/Res", lastIndex: lastIndex,cls: true, initialLoad: true);
scrollController.addListener(scrollListener);
super.initState();
}
#override
void dispose() {
scrollController.removeListener(scrollListener);
super.dispose();
}
loadBasicStructureDetail(submenu ,callback, context, deviceSize){
return StreamBuilder(
stream: ctrlLab.stream,
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.error != null) {
print(snapshot.error);
return Center(child: Text('Ocorreu um erro!'));
}else {
return GridView.builder(
padding: EdgeInsets.all(10.0),
controller: scrollController,
itemCount: snapshot.data.length +1,
itemBuilder: (ctx, i) {
path = callback;
if (i < snapshot.data.length) {
ItemLab item = snapshot.data[i];
>>>> here my code to format the tiles...
What I'm missing here
Try removing
if (_isLoading || !hasMore) {
return Future.value();
}

How to NOT show the current user in a Grid View?

I have a function called getAllUsers() that returns all users from a database. The problem is that I want GridView.builder() to display all the users except the current user, but despite all the research I did, nothing seems to work out.
If i use the if condition like if(snapshot.data.documents[i].data["username"] != currentUserId within itemBuilder:, it returns a blank tile which represents the current user which creates a gap within the grid view. Thus, it makes the grid view look really bad.
I believe this problem could have been solved if I knew how to include the inequality query in the getAllUsers() method. But my understanding is that Firestore has yet to provide this function/argument.
HomeFragment class
Database _database = Database();
Stream _stream;
String currentUserId;
#override
void initState() {
getCurrentUserId();
getAllUsers();
super.initState();
}
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted)
setState(() => _stream = val);
});
}
getCurrentUserId() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
currentUserId = currentUser.uid;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
return snapshot.data == null ? Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, i) {
return Container(
child: Text(snapshot.data.documents[i].data["username"])
);
}
// etc etc..
Database class
getAllUsers() async {
return await _firestore.collection("users").snapshots();
}
I tried to use this, but _stream2 returns null
Stream _stream, _stream2;
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted) {
List<String> list;
setState(() {
_stream = val;
_stream2 = _stream.where((snapshot) {
_querySnapshot = snapshot;
for (int i = 0; i < _querySnapshot.documents.length; i++)
list.add(_querySnapshot.documents[i].data["userId"]);
return list.contains(currentUserId) == false;
});
});
}
});
}
I also tried this, it is not working
getAllUsers() async {
Stream<QuerySnapshot> snapshots = await _database.getAllUsers();
_stream = snapshots.map((snapshot) {
snapshot.documents.where((documentSnapshot) {
return documentSnapshot.data["userId"] != currentUserId;
});
});
}
Maybe you can try something like this. You filter the query result:
getAllUsers() async {
final Stream<QuerySnapshot> snapshots = await _firestore.collection("users").snapshots();
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => User.fromMap(snapshot.data)
.where((user) => user.id != currentUser.id)
.toList();
return result;
}
}
If you do not have an User class, you can replace some lines with this. But the result will be a list of Map<String, dynamic> instead of a list of User objects.
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => snapshot.data
.where((user) => user['id'] != currentUser.id)
.toList();
return result;
This solution worked well for me.
firestore.collection('your collection').where('x', isNotEqualTo: auth.currentUser!.uid).snapshots();

How to return Future List from DataSnapshot

I want to return a Future List from Firebase Database snapshot and this is my code but I cant get it work properly:
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
dbRef.child(id).once().then((DataSnapshot snapshot) {
if (snapshot.value != null) {
Map<dynamic, dynamic> jsres = snapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
});
print('here is i ${ids[i]} ');
}
return coches;
}
The return I get is empty Area. Can anyone help me with this, please?
Note, dbRef.child(id).once(); is a async function, so you must wait it ends to get your data. Use await keyword to do it.
Future<List<CocheDetailItem>> getCoches(ids) async {
List<CocheDetailItem> coches = [];
final dbRef = FirebaseDatabase.instance.reference().child('17082019');
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var dataSnapshot = await dbRef.child(id).once();
if (dataSnapshot.value != null) {
Map<dynamic, dynamic> jsres = dataSnapshot.value;
CocheDetailItem coche = CocheDetailItem.fromJson(jsres);
coches.add(coche);
}
print('here is i ${ids[i]} ');
}
return coches;
}
well.. I don't use firebase but I send a request to my database with this (you have to use async and await)
Future<List<PlaceModel>> getPlaces(String ciudad, String tipo) async {
Uri request = Uri.http('domain.com', '/getPlaces/$ciudad/$tipo');
ResponseModel response = ResponseModel.fromJsonMap(json.decode((await http.get(request)).body));
List<PlaceModel> items = [];
if(response.res) {
if(response.value != null) {
for(var item in response.value) {
final place = PlaceModel.fromJsonMap(item);
items.add(place);
}
}
}
print("Places Loaded: ${items.length}");
return items;
}
I use my ResponseModel to convert the json answer in an object.
Then I show it with the future builder:
class PlacesListPage extends StatelessWidget{
final _selectedLocation, _selectedList;
PlacesListPage(this._selectedLocation, this._selectedList);
final _provider = PlaceProvider();
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
child: FutureBuilder(
future: _provider.getPlaces(_selectedLocation, _selectedList), // async request to database
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { // check when your request is done
if(snapshot.data.length != 0) { // check if any data has been downloaded
return ListView.builder( // build a listview of any widget with snapshot data
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
// i just return containers but you can use any custom widget, it's like a forEach and use the index var
return Container(
child: Text(snapshot.data[index]),
);
},
);
} else {
// If you don't have anything in your response shows a message
return Text('No data');
}
} else {
// shows a charge indicator while the request is made
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}