Line data Source code
1 : import 'dart:async'; 2 : import 'dart:collection'; 3 : 4 : import 'package:flutter/cupertino.dart'; 5 : import 'package:rx_bloc/rx_bloc.dart'; 6 : import 'package:rxdart/rxdart.dart'; 7 : 8 : part 'paginated_list_extensions.dart'; 9 : 10 : /// PaginatedList class is an extension on the list class that allows for easier 11 : /// data manipulation and work with paginated data. 12 : /// 13 : /// The [PaginatedList] class requires a [list] of data and a [pageSize] integer 14 : /// which will allow the list to be separated into multiple pages. 15 : /// 16 : /// If the number of total items is known in advance, it can be specified as the 17 : /// [totalCount] parameter. 18 : /// 19 : /// The [isLoading] parameter represents whether the data is loading or has 20 : /// loaded. This can be useful when building ui-related refresh states. 21 : /// 22 : /// If an error occurs while using the paginated list, it can be stored inside 23 : /// the [error] parameter. Usually you would store any data fetching or related 24 : /// errors. 25 : /// 26 : /// The number of loaded pages can be accessed any time by referring to the 27 : /// [pageNumber] value. Please note, that the number of pages is affected by the 28 : /// actual [length] of the list and the [pageSize]. 29 : /// 30 : /// Unlike the [length] of the list which is the actual number of items in the 31 : /// list, the [itemCount] represents the number of items that should be rendered 32 : /// on screen. The [itemCount] is equal to [length], unless the next page is 33 : /// loading. Then, the [itemCount] also accounts for the loading widget at the 34 : /// bottom of the list in case there are more items to be loaded. 35 : /// 36 : /// In order to tell whether there is a next page to be loaded, [hasNextPage] 37 : /// can be used for that case. 38 : /// 39 : /// Getters [isInitialLoading] and [isNextPageLoading] reflect the current 40 : /// loading state of the list. If there is no data and [isLoading] is true, the 41 : /// getter [isInitialLoading] will return true. Similarly, if [isLoading] is 42 : /// true and we can load a new page, the getter [isNextPageLoading] will return 43 : /// true. 44 : /// 45 : class PaginatedList<E> extends ListBase<E> { 46 : /// PaginatedList constructor 47 2 : PaginatedList({ 48 : required this.list, 49 : required this.pageSize, 50 : this.error, 51 : this.totalCount, 52 : this.isLoading = false, 53 : bool isInitialized = false, 54 : }) : _isInitialized = isInitialized; 55 : 56 : /// The list containing the actual data. 57 : final List<E> list; 58 : 59 : /// The number of elements per one page. 60 : final int pageSize; 61 : 62 : /// If an exception is thrown, the [error] field will capture it. 63 : final Exception? error; 64 : 65 : /// Optional field containing total number of elements (if known). 66 : final int? totalCount; 67 : 68 : /// Indicates whether the paginated list is loading or not. 69 : final bool isLoading; 70 : 71 : /// Indicates whether the paginated list has been populated with data 72 : final bool _isInitialized; 73 : 74 : /// Temporary list which stores data in between refreshes 75 : List<E> _backupList = []; 76 : 77 : /// Setter for the length of the list. 78 2 : @override 79 4 : set length(int newLength) => list.length = newLength; 80 : 81 : /// Returns the actual list length. 82 2 : @override 83 4 : int get length => list.length; 84 : 85 : /// Returns the number of items in a list. When loading a new page, the number 86 : /// of items is increased by one, which can be used to represent the bottom 87 : /// loading widget. 88 2 : int get itemCount => 89 11 : hasNextPage && isNextPageLoading ? list.length + 1 : list.length; 90 : 91 : /// The number of loaded pages. 92 14 : int get pageNumber => list.isNotEmpty ? (length / pageSize).ceil() : 0; 93 : 94 : /// The next page to load 95 2 : int get pageToLoad => pageNumber; 96 : 97 : /// Getter used for telling us whether there is a new page to load. 98 8 : bool get hasNextPage => totalCount == null || list.length < totalCount!; 99 : 100 : /// Getter used for telling us whether this the initial data being loaded. 101 10 : bool get isInitialLoading => isLoading && list.isEmpty && !_isInitialized; 102 : 103 : /// Getter for telling us whether we are loading a new page. 104 8 : bool get isNextPageLoading => isLoading && list.isNotEmpty; 105 : 106 2 : @override 107 4 : E operator [](int index) => list[index]; 108 : 109 1 : @override 110 : void operator []=(int index, E value) { 111 2 : list[index] = value; 112 : } 113 : 114 : /// Returns a modified version of the current PaginatedList 115 2 : PaginatedList<E> copyWith({ 116 : List<E>? list, 117 : bool? isLoading, 118 : int? totalCount, 119 : Exception? error, 120 : int? pageSize, 121 : bool? isInitialized, 122 : }) => 123 2 : PaginatedList( 124 1 : list: list ?? this.list, 125 1 : isLoading: isLoading ?? this.isLoading, 126 2 : totalCount: totalCount ?? this.totalCount, 127 2 : pageSize: pageSize ?? this.pageSize, 128 2 : isInitialized: isInitialized ?? _isInitialized, 129 : error: error, 130 4 : ).._backupList = _backupList; 131 : 132 : /// Returns element at given index. If element outside bound, null is returned 133 12 : E? getItem(int index) => (index >= length || index < 0) ? null : list[index]; 134 : 135 : /// Resets the list data 136 2 : void reset({bool hard = false}) { 137 4 : _backupList.clear(); 138 : 139 2 : if (hard == false) { 140 6 : _backupList.addAll(list); 141 : } 142 : 143 2 : length = 0; 144 : } 145 : 146 1 : @override 147 : operator ==(other) => 148 1 : other is PaginatedList<E> && 149 3 : other.pageSize == pageSize && 150 3 : other.error == error && 151 3 : other.totalCount == totalCount && 152 3 : other.isLoading == isLoading && 153 3 : other.list == list; 154 : 155 1 : @override 156 : int get hashCode => 157 3 : pageSize.hashCode ^ 158 3 : error.hashCode ^ 159 3 : totalCount.hashCode ^ 160 3 : isLoading.hashCode ^ 161 3 : _isInitialized.hashCode ^ 162 2 : list.hashCode; 163 : 164 1 : @override 165 1 : String toString() => 166 3 : '{pageSize: $pageSize, error: $error, totalCount: $totalCount, ' 167 2 : 'isLoading: $isLoading, list: $list}'; 168 : }