I am trying to implement a infinite scroll pagnation in Flutter using provider for state management.
I have two page, HomePage() and TestingPage().On start up, the code is able to successfully load data from the api endpoint but the retrieved data was not displayed by the listview builder.The data is only displayed if i switch to the TestingPage() and back to HomePage().
The code will load more data if i reach the end of the scroll view, but the same problem is happening again, list view builder will not display the newly loaded data. It will only display it if i switch to TestingPage() and back to HomePage().
Reposting this because i made a mistake of posting the wrong code.
// UserInfo.dart
class UserInfo {
int userId;
int id;
String title;
String body;
UserInfo(this.userId, this.id, this.title, this.body);
factory UserInfo.fromJson(Map<String, dynamic> json) {
final userId = json['userId'];
final id = json['id'];
final title = json['title'];
final body = json['body'];
return UserInfo(userId, id, title, body);
}
@override
String toString() {
return "id: $id";
}
}
// user_info_response.dart
import 'package:end_point/models/UserInfo.dart';
class ListOfUserInfoResponse {
int? code;
List<UserInfo> listOfUserInfo;
ListOfUserInfoResponse({this.code, required this.listOfUserInfo});
factory ListOfUserInfoResponse.fromJson(int statusCode, List<dynamic> json) {
List<dynamic> list = json;
return ListOfUserInfoResponse(
code: statusCode,
listOfUserInfo: list.map((i) => UserInfo.fromJson(i)).toList());
}
}
// user_info_api.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:end_point/models/API/user_info_response.dart';
Future<ListOfUserInfoResponse> getListOfUserInfo(page) async {
final response = await http.get(Uri.parse(
"https://jsonplaceholder.typicode.com/posts?_limit=15&_page=$page"));
if (response.statusCode == 200) {
return ListOfUserInfoResponse.fromJson(
response.statusCode, jsonDecode(response.body));
} else {
throw Exception("Failed to load data");
}
}
// user_info_provider.dart
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:end_point/models/UserInfo.dart';
import 'package:end_point/api/user_info_api.dart' as UserInfoAPI;
import 'package:end_point/models/API/user_info_response.dart';
class UserInfoProvider with ChangeNotifier {
int _page = 1;
int get page => _page;
bool _isLoading = false;
bool get isLoading => _isLoading;
List<UserInfo> _listOfUserData = [];
List<UserInfo> get listOfUserData => _listOfUserData;
set listOfUserData(List<UserInfo> listOfUserData) {
_listOfUserData = listOfUserData;
notifyListeners();
}
bool _hasMore = true;
bool get hasMore => _hasMore;
Future<void> loadMoreData() async {
if (_isLoading) return;
_isLoading = true;
ListOfUserInfoResponse response =
await UserInfoAPI.getListOfUserInfo(_page);
_listOfUserData.addAll(response.listOfUserInfo);
_page++;
_isLoading = false;
if (response.listOfUserInfo.length < 20) {
_hasMore = false;
}
notifyListeners();
}
void refreshData() async {
_isLoading = false;
_hasMore = true;
_page = 1;
listOfUserData.clear();
notifyListeners();
}
}
// home.dart
import 'package:end_point/providers/user_info_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:developer';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _scrollController = ScrollController();
late UserInfoProvider userInfoProvider;
@override
void initState() {
super.initState();
userInfoProvider = Provider.of<UserInfoProvider>(context, listen: false);
userInfoProvider.loadMoreData();
_scrollController.addListener(_onScroll);
}
Future<void> _onScroll() async {
if (_scrollController.position.maxScrollExtent ==
_scrollController.offset) {
await userInfoProvider.loadMoreData();
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future refresh() async {
userInfoProvider.refreshData();
await userInfoProvider.loadMoreData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("END POINT LEGGO")),
body: RefreshIndicator(
key: UniqueKey(),
onRefresh: refresh,
child: ListView.builder(
key: const PageStorageKey<String>('HP'),
itemCount: userInfoProvider.listOfUserData.length,
controller: _scrollController,
shrinkWrap: true,
itemBuilder: (context, index) {
final id = userInfoProvider.listOfUserData[index].id;
if (index < userInfoProvider.listOfUserData.length) {
return Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
height: 50,
width: 200,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.red),
child: ListTile(title: Text('$id'))),
);
} else {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Center(
child: userInfoProvider.hasMore
? const CircularProgressIndicator()
: const Text('No more data to load'),
));
}
}),
));
}
I found the fix to this problem, @home.dart instead of using the UserInfoProvider instance that was declared in the initState().
Simply declare a new userInfoProvider instance inside the Widget build(). E.g. final data = Provider.of(context);
From what I understood, this is because the instance of the UserInfoProvider that was declared in the initState() has listen: false. false value will mean that any value changes, will not trigger a rebuild of the widget.