Flutter: ListView not displaying anything + lazy loading - flutter

I am new in flutter and trying to create a listview with load more functionality.
Here is my class. It is not displaying anything, blank screen. List has data I am getting result in console.
class ReportPurchaseNew extends StatefulWidget {
final AdminUserDetails userDetails;
final String title;
const ReportPurchaseNew({Key key, this.title, this.userDetails})
: super(key: key);
#override
State<StatefulWidget> createState() => new ReportPurchaseState();
}
class ReportPurchaseState extends State<ReportPurchaseNew> {
String fromDate = "", toDate = "", usageType = "";
int limit = 7, offset = 0;
static int page = 0;
List<Result> _List = new List();
List<Result> _filteredList;
Future<PurchaseReport> _PurchaseReportResponse;
List<UsageResult> _usageList = [];
UsageResult _usageVal;
ScrollController _sc = new ScrollController();
bool isLoading = false;
//List users = new List();
#override
void initState() {
this._getMorePurchase(page);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels ==
_sc.position.maxScrollExtent) {
_getMorePurchase(page);
}
});
}
#override
void dispose() {
_sc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Lazy Load Large List"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomInset: false,
);
}
Widget _buildList() {
return ListView.builder(
itemCount: _List.length + 1, // Add one more item for progress indicator
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (BuildContext context, int index) {
if (index == _List.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
leading: CircleAvatar(
radius: 30.0,
),
title :Text("my:"+(_List[index]
.product)),
subtitle: Text((_List[index]
.unitPrice)),
);
}
},
controller: _sc,
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
Future<PurchaseReport> getProjectDetails() async {
var result = await PurchaseReportRequest(
context,
widget.userDetails.result.raLoginId,
limit.toString(),
offset.toString(),
fromDate,
toDate,
_usageVal!=null ? _usageVal.name : "" ,
);
return result;
}
void _getMorePurchase(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
_PurchaseReportResponse = getProjectDetails();
setState(() {
isLoading = false;
_PurchaseReportResponse.then((response) {
if (response != null) {
_List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
print('name:' +_List[i].product );
}
} else {
errorRaisedToast(context);
}
});
});
}
}
}

Try This,
if (response != null) {
List newList = new List();
// _List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
newList.add(response.result[i]);
print('name:' +_List[i].product);
}
isLoading = false;
_List.addAll(newList);
page++;
} else {
errorRaisedToast(context);
}

Related

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.

Multiple showDialog for validation

On my project I need to use several showDialog one after the other.
For user creation, I use a SearchField widget to retrieve info from a table related to the user.
If the SearchField value does not exist I would like to propose the creation. Depending on the choice either the form is in error or I propose to register the user.
For this I use a showDialog in the validator of the SearchField and an if validator is correct.
My problem is that my second dialog box is displayed before validating the first one and even above that of the SearchField.
What is the correct way to do this?
Thank you,
class InformationsPage extends StatefulWidget {
const InformationsPage({
required Key key,
required this.user,
required this.type,
}) : super(key: key);
final User user;
final FenType type;
#override
InformationsPageState createState() => InformationsPageState();
}
class InformationsPageState extends State<InformationsPage>
with AutomaticKeepAliveClientMixin {
InformationsPageState({this.user});
final User? user;
late UserApi _api;
#override
bool get wantKeepAlive => true;
bool _familyIsCreated = false;
late User userSaved;
late FenType type;
//Info Form
var _pseudoController = TextEditingController();
var _familyController = TextEditingController();
#override
void initState() {
super.initState();
_api = UserApi();
_pseudoController = TextEditingController(text: widget.user.pseudo);
_familyController = TextEditingController(text: widget.user.familyName);
userSaved = User.fromUser();
type = widget.type;
}
#override
void dispose() {
_pseudoController.dispose();
_familyController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: <Widget>[
FutureBuilder(
future: _api.getFamilies(),
builder: (context, AsyncSnapshot<List<Family>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
"Something wrong with message: ${snapshot.error.toString()}"));
} else if (snapshot.connectionState == ConnectionState.done) {
List<Family> _list = snapshot.data!;
return _buildDropdownSearchFamilies(_list);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
TextFormField(
readOnly: type == FenType.read ? true : false,
inputFormatters: [LowerCaseTextFormatter()],
controller: _pseudoController,
onSaved: (value) => userSaved.pseudo = value,
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'Pseudo',
labelText: 'Pseudo',
),
validator: (value) =>
value!.isEmpty ? 'Obligatory' : null),
],
);
}
int? _contains(List<Family> list, String? name) {
int? res = -1;
for (Family element in list) {
if (element.name == name) {
res = element.id;
break;
}
}
return res;
}
Widget _buildDropdownSearchFamilies(List<Family> _list) {
return SearchField(
controller: _familyController,
suggestions: _list
.map((e) =>
SearchFieldListItem(e.name!, child: Text(e.name!), item: e.id))
.toList(),
hint: 'Family',
validator: (x) {
if (x!.isEmpty) {
userSaved.familyId = null;
userSaved.familyName = null;
return null;
}
int? id = _contains(_list, x);
if (id == -1) {
userSaved.familyId == null;
showDiaglog(x);
if (userSaved.familyId != null) {
return null;
} else {
return 'Family not exist';
}
} else {
userSaved.familyId = id;
userSaved.familyName = x;
return null;
}
},
searchInputDecoration: const InputDecoration(
labelText: 'Family', icon: Icon(Icons.groups)),
itemHeight: 50,
onTap: (x) {
userSaved.familyId = x.item as int?;
userSaved.familyName = x.child.toString();
});
}
showDiaglog(String family) async {
String title = "Family";
String message =
"Family $family not exist. Create ?";
String textKoButton = "no";
String textOkButton = "yes";
MyDialog alert = MyDialog(
title: title,
message: message,
onPressedKo: koButtonPressed(),
onPressedOk: okButtonPressed(family),
textKoButton: textKoButton,
textOkButton: textOkButton);
await showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
void Function() koButtonPressed() => () {
_familyIsCreated = false;
Navigator.of(context).pop(false);
};
void Function() okButtonPressed(family) => () {
_save(family);
Navigator.of(context).pop();
};
void _save(family) async {
UserApi apiUser = UserApi();
Family oldF = Family.empty();
Family newF = Family.empty();
newF.name = family;
newF.createdAt = oldF.createdAt;
newF.deletedAt = newF.deletedAt;
Map<String, dynamic> data = oldF.toJson(newF);
int res = -1;
res = await apiUser.createFamily(data);
SnackBar snackBar;
if (res != -1) {
snackBar = MyWidget.okSnackBar('Family created');
userSaved.familyId = res;
userSaved.familyName = family;
} else {
snackBar = MyWidget.koSnackBar(
'Family not created');
userSaved.familyId = null;
userSaved.familyName = null;
}
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
My form :
class UserFormPage extends StatefulWidget {
static const String routeName = '/admin/user-form';
final User? user;
final FenType fenType;
const UserFormPage({Key? key, required this.user, required this.fenType})
: super(key: key);
#override
_UserFormPageState createState() => _UserFormPageState();
}
class _UserFormPageState extends State<UserFormPage>
with SingleTickerProviderStateMixin {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_appState');
static final GlobalKey<InformationsPageState> _infoKey =
GlobalKey<InformationsPageState>();
late TabController _controller;
late User _user;
late User _userSaved;
#override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: 2);
_user = widget.user!;
_userSaved = widget.user!;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () =>
Navigator.pushReplacementNamed(context, Routes.admUserList),
),
title: const Text('Member'),
actions: <Widget>[
Visibility(
visible: widget.fenType != FenType.read ? true : false,
child: IconButton(
icon: const Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState!.validate()) {
return;
}
showDiaglog();
},
))
],
bottom: TabBar(
controller: _controller,
tabs: const [
Tab(text: 'Info'),
Tab(text: 'Others'),
],
),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Flexible(
child: TabBarView(
controller: _controller,
children: <Widget>[
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InformationsPage(
user: _user,
key: _infoKey,
type: widget.fenType),
])),
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DetailsPage(
user: _user,
key: _detailsKey,
type: widget.fenType)
],
)),
],
))
],
))),
);
}
void _save() async {
final infoState = _infoKey.currentState;
_userSaved = infoState?.userSaved ?? _user;
_userSaved.pseudo = infoState?.userSaved.pseudo ?? _user.pseudo;
Map<String, dynamic> data = _user.userToJsonClean(_userSaved);
if (!_userSaved.userIsUpdated()) {
final outSnackBar = MyWidget.okSnackBar('Not update');
ScaffoldMessenger.of(context).showSnackBar(outSnackBar);
} else {
UserApi apiUser = UserApi();
bool res = false;
res = widget.fenType == FenType.update
? await apiUser.update(data)
: await apiUser.create(data);
SnackBar snackBar;
res
? snackBar = MyWidget.okSnackBar('Member saved')
: snackBar = MyWidget.koSnackBar(
'Member not saved');
ScaffoldMessenger.of(context).showSnackBar(snackBar);
_user = _userSaved;
if (widget.fenType == FenType.create) {
Navigator.of(context).popAndPushNamed(Routes.admUserList);
}
}
}
void showDiaglog() {
String pseudo = _userSaved.pseudo!;
String title = "Save";
String message = widget.fenType == FenType.create
? "Create member $pseudo ?"
: "Save meber $pseudo ?";
String textKoButton = "no";
String textOkButton = "yes";
MyDialog alert = MyDialog(
title: title,
message: message,
onPressedKo: koButtonPressed(),
onPressedOk: okButtonPressed(),
textKoButton: textKoButton,
textOkButton: textOkButton);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
void Function() koButtonPressed() => () {
Navigator.of(context).pop(false);
};
void Function() okButtonPressed() => () {
_formKey.currentState!.save();
_save();
Navigator.of(context).pop();
};
}
I resolve this problem to modified the widget SearchField to a DropdownSearch.

List view not getting rebuild properly

I have this comment section in my application.
User can like a comment.
Whenever a comment is added, I call the database to get the latest comment and rebuild the class.
But as shown below When a new comment is added, it retains the state of the previous comment
as seen above when a not liked comment is added it is having state of previous comment
Below is the given code
#override
void initState() {
getComments();
super.initState();
}
getComments() async {
try {
List<Comment> commentList =
await Provider.of<Database>(context, listen: false)
.postComments(widget.postid);
setState(() {
data = commentList;
});
} catch (e) {
setState(() {
data = 'error';
});
// throw ('e');
}
}
dynamic data;
addComment(BuildContext context) async {
String commentData = _textEditingController.text.trim();
if (commentData.isNotEmpty) {
setState(() {
showShimmer = true;
_textEditingController.clear();
});
bool result = await Provider.of<Database>(context, listen: false)
.addMessageToPost(widget.postid, true, commentData);
if (result) {
getComments();
}
}
}
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: settingsAppBar(context, 'Comments'),
body: Column(
children: [
showShimmer
? Shimmer.fromColors(
baseColor: Colors.grey[200]!,
highlightColor: Colors.grey[100]!,
child: ShimmerWidget())
: Container(),
Expanded(
child: data == null
? Center(child: CircularProgressIndicator())
: data == 'error'
? Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () {
getComments();
},
child: Text('Retry')),
],
),
)
: data.length >= 1
? RefreshIndicator(
onRefresh: () async {
setState(() {
data = null;
});
getComments();
},
child: ListView(
controller: _scrollController,
children: buidlCommentWidgets()),
)
: Text('No data'),
),
SafeArea(...textfield) ],
),
),
],
),
),
],
),
);
}
List<Widget> buidlCommentWidgets() {
List<Widget> commentWidgetList = [];
for (var i = 0; i < data.length; i++) {
commentWidgetList.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 8.0),
child: CommentListTile(
comment: data[i],
postUid: widget.postedUid,
),
));
}
return commentWidgetList;
}
}
//////////////////////////////////////////////////////////////
class CommentListTile extends StatefulWidget {
const CommentListTile({
Key? key,
required this.comment,
required this.postUid,
this.isReply = false,
}) : super(key: key);
final Comment comment;
final String postUid;
final bool isReply;
#override
_CommentListTileState createState() => _CommentListTileState();
}
class _CommentListTileState extends State<CommentListTile> {
late bool isLiked;
late int likeCount;
handleLike(BuildContext context) async {
setState(() {
isLiked = !isLiked;
likeCount = isLiked ? likeCount + 1 : likeCount - 1;
});
bool result = await Provider.of<Database>(context, listen: false)
.postActivity(
PostActivityEnum.likeDislikeComment, widget.comment.commentId);
if (!result) {
setState(() {
isLiked = !isLiked;
likeCount = isLiked ? likeCount + 1 : likeCount - 1;
});
}
}
#override
void initState() {
isLiked = widget.comment.isLikedByViewer;
likeCount = widget.comment.likesCount;
super.initState();
}
#override
Widget build(BuildContext context) {
....ListTile
This is a very common problem in the flutter.
This happens due to the widget tree and the render tree difference, Identification of the widget by its own Key can be easier for the flutter to keep an update on both render and widget tree.
For an easy solution, you can pass a unique key to the CommentListTile widget while building the list and also while adding a new widget to the list.
List<Widget> buidlCommentWidgets() {
List<Widget> commentWidgetList = [];
for (var i = 0; i < data.length; i++) {
commentWidgetList.add(Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 8.0),
child: CommentListTile(
key: SOME_UNIQUE_KEY_HERE // Add Unique key here
comment: data[i],
postUid: widget.postedUid,
),
));
}
return commentWidgetList;
}
It can be easy if you have some kind of commentId.
EDIT: 27 Aug 2021
You definitely should use your buildCommentWidgets() method like this:
List<Widget> buidlCommentWidgets() {
return data.map((comment) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: CommentListTile(
key: SOME_UNIQUE_KEY_HERE // Add Unique key here
comment: comment,
postUid: widget.postedUid,
),
);
}).toList();
}

Flutter UI doesn't update with changes on StateNotifier

I want to create a list o buttons with text so that the user can select one of them.
For the state of the buttons I used a StateNotifier:
class SeleccionStateNotifier extends StateNotifier<List<bool>> {
int cantidad;
SeleccionStateNotifier(this.cantidad)
: super(List.generate(cantidad, (index) => false));
void CambioValor(int indice) {
for (int i = 0; i < cantidad; i++) {
if (i == indice) {
state[i] = true;
} else {
state[i] = false;
}
}
}
}
final seleccionProvider =
StateNotifierProvider<SeleccionStateNotifier, List<bool>>((ref) {
final lector = ref.watch(eleccionesSinSeleccionStateNotifierProvider);
return SeleccionStateNotifier(lector.length);
});
Now in the UI I just want to show a text and the value of the button (false for everyone except the one the user selects)
class EleccionesList5 extends ConsumerWidget {
const EleccionesList5({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
bool este = false;
final eleccioneswatchCon =
watch(eleccionesSinSeleccionStateNotifierProvider);
final seleccionwatch = watch(seleccionProvider);
final buttons = List<Widget>.generate(
eleccioneswatchCon.length,
(i) => Container(
padding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
child: TextButton(
onPressed: () {
context.read(seleccionProvider.notifier).CambioValor(i);
print('OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO');
for (int id = 0; id < eleccioneswatchCon.length; id++) {
print(seleccionwatch[id]);
}
},
child: Text(eleccioneswatchCon[i].eleccion + ' ' + seleccionwatch[i].toString()),
),
),
).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Wrap(
alignment: WrapAlignment.spaceAround,
children: buttons,
),
],
);
}
}
based on my previous answer you should update state itself, not an inner value of a list to take effect
void CambioValor(int indice) {
final list = List.of(state);
for (int i = 0; i < cantidad; i++) {
if (i == indice) {
list[i] = true;
} else {
list[i] = false;
}
}
/// it will update when you use the setter of state
state = list;
}

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