scrollcontroller doesn't work with futurebuilder - flutter

i have an api call that upload data by offset, my goal is to load 10 by 10 on user scroll down, the problem is that i can't scroll down and show more data: here is my snippets:
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
HomeHeader(),
ProductsGridViewInfiniteScroll(),
],
),
);
}
}
here the scrollcontroller seems doesn't work:
class ProductsGridViewInfiniteScroll extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ProductsGridViewInfiniteScrollState();
}
}
class ProductsGridViewInfiniteScrollState
extends State<ProductsGridViewInfiniteScroll> {
Future<ProductList> products;
int offset;
ScrollController _controller;
_scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
setState(() {
offset += 10;
products = loadProductsByIdService(1, offset, 10);
});
}
}
#override
void initState() {
offset = 0;
products = loadProductsByIdService(1, offset, 10);
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
Widget build(BuildContext context) {
return FutureBuilder<ProductList>(
future: products,
builder: (context, snapshot) {
if (snapshot.hasData) {
return GridView.builder(
controller: _controller,
itemCount: snapshot.data.products.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 4.0,
mainAxisSpacing: 10.0),
shrinkWrap: true,
physics: ScrollPhysics(),
itemBuilder: (BuildContext ctx, index) {
return Container(
alignment: Alignment.center,
child: ProductCard(product: snapshot.data.products[index]),
);
});
} else {
return SizedBox();
}
});
}
}
Future<ProductList> loadProductsByIdService(serviceid, offset, limit) async {
var datamap = {'service_id': serviceid, 'offset': offset, 'limit': limit};
var data = json.encode(datamap);
ProductList products;
final response = await http.post(Uri.parse(PRODUCTS),
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
body: data,
encoding: Encoding.getByName("utf-8"));
if (response.body.isNotEmpty) {
if (response.statusCode == 200) {
products = ProductList.fromJson(json.decode(response.body));
}
} else {
throw Exception('echec de chargement des produits');
}
return products;
}
i want to rebuild the build function and update the products variable every time the scroll reaches the bottom of the screeen, any help please;

It looks like ClampingScrollPhysics is the default behavior of scroll physics in Android and BouncingScrollPhysics.
Most probably you are running on IOS so by default you are using BouncingScrollPhysics. If that is the case, please change your if condition to:
_scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
_controller.position.outOfRange) {
_controller.jumpTo(0);
setState(() {
offset += 10;
products = getProducts(offset, limit);
});
}
}
When bouncing physics is used, the scrolling passes max scrolling extend and outOfRange becomes true.
You need to reset controller offset back to 0 as it will keep calling the listener multiple times which skips pages by increasing offset.

if anyone have the same issue this is the final solution that worked for me without the use of FutureBuilder :
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() => new HomeState();
}
class HomeState extends State<Home> {
static int offset = 0;
ScrollController _sc = new ScrollController();
bool isLoading = false;
ProductList products = new ProductList(products: []);
#override
void initState() {
this._getMoreData(1, offset, 10);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels == _sc.position.maxScrollExtent) {
_getMoreData(1, offset += 10, 10);
}
});
}
#override
void dispose() {
_sc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return _buildGrid();
}
Widget __buildGrid() {
return GridView.builder(
controller: _sc,
itemCount: products.products.length + 1,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
shrinkWrap: true,
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (BuildContext ctx, index) {
if (index == products.products.length) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
} else {
return Container(
alignment: Alignment.topLeft,
// child: Flexible(
child: ProductCard(product: products.products[index]),
// ),
);
}
});
}
void _getMoreData(serviceid, offset, limit) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var datamap = {'service_id': serviceid, 'offset': offset, 'limit': limit};
var data = json.encode(datamap);
ProductList ps;
final response = await http.post(Uri.parse(PRODUCTS),
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
body: data,
encoding: Encoding.getByName("utf-8"));
if (response.statusCode == 200) {
ps = ProductList.fromJson(json.decode(response.body));
setState(() {
isLoading = false;
products.products.addAll(ps.products);
// offset += 10;
});
}
}
}
}

Related

Flutter: I wants to show message to the user only if the user reaches end of the ListView

Below is my code but it is showing the SnackBar frequently when I reach the bottom of ListView. It also shows the SnackBar on the pages also but I wants to show it only one time how to do that.
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},
Because you are assigning new listeners every time item builder calls.
put this code in ititState so it just called once.
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
Remove the listener from the itembuilder, instead use it in initState(),as everytime item is builded on listview, it will call this listener, so it is going on everytime item get builded.
You can use Listener on ScrollController, your issue is that you assign Listener to controller in build method which is wrong, you should do it once in initState. This is a full example of what you want:
class ScrollPageTest extends StatefulWidget {
const ScrollPageTest({Key? key}) : super(key: key);
#override
State<ScrollPageTest> createState() => _ScrollPageTest();
}
class _ScrollPageTest extends State<ScrollPageTest> {
ScrollController controller = ScrollController();
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
if (controller.position.atEdge) {
if (controller.position.pixels != 0) {
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text('data = $index'),
);
},
itemCount: 100,
),
);
}
}
Try this:
bool isSnackBarShown = false;
...
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if ((_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent)
&& !isSnackBarShown) {
isSnackBarShown = true;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},

Pagination with Future Builder and List of snapshots, Flutter

I want to implement pagination when calling APIs(without any packages) with my FutureBuilder, that calls 2 APIs at the same time since one depends on the other and I'm not sure how to do that. Here is the code below:
The ListView from FutureBuilder:
final ScrollController _scrollController = ScrollController();
#override
void initState() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {}
});
super.initState();
}
FutureBuilder(
future: Future.wait(
[
RepositoryFromAPItoDB().getAllMovies(),
RepositoryFromAPItoDB().getAllGenres()
],
),
builder:
(BuildContext context, AsyncSnapshot<List<dynamic>?> snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data?[0].length,
itemBuilder: (BuildContext context, int index) {
return MoviesListTile();
},
);
}
},
),
API calls for both Lists:
Future<List<Movies?>> getAllMovies() async {
Response response = await Dio().get(Constants().moviesURL);
return (response.data['results'] as List).map((movies) {
return Movies.fromJson(movies);
}).toList();
}
Future<List<Genres?>> getAllGenres() async {
Response response = await Dio().get(Constants().genresURL));
return (response.data['genres'] as List).map((genres) {
return Genres.fromJson(genres);
}).toList();
}
Now that I have call the APIs, my list is populated and everything is working fine, except I can't implement any type of pagination.. And how do I display some sort of CircularProgressIndicator() or anything like that while I scroll the bottom of the list and when it loads again? Thanks in advance for your help!
import 'package:flutter/material.dart';
import 'package:pageview_demo/main.dart';
import 'package:pageview_demo/usersModel.dart';
import 'DataNum.dart';
class School extends StatefulWidget {
const School({Key? key}) : super(key: key);
#override
_SchoolState createState() => _SchoolState();
}
class _SchoolState extends State<School> with AutomaticKeepAliveClientMixin {
ScrollController _scrollController = ScrollController(keepScrollOffset: true);
int pageNo = 1;
bool isLoading = false;
List<UsersModel> _usersModel = [];
List<Datum> data = [];
#override
void initState() {
_scrollController.addListener(() {
final pos = _scrollController.position;
final triggerFetchMoreSize = 0.9 * pos.maxScrollExtent;
if (pos.pixels > triggerFetchMoreSize) {
Future.delayed(const Duration(seconds: 5), () {
_callApi();
});
}
});
_callApi();
super.initState();
}
_callApi() async {
var response = await getHttp();
isLoading = true;
_usersModel.clear();
setState(() {
if (data.isEmpty) {
_usersModel.add(response);
data.addAll(_usersModel[0].data);
isLoading = false;
} else {
_usersModel.add(response);
data.insertAll(data.length, _usersModel[0].data);
isLoading = false;
}
});
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
pageNo = 1;
isLoading = true;
_usersModel.clear();
data.clear();
_callApi();
},
child: ListView.builder(
padding: EdgeInsets.all(10),
shrinkWrap: true,
scrollDirection: Axis.vertical,
controller: _scrollController,
physics: const BouncingScrollPhysics(),
itemCount: isLoading ? 0 : data.length,
itemBuilder: (context, indx) => indx == data.length - 1
? Center(child: LinearProgressIndicator())
: ListTile(
onTap: (){},
leading: Text("${data[indx].id}"),
title: Text("${data[indx].name}"),
subtitle: Text("${data[indx].email}"),
),
),
);
}
#override
bool get wantKeepAlive => true;
}

Flutter video caching for 10 seconds on next 4 videos

Does anyone know how to do Flutter Video Caching in List for 4 to 6 seconds? (For next 5 videos) like Instagram reels.
Is there any way to do it?
I had taken PageView.builder to show a video with a full page.
I have tried one cached_video_player but it's loading full video.
Here is what I have done.
VideoWidget:
typedef OnVideoCompleted = void Function();
class VideoWidget extends StatefulWidget {
//final bool? play;
final bool? isDuetVideo;
final String? url;
final OnVideoCompleted? onVideoCompleted;
final HomeWidgetBloc? homeWidgetBloc;
const VideoWidget(
{Key? key,
this.onVideoCompleted,
required this.url,
required this.homeWidgetBloc,
required this.isDuetVideo})
: super(key: key);
#override
_VideoWidgetState createState() => _VideoWidgetState();
}
class _VideoWidgetState extends State<VideoWidget> {
late VideoPlayerController videoPlayerController;
late Future<void> _initializeVideoPlayerFuture;
final _controllerStateStream = BehaviorSubject<int>.seeded(0);
VoidCallback? _listener;
StreamSubscription? _playPauseSubscription;
#override
void initState() {
super.initState();
videoPlayerController = new VideoPlayerController.network(widget.url!);
videoPlayerController.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
_controllerStateStream.add(1);
_observeForPlayPause();
_observerForSeekDuration();
_listener = () {
final value =
widget.homeWidgetBloc?.videoEndedStream.valueWrapper?.value;
print('duration -----value--- ${value}');
if (videoPlayerController.value.duration.inSeconds > 0 &&
videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds &&
(videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds)) {
// FOR AUTO PLAY NEXT VIDEO...
widget.onVideoCompleted?.call();
print(
'duration -----addListener--- ${videoPlayerController.value.duration}');
}
};
videoPlayerController.addListener(_listener!);
});
} // This closing tag was missing
#override
void dispose() {
super.dispose();
_controllerStateStream.close();
_playPauseSubscription?.cancel();
try {
if (_listener != null) {
videoPlayerController.removeListener(_listener!);
}
videoPlayerController.dispose();
} catch (e) {
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: _controllerStateStream,
builder: (context, snapshot) {
final isReady = (snapshot.data ?? 0) == 1;
if (!isReady) {
return _progressWidget();
}
return new Stack(children: <Widget>[
Container(
child: Center(
child: (widget.isDuetVideo! ||
videoPlayerController.value.size.width >
videoPlayerController.value.size.height)
? AspectRatio(
child: VideoPlayer(
videoPlayerController,
),
aspectRatio: videoPlayerController.value.aspectRatio,
)
: VideoPlayer(
videoPlayerController,
),
widthFactor: double.maxFinite,
heightFactor: double.maxFinite,
),
),
Visibility(
visible: !widget.isDuetVideo!,
child: VideoPlayerCustomControlsWidget(
controller: videoPlayerController,
),
),
]);
},
);
}
Center _progressWidget() {
return Center(
child: CircularProgressIndicator(
color: AppStyles.primary500Color,
),
);
}
void _observeForPlayPause() {
_playPauseSubscription =
widget.homeWidgetBloc?.videoPlayPauseStream.listen((value) {
if (value == PLAY)
videoPlayerController.play();
else
videoPlayerController.pause();
});
}
void _observerForSeekDuration() {
_playPauseSubscription =
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.listen((value) {
if (value == true) videoPlayerController.seekTo(Duration.zero);
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.add(false);
});
}
}
Update:
I found many answers (like this) but that all answers are only for caching the current video, not the next/prev videos from the list. I want it, especially for the list.
this is what I used in my app, preload_page_view, it preloads a specific count of pre/next pages:
#override
Widget build(BuildContext context) {
return new PreloadPageView.builder(
itemCount: ...,
itemBuilder: ...,
onPageChanged: (int position) {...},
.....
preloadPagesCount: 3,
controller: PreloadPageController(),
);
}

Flutter lazy loading from Api

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

Best practices for implementing an infinite scroll GridView in Flutter?

I want to create a GridView that displays items which will be fetching from server by offset. I load only 10 items in GridView, and after user scroll reached to 10, I will load more 10 items. What are the best practices for implementing an infinite scroll GridView in Flutter?
You need to add the ScrollController for the scrolling detection at the bottom for the ListView and GridView. As you need the GridView i have created the ScrollController listner and added to the GridView's contollerfor the detection of the scroll. I have created the demo of it , please check it once. At first time it load the 10 items and when list comes to the bottom then it add more 10 items in it.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomeState();
}
}
class HomeState extends State<HomeScreen> {
List dataList = new List<int>();
bool isLoading = false;
int pageCount = 1;
ScrollController _scrollController;
#override
void initState() {
super.initState();
////LOADING FIRST DATA
addItemIntoLisT(1);
_scrollController = new ScrollController(initialScrollOffset: 5.0)
..addListener(_scrollListener);
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gridview',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.red,
accentColor: Color(0xFFFEF9EB),
),
home: Scaffold(
appBar: new AppBar(),
body: GridView.count(
controller: _scrollController,
scrollDirection: Axis.vertical,
crossAxisCount: 2,
mainAxisSpacing: 10.0,
physics: const AlwaysScrollableScrollPhysics(),
children: dataList.map((value) {
return Container(
alignment: Alignment.center,
height: MediaQuery.of(context).size.height * 0.2,
margin: EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
),
child: Text("Item ${value}"),
);
}).toList(),
)));
}
//// ADDING THE SCROLL LISTINER
_scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount + 1;
addItemIntoLisT(pageCount);
}
});
}
}
////ADDING DATA INTO ARRAYLIST
void addItemIntoLisT(var pageCount) {
for (int i = (pageCount * 10) - 10; i < pageCount * 10; i++) {
dataList.add(i);
isLoading = false;
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
And output of the above program as follow
class AllOrdersPage extends StatefulWidget {
#override
_AllOrdersPageState createState() => _AllOrdersPageState();
}
class _AllOrdersPageState extends State<AllOrdersPage> {
List<OrderDatum> ordersList;
ScrollController _scrollController = ScrollController();
int skip = 0;
bool shouldLoadMore = true;
Future<OrdersResponse> future;
#override
void initState() {
super.initState();
ordersList = [];
future = getAllOrders(skip); //load data for first time
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) { //Check whether user scrolled to last position
if (shouldLoadMore) {
setState(() {
skip += ordersList.length;
future = getAllOrders(skip); //load more data
});
}
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<OrdersResponse>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasError)
return ErrorText('${snapshot.error.toString()}');
if (snapshot.hasData) {
skip = snapshot.data.skip;
if (snapshot.data.limit + snapshot.data.skip >=
snapshot.data.total) {
shouldLoadMore = false;
}
snapshot.data.data.forEach((element) {
if (!ordersList.contains(element)) ordersList.add(element);
});
if (skip == 0 && ordersList.isEmpty) {
return ErrorText('No orders.');
}
return Scrollbar(
controller: _scrollController,
isAlwaysShown: true,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 2.5,
mainAxisSpacing: 8),
controller: _scrollController,
padding: const EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
if (index == ordersList.length) {
return shouldLoadMore
? Center(child: CircularProgressIndicator())
: Container();
}
return Container(
width: MediaQuery.of(context).size.width,
child: OrderCard(ordersList[index]));
},
itemCount: ordersList.length + 1,
),
);
}
return Loader();
});
}
}
Thanks