Search code examples
flutterinfinite-scrollflutter-providerflutter-listview

ListView Builder not building properly


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

Solution

  • 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.