diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/example/lib/main.dart b/example/lib/main.dart index c6546be..c86f9c6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -105,7 +105,7 @@ class _CustomInfiniteList extends StatelessWidget { }, ), onError: (context, retry, error) { - Scaffold.of(context) + ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar(SnackBar( content: Text(error.toString()), @@ -119,7 +119,7 @@ class _CustomInfiniteList extends StatelessWidget { } } -Future> _itemLoader(int limit, {int start = 0}) async { +Future?> _itemLoader(int limit, {int start = 0}) async { await Future.delayed(const Duration(seconds: 1)); if (start >= 100) return null; if (Random().nextInt(2) == 0) throw Exception('Oops!'); @@ -136,9 +136,9 @@ class _Loading extends StatelessWidget { class _Error extends StatelessWidget { const _Error({ - Key key, - this.error, - this.retry, + Key? key, + required this.error, + required this.retry, }) : super(key: key); final Object error; @@ -153,7 +153,7 @@ class _Error extends StatelessWidget { children: [ Text( error.toString(), - style: theme.textTheme.headline4.copyWith(color: theme.errorColor), + style: theme.textTheme.headline4?.copyWith(color: theme.errorColor), textAlign: TextAlign.center, ), const SizedBox(height: 16), @@ -169,7 +169,7 @@ class _Error extends StatelessWidget { } class _ErrorLoader extends StatelessWidget { - const _ErrorLoader({Key key, @required this.retry}) : super(key: key); + const _ErrorLoader({Key? key, required this.retry}) : super(key: key); final VoidCallback retry; @override diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 419028c..52b9165 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -14,7 +14,7 @@ dependencies: path: ../ dev_dependencies: - very_good_analysis: ^1.0.4 + very_good_analysis: ^2.0.0 flutter: uses-material-design: true diff --git a/lib/very_good_infinite_list.dart b/lib/very_good_infinite_list.dart index d34c7b5..9e7cdbc 100644 --- a/lib/very_good_infinite_list.dart +++ b/lib/very_good_infinite_list.dart @@ -19,7 +19,7 @@ class InfiniteListException implements Exception {} /// /// * [limit] is the number of items you'd like to fetch. /// * [start] is an optional offset which defaults to 0. -typedef ItemLoader = Future> Function(int limit, {int start}); +typedef ItemLoader = Future?> Function(int limit, {int start}); /// Function which returns a [Widget] given a [context], [retry], and [error]. /// Used by [InfiniteList] to render widgets in response to exceptions thrown @@ -48,15 +48,15 @@ typedef OnError = void Function( class InfiniteListBuilder { /// {@macro infinite_list_builder} const InfiniteListBuilder({ - @required this.success, - WidgetBuilder loading, - ErrorBuilder error, - WidgetBuilder empty, + required this.success, + WidgetBuilder? loading, + ErrorBuilder? error, + WidgetBuilder? empty, }) : _loading = loading, _error = error, _empty = empty; - final WidgetBuilder _loading; + final WidgetBuilder? _loading; /// [WidgetBuilder] which is invoked when the [InfiniteList] /// is rendered while content is being fetched by the [ItemLoader]. @@ -73,7 +73,7 @@ class InfiniteListBuilder { /// retrieved from the [ItemLoader]. final Widget Function(BuildContext, T) success; - final ErrorBuilder _error; + final ErrorBuilder? _error; /// [WidgetBuilder] which is invoked when the [InfiniteList] /// is rendered and an [InfiniteListException] @@ -89,7 +89,7 @@ class InfiniteListBuilder { }; } - final WidgetBuilder _empty; + final WidgetBuilder? _empty; /// [WidgetBuilder] which is invoked when the [InfiniteList] /// is rendered and the [ItemLoader] has returned an empty list. @@ -109,20 +109,18 @@ class InfiniteListBuilder { class InfiniteList extends StatefulWidget { /// {@macro infinite_list} const InfiniteList({ - Key key, - @required this.itemLoader, - @required this.builder, - WidgetBuilder bottomLoader, - ErrorBuilder errorLoader, + Key? key, + required this.itemLoader, + required this.builder, + WidgetBuilder? bottomLoader, + ErrorBuilder? errorLoader, + ScrollController? scrollController, this.debounceDuration, this.reverse = false, this.onError, this.padding, - ScrollController scrollController, - double scrollOffsetThreshold, - }) : assert(itemLoader != null), - assert(builder != null), - _bottomLoader = bottomLoader, + double? scrollOffsetThreshold, + }) : _bottomLoader = bottomLoader, _errorLoader = errorLoader, _scrollController = scrollController, _scrollOffsetThreshold = @@ -130,7 +128,7 @@ class InfiniteList extends StatefulWidget { super(key: key); /// The amount of space by which to inset the children of the [builder]. - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// {@macro infinite_list_builder} final InfiniteListBuilder builder; @@ -139,7 +137,7 @@ class InfiniteList extends StatefulWidget { /// to lazily fetch content. final ItemLoader itemLoader; - final WidgetBuilder _bottomLoader; + final WidgetBuilder? _bottomLoader; /// [WidgetBuilder] which is responsible for rendering the bottom loader /// widget which is rendered when the user scrolls to the bottom of the list @@ -152,7 +150,7 @@ class InfiniteList extends StatefulWidget { ); } - final ErrorBuilder _errorLoader; + final ErrorBuilder? _errorLoader; /// [WidgetBuilder] which is responsible for rendering the bottom loader /// widget which is rendered when additional content is unable to be loaded @@ -167,13 +165,13 @@ class InfiniteList extends StatefulWidget { ); /// {@macro on_error} - final OnError onError; + final OnError? onError; - final ScrollController _scrollController; + final ScrollController? _scrollController; /// Debounce duration for the [itemLoader]. /// Defaults to `const Duration(milliseconds: 100)`. - final Duration debounceDuration; + final Duration? debounceDuration; /// Whether the scroll view scrolls in the reading direction. /// @@ -196,9 +194,9 @@ class InfiniteList extends StatefulWidget { } class _InfiniteListState extends State> { - ScrollController _scrollController; - _ListController _controller; - _Debouncer _debouncer; + late ScrollController _scrollController; + late _ListController _controller; + late _Debouncer _debouncer; void _onListStateChanged() { final state = _controller.value; @@ -225,7 +223,7 @@ class _InfiniteListState extends State> { @override void dispose() { - _debouncer?.dispose(); + _debouncer.dispose(); _controller ..removeListener(_onListStateChanged) ..dispose(); @@ -250,7 +248,7 @@ class _InfiniteListState extends State> { Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _controller, - builder: (context, state, child) { + builder: (context, _ListState state, child) { final itemCount = state.hasReachedMax == false ? state.items.length + 1 : state.items.length; @@ -314,9 +312,9 @@ class _InfiniteListState extends State> { class _DefaultError extends StatelessWidget { const _DefaultError({ - Key key, - this.error, - this.retry, + Key? key, + required this.error, + required this.retry, }) : super(key: key); final Object error; @@ -331,7 +329,7 @@ class _DefaultError extends StatelessWidget { children: [ Text( '$error', - style: theme.textTheme.headline4.copyWith(color: theme.errorColor), + style: theme.textTheme.headline4?.copyWith(color: theme.errorColor), textAlign: TextAlign.center, ), const SizedBox(height: 16), @@ -346,7 +344,7 @@ class _DefaultError extends StatelessWidget { } class _DefaultErrorLoader extends StatelessWidget { - const _DefaultErrorLoader({Key key, @required this.retry}) : super(key: key); + const _DefaultErrorLoader({Key? key, required this.retry}) : super(key: key); final VoidCallback retry; @override @@ -368,7 +366,7 @@ class _ListException implements Exception { final Object value; - static const none = _ListException(null); + static const none = _ListException(Object()); } class _ListState { @@ -387,11 +385,11 @@ class _ListState { final _ListException exception; _ListState copyWith({ - @required _ListStatus status, - int currentIndex, - List items, - bool hasReachedMax, - _ListException exception, + required _ListStatus status, + int? currentIndex, + List? items, + bool? hasReachedMax, + _ListException? exception, }) { return _ListState( currentIndex: currentIndex ?? this.currentIndex, @@ -455,10 +453,10 @@ class _ListController extends ValueNotifier<_ListState> { } class _Debouncer { - _Debouncer({Duration delay}) : _delay = delay ?? _kDebounceDuration; + _Debouncer({Duration? delay}) : _delay = delay ?? _kDebounceDuration; final Duration _delay; - Timer _timer; + Timer? _timer; void call(void Function() action) { _timer?.cancel(); diff --git a/pubspec.yaml b/pubspec.yaml index b9ee70c..59e6563 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,15 +2,15 @@ name: very_good_infinite_list description: >- A Very Good Infinite List Widget created by Very Good Ventures. Comes in handy when making activity feeds, news feeds, etc. -version: 0.2.1 +version: 0.3.0 repository: https://github.com/VeryGoodOpenSource/very_good_infinite_list issue_tracker: https://github.com/VeryGoodOpenSource/very_good_infinite_list/issues homepage: https://github.com/VeryGoodOpenSource/very_good_infinite_list documentation: https://github.com/VeryGoodOpenSource/very_good_infinite_list environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.0" dependencies: flutter: @@ -19,4 +19,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^1.0.4 + very_good_analysis: ^2.0.0 diff --git a/test/very_good_infinite_list_test.dart b/test/very_good_infinite_list_test.dart index 0b13ffa..5d60db9 100644 --- a/test/very_good_infinite_list_test.dart +++ b/test/very_good_infinite_list_test.dart @@ -4,31 +4,9 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart'; void main() { group('InfiniteList', () { - group('constructor', () { - test('throws AssertionError when itemLoader is null', () { - expect( - () => InfiniteList( - itemLoader: null, - builder: InfiniteListBuilder(success: (_, __) => const SizedBox()), - ), - throwsAssertionError, - ); - }); - - test('throws AssertionError when builder is null', () { - expect( - () => InfiniteList( - itemLoader: (int limit, {int start}) async => [], - builder: null, - ), - throwsAssertionError, - ); - }); - }); - testWidgets('updates scroll controller when changed', (tester) async { var itemLoaderCallCount = 0; - final itemLoader = (int limit, {int start}) async { + final itemLoader = (int limit, {int? start}) async { itemLoaderCallCount++; return List.generate(15, (i) => i); }; @@ -91,7 +69,7 @@ void main() { }); testWidgets('reverse updates list view', (tester) async { - final itemLoader = (int limit, {int start}) async { + final itemLoader = (int limit, {int? start}) async { return List.generate(15, (i) => i); }; @@ -110,7 +88,7 @@ void main() { }); testWidgets('list view is not reversed by default', (tester) async { - final itemLoader = (int limit, {int start}) async { + final itemLoader = (int limit, {int? start}) async { return List.generate(15, (i) => i); }; @@ -128,7 +106,7 @@ void main() { }); testWidgets('list view supports custom padding', (tester) async { - final itemLoader = (int limit, {int start}) async { + final itemLoader = (int limit, {int? start}) async { return List.generate(15, (i) => i); }; const padding = EdgeInsets.all(16); @@ -148,7 +126,7 @@ void main() { testWidgets('invokes itemLoader immediately', (tester) async { var itemLoaderCallCount = 0; - final itemLoader = (int limit, {int start}) { + final itemLoader = (int limit, {int? start}) async { itemLoaderCallCount++; }; @@ -162,7 +140,7 @@ void main() { testWidgets('renders default loading widget by default', (tester) async { await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async => [], + itemLoader: (int limit, {int? start}) async => [], builder: InfiniteListBuilder(success: (_, __) => const SizedBox()), )); @@ -172,7 +150,7 @@ void main() { testWidgets('renders default bottom loader widget by default', (tester) async { await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { return List.generate(1, (i) => i); }, builder: InfiniteListBuilder(success: (_, __) => const SizedBox()), @@ -190,15 +168,15 @@ void main() { (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, - (int limit, {int start}) async { + (int limit, {int? start}) async { throw Exception('oops'); }, ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -240,15 +218,15 @@ void main() { testWidgets('renders default error widget by default', (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, - (int limit, {int start}) async { + (int limit, {int? start}) async { throw InfiniteListException(); }, ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -290,15 +268,15 @@ void main() { testWidgets('retry from first time failure', (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { throw InfiniteListException(); }, - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -326,18 +304,18 @@ void main() { testWidgets('retry from subsequent failure (critical)', (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, - (int limit, {int start}) async { + (int limit, {int? start}) async { throw InfiniteListException(); }, - (int limit, {int start}) async { - return List.generate(15, (i) => i + start); + (int limit, {int? start}) async { + return List.generate(15, (i) => i + start!); }, ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -384,18 +362,18 @@ void main() { testWidgets('retry from subsequent failure', (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, - (int limit, {int start}) async { + (int limit, {int? start}) async { throw Exception('oops'); }, - (int limit, {int start}) async { - return List.generate(15, (i) => i + start); + (int limit, {int? start}) async { + return List.generate(15, (i) => i + start!); }, ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -443,13 +421,13 @@ void main() { (tester) async { var itemLoaderCallCount = 0; final itemLoaderResults = [ - (int limit, {int start}) async { + (int limit, {int? start}) async { return List.generate(15, (i) => i); }, - (int limit, {int start}) async => [], + (int limit, {int? start}) async => [], ]; await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { itemLoaderCallCount++; return itemLoaderResults.removeAt(0).call(limit, start: start); }, @@ -494,7 +472,7 @@ void main() { testWidgets('renders default empty widget by default', (tester) async { await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async => [], + itemLoader: (int limit, {int? start}) async => [], builder: InfiniteListBuilder(success: (_, __) => const SizedBox()), )); @@ -505,7 +483,7 @@ void main() { testWidgets('renders default error widget by default (generic)', (tester) async { await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { throw Exception('oops'); }, builder: InfiniteListBuilder(success: (_, __) => const SizedBox()), @@ -518,7 +496,7 @@ void main() { testWidgets('renders default error widget by default (specific)', (tester) async { await tester.pumpApp(InfiniteList( - itemLoader: (int limit, {int start}) async { + itemLoader: (int limit, {int? start}) async { throw InfiniteListException(); }, builder: InfiniteListBuilder(success: (_, __) => const SizedBox()),