Reputation: 885
I try to use lazy load to show the order of the customer by using the ScrollController
.
Of course, the new user has a low number of orders and those items are not enough to take up the entire screen. So the ScrollController
doesn't work. What I can do?
This code will show a basic lazy load. You can change the _initialItemsLength
to a low value like 1 to see this issue.
You can try this at api.flutter.dev
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late List myList;
ScrollController _scrollController = ScrollController();
int _initialItemsLength = 10, _currentMax = 10;
@override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => "Item : ${i + 1}");
_scrollController.addListener(() {
print("scrolling: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
_getMoreData() {
print("load more: ${myList.length}");
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add("Item : ${i + 1}");
}
_currentMax = _currentMax + 10;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
}
First, start _initialItemsLength with 10. The scroller will be available and you will see it in the console. After that, change _initialItemsLength to 1. The console will be blank.
Upvotes: 0
Views: 459
Reputation: 885
The point is 'Find some parameter that can tell whether scroll is enabled or not. If not just load more until the scroll is enabled. Then use a basic step for a lazy load like the code in my question.'
After I find this parameter on google, I don't find this. But I try to check any parameter as possible. _scrollController.any
until I found this.
For someone who faces this issue like me.
You can detect the scroll is enabled by using _scrollController.position.maxScrollExtent == 0
with using some delay before that.
This is my code. You can see it works step by step in the console.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class PageStackoverflow72734370 extends StatefulWidget {
const PageStackoverflow72734370({Key? key}) : super(key: key);
@override
State<PageStackoverflow72734370> createState() => _PageStackoverflow72734370State();
}
class _PageStackoverflow72734370State extends State<PageStackoverflow72734370> {
late final List myList;
final ScrollController _scrollController = ScrollController();
final int _initialItemsLength = 1;
bool isScrollEnable = false, isLoading = false;
@override
void initState() {
super.initState();
print("\ninitState work!");
print("_initialItemsLength: $_initialItemsLength");
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() {
print("\nListener work!");
print("position: ${_scrollController.position.pixels}");
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) _getData();
});
_helper();
}
Future _helper() async {
print("\nhelper work!");
while (!isScrollEnable) {
print("\nwhile loop work!");
await Future.delayed(Duration.zero); //Prevent errors from looping quickly.
try {
print("maxScroll: ${_scrollController.position.maxScrollExtent}");
isScrollEnable = 0 != _scrollController.position.maxScrollExtent;
print("isScrollEnable: $isScrollEnable");
if (!isScrollEnable) _getData();
} catch (e) {
print(e);
}
}
print("\nwhile loop break!");
}
void _getData() {
print("\n_getData work!");
if (isLoading) return;
isLoading = true;
int i = myList.length;
int j = myList.length + 1;
for (i; i < j; i++) {
myList.add("Item : ${i + 1}");
}
print("myList.length: ${myList.length}");
isLoading = false;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return const CupertinoActivityIndicator();
}
return ListTile(title: Text(myList[i]));
},
itemCount: myList.length + 1,
),
);
}
}
You can test in my test. You can change the initial and incremental values at ?initial=10&incremental=1
.
I know, this case is rare. Most applications show more data widget height than the height of the screen or the data fetching 2 turns that enough for making these data widget height than the height of the screen. But I put these data widgets in the wrap for users that use the desktop app. So, I need it.
Upvotes: 0
Reputation: 2089
scroll listener will be triggered only if user try to scroll
as an option you need to check this condition _scrollController.position.pixels == _scrollController.position.maxScrollExtent
after build method executed and each time when user scroll to bottom
just change a bit initState
and _getMoreData
methods
@override
void initState() {
super.initState();
myList = List.generate(_initialItemsLength, (i) => 'Item : ${i + 1}');
_scrollController.addListener(() => _checkIsMaxScroll());
WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll());
}
void _checkIsMaxScroll() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_getMoreData();
}
}
_getMoreData() {
print('load more: ${myList.length}');
for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add('Item : ${i + 1}');
}
_currentMax = _currentMax + 10;
setState(() => WidgetsBinding.instance.addPostFrameCallback((_) => _checkIsMaxScroll()));
}
Upvotes: 1
Reputation: 2878
You can set your ListView
with physics: AlwaysScrollableScrollPhysics()
, and thus it will be scrollable even when the items are not too many. This will lead the listener to be triggered.
Key code part:
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, i) {
if (i == myList.length) {
return CupertinoActivityIndicator();
}
return ListTile(
title: Text(myList[i]),
);
},
itemCount: myList.length + 1,
),
);
}
Upvotes: 0