Flutter lazy loading from Api - flutter

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

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

Flutter : scrollController.isAttached is always false

How can I scroll to a special widget in a ListView? For example, I want to automatically scroll to some container in ListView if I press a certain button on a previous screen. I will pass to the next screen an Id (from id I will know the index) and when I navigate to the next screen I want to automatically scroll to this widget.
the code in main screen : Navigator.push(context, MaterialPageRoute(builder: (_) => CreatedEstatesScreen(estateId: id)));
the code in the next screen :
class RecentEstateOrdersScreen extends StatefulWidget {
static const String id = "RecentEstateOrdersScreen";
String? estateId;
RecentEstateOrdersScreen({Key? key, this.estateId}) : super(key: key);
#override
_RecentEstateOrdersScreenState createState() =>
_RecentEstateOrdersScreenState();
}
class _RecentEstateOrdersScreenState extends State<RecentEstateOrdersScreen> {
late RecentEstatesOrdersBloc _recentEstatesOrdersBloc;
late ItemScrollController scrollController;
late ItemPositionsListener itemPositionsListener;
String? userToken;
List<EstateOrder> orders = [];
#override
void initState() {
super.initState();
_recentEstatesOrdersBloc = RecentEstatesOrdersBloc(EstateOrderRepository());
_onRefresh();
User? user = BlocProvider.of<UserLoginBloc>(context).user;
if (user != null && user.token != null) {
userToken = user.token;
}
scrollController = ItemScrollController();
itemPositionsListener = ItemPositionsListener.create();
}
_onRefresh() {
if (BlocProvider.of<UserLoginBloc>(context).user!.token != null) {
_recentEstatesOrdersBloc.add(
RecentEstatesOrdersFetchStarted(
token: BlocProvider.of<UserLoginBloc>(context).user!.token!),
);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)!.recent_created_orders,
),
),
body: BlocConsumer<RecentEstatesOrdersBloc, RecentEstatesOrdersState>(
bloc: _recentEstatesOrdersBloc,
listener: (context, recentOrdersState) async {
if (recentOrdersState is RecentEstatesOrdersFetchError) {
var error = recentOrdersState.isConnectionError
? AppLocalizations.of(context)!.no_internet_connection
: recentOrdersState.error;
await showWonderfulAlertDialog(
context, AppLocalizations.of(context)!.error, error);
}
},
builder: (BuildContext context, recentOrdersState) {
if (recentOrdersState is RecentEstatesOrdersFetchProgress) {
return const ClientsOrdersShimmer();
}
if (recentOrdersState is! RecentEstatesOrdersFetchComplete) {
return Container();
}
orders = recentOrdersState.estateOrders;
if (orders.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
documentOutlineIconPath,
width: 0.5.sw,
height: 0.5.sw,
color: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.42),
),
48.verticalSpace,
Text(
AppLocalizations.of(context)!.have_not_recent_orders,
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
if (widget.estateId != null) {
SchedulerBinding.instance!.addPostFrameCallback((_) {
jumpToOrder(orders);
});
}
return RefreshIndicator(
color: Theme.of(context).colorScheme.primary,
onRefresh: () async {
_onRefresh();
},
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (_, index) {
return EstateOrderCard(
estateOrder: orders.elementAt(index),
);
}),
);
},
),
),
);
}
jumpToOrder(List<EstateOrder> orders) {
int index = getIndexFromId(orders);
if (index != -1) {
if (scrollController.isAttached) {
scrollController.scrollTo(
index: index,
duration: const Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
}
}
getIndexFromId(List<EstateOrder> orders) {
for (int i = 0; i < orders.length; i++) {
if (orders.elementAt(i).id == int.parse(widget.estateId!)) {
return i;
}
}
return -1;
}
}```
If you are using the library then you have to use ScrollablePositionedList.builder, not the normal ListView.builder.

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

Get more items to listview without overwriting old items - Flutter

I want to make a pagination to my application using hacker news api, I want load more data when I scroll down and add new items to old items I have in the first page.
That's the code of the item page, I use a bloc pattern I wend variable named size to the bloc withe the event ant in the bloc I fetch only 6 items in the first load and when I scroll down I add 6 item to my loop ...
class _TopPageState extends State<TopPage> {
#override
void initState() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
size = size + 6;
context.read<ItemBloc>().add(GetTopEvent(size));
}
});
super.initState();
}
#override
void dispose() {
super.dispose();
_scrollController.dispose();
}
ScrollController _scrollController = new ScrollController();
int size = 6;
bool isLoading = false;`enter code here`
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<ItemBloc, ItemState>(
builder: (context, state) {
if (state.topState == RequestState.LOADING) {
return const CircularProgressIndicator();
} else if (state.topState == RequestState.ERROR) {
print(state.errorMsg);
return Container();
}else if (state.topState == RequestState.NONE) {
context.read<ItemBloc>().add(GetTopEvent(size));
return Container();
}
else if (state.topState == RequestState.LOADED) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
_getMoreData(notification.metrics);
}
return true;
},
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: state.reposTop.length,
itemBuilder: (context, index) {
return GestureDetector(
child: Item(state.reposTop[index]),
onTap: () {
if (state.reposTop[index].url != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
WebViewPage(state.reposTop[index])),
);
} else if (state.reposTop[index].text != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ItemDetails(state.reposTop[index])),
);
}
});
}),
);
} else {
return Container();
}
},
),
),
);
}
void _getMoreData(ScrollMetrics metrics) {
if (_scrollController.position.pixels ==
_scrollController.position.minScrollExtent) {
//&& size != 5
if( size - 6 <=0){
context.read<ItemBloc>().add(GetTopEvent(size));
}
else{
size = size - 6;
}
context.read<ItemBloc>().add(GetTopEvent(size));
}
}
}

how to add onscroll fetch data (pagination) in flutter & firebase realtime?

Hope Well,
I am using firebase realtime database on my flutter app (similar social media app). i have feed page and feed state page. i wanna show 10 posts first and after scroll bottom, load again 10 posts. i tried some methods but not working.
my codes
feed page code
Widget build(BuildContext context) {
var authstate = Provider.of<AuthState>(context, listen: false);
return Consumer<FeedState>(
builder: (context, state, child) {
final List<FeedModel> list = state.getPostList(authstate.userModel);
return CustomScrollView(
slivers: <Widget>[
child,
state.isBusy && list == null
? SliverToBoxAdapter(
child: Container(
height: fullHeight(context) - 135,
child: CustomScreenLoader(
height: double.infinity,
width: fullWidth(context),
backgroundColor: Colors.white,
),
),
)
: !state.isBusy && list == null
? SliverToBoxAdapter(
child: EmptyList(
'Follow someone',
subTitle:
'goto search page to find & follow Someone.\n When they added new post,\n they\'ll show up here.',
),
)
: SliverList(
delegate: SliverChildListDelegate(
list.map(
(model) {
return Container(
color: Colors.white,
child: Post(
model: model,
trailing: PostBottomSheet().PostOptionIcon(
context,
model: model,
type: PostType.Post,
scaffoldKey: scaffoldKey),
),
);
},
).toList(),
),
)
],
);
},
feed state code
List<FeedModel> get feedlist {
if (_feedlist == null) {
return null;
} else {
return List.from(_feedlist.reversed);
}
}
List<FeedModel> getPosttList(UserModel userModel) {
if (userModel == null) {
return null;
}
return feedlist;
}
I modified your code and use a ScrollController to load more data when the user reaches the end of the loaded data. (The data provider is hard-coded but you should be able to relate it to your scenario.) Note that I changed your code to use SliverChildBuilderDelegate which is more efficient.
import 'package:flutter/material.dart';
class ScrollTest extends StatefulWidget {
#override
_ScrollTestState createState() => _ScrollTestState();
}
class _ScrollTestState extends State<ScrollTest> {
bool isLoading = false;
bool isEnd = false;
final List<FeedModel> list = [];
ScrollController _controller;
_scrollListener() async {
var position = _controller.offset /
(_controller.position.maxScrollExtent -
_controller.position.minScrollExtent);
if (position > 0.5 && !_controller.position.outOfRange) {
await _getMoreData(list.length);
}
}
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getMoreData(list.length);
}
Future<void> _getMoreData(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var tlist = await Feed.getPostList(index);
setState(() {
if (tlist.length == 0) {
isEnd = true;
} else {
list.addAll(tlist);
index = list.length;
}
isLoading = false;
});
}
}
#override
Widget build(BuildContext context) {
return CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: Colors.white,
height: 300,
child: Text(list[index].text),
);
},
childCount: list.length,
),
),
SliverFillRemaining(
child: Center(
child: isEnd ? Text('End') : CircularProgressIndicator(),
)),
],
);
}
}
// Dummy FeedModel
class FeedModel {
final String text;
FeedModel(this.text);
}
// Dummy Feed provider
class Feed {
static final data = [
FeedModel('1'),FeedModel('2'),FeedModel('3'),FeedModel('4'),
FeedModel('5'),FeedModel('6'),FeedModel('7'),FeedModel('8'),
FeedModel('9'),FeedModel('10'),FeedModel('11'),FeedModel('12'),
FeedModel('13'),
];
static Future<List<FeedModel>> getPostList(int index) async {
List<FeedModel> l = [];
for (var i = index; i < index + 5 && i < data.length; i++) {
l.add(data[i]);
}
await Future.delayed(Duration(seconds: 1));
return l;
}
}