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.
Related
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?
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(...);
}
);
}
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();
}
I have a class that displays in a list view some JSON data (events) that I get with an API request and save them to the storage of the device so to not be downloaded every time, UNLESS the user makes a Pull-to-refresh operation so to download news events.
In case during the operation of download there is no internet connection the app display "Impossible to download the events list: check your internet connection!".
So I aspect that if it is the first time the user opens the app, it should download the events or show in case of internet connection missing the message mentioned above (or that there are no events in case the length of the events array downloaded == 0). If it is not the first time show the list of the events previously downloaded and saved.
My problem is that if, for example, I have internet turned off and after I turned on, the pull to refresh doesn't work, instead when I have the list downloaded I can make a pull to refresh operation.
This is my code:
class EventDetails {
String data;
int id;
String name;
String description;
String applicationStatus;
String applicationStarts;
String applicationEnd;
String starts;
String ends;
int fee;
EventDetails({
this.data,
this.id,
this.name,
this.description,
this.applicationStatus,
this.applicationStarts,
this.applicationEnd,
this.starts,
this.ends,
this.fee,
});
EventDetails.fromJson(Map<String, dynamic> json) {
data = json['data'];
id = json['id'];
name = json['name'];
description = json['description'];
applicationStatus = json['application_status'];
applicationStarts = json['application_starts'];
applicationEnd = json['application_ends'];
starts = json['starts'];
ends = json['ends'];
fee = json['fee'];
}
}
class EventsListView extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _EventListState();
}
}
class _EventListState extends State<EventsListView> {
List<EventDetails> list;
Storage storage = Storage();
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _getData,
child: FutureBuilder<List<EventDetails>>(
future: loadEvents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<EventDetails> data = snapshot.data;
if (data.length == 0) {
return Text("No events found",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return _eventsListView(data);
}
} else if (snapshot.hasError) {
if (snapshot.error.runtimeType.toString() == "SocketException") {
return Text(
"Impossible to download the events list: check your internet connection!",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return Text("${snapshot.error}");
}
}
return CircularProgressIndicator();
},
),
);
}
Future<List<EventDetails>> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
return list;
} else {
return await downloadEvents(list, storage);
}
}
Future<List<EventDetails>> downloadEvents(
List<EventDetails> list, Storage storage) async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
return list;
} else {
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
ListView _eventsListView(data) {
return ListView.separated(
itemCount: data.length,
separatorBuilder: (context, index) => Divider(
color: const Color(0xFFCCCCCC),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: _tile(data[index].name),
onTap: () {
Navigator.pushNamed(
context,
SingleEvent.routeName,
arguments: ScreenArguments(
data[index].name,
data[index].description,
data[index].starts,
data[index].ends,
),
);
});
});
}
Future<void> _getData() async {
setState(() {
downloadEvents(list,storage);
});
}
#override
void initState() {
super.initState();
loadEvents();
}
ListTile _tile(String title) => ListTile(
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
);
}
I am really new in Flutter, what I am doing wrong?
FutureBuilder will not refresh once the future is evaluated. If you want the pull to refresh to work, you could just store the list data as a state of the widget and render different UI based on the state.
In addition to that, RefreshIndicator will not work if the child is not scrollable. Instead returning plain Text widget when there is no data, return SingleChildScrollView with a text inside so that you have a scrollable inside your RefreshIndicator.
Here is an example:
class EventsListView extends StatefulWidget {
#override
_EventsListViewState createState() => _EventsListViewState();
}
class _EventsListViewState extends State<EventsListView> {
List list;
Storage storage = Storage();
String errorMessage;
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: downloadEvents,
child: listWidget(),
);
}
Widget listWidget() {
if (list != null) {
return ListView(); // here you would return the list view with contents in it
} else {
return SingleChildScrollView(child: Text('noData')); // You need to return a scrollable widget for the refresh to work.
}
}
Future<void> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
errorMessage = null;
setState(() {});
} else {
await downloadEvents();
}
}
Future<void> downloadEvents() async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
errorMessage = null;
setState(() {});
} else {
setState(() {
errorMessage =
'Error occured'; // here, you would actually add more if, else statements to show better error message
});
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
#override
void initState() {
super.initState();
loadEvents();
}
}
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(),
);
}
},
),
);
}
}