Very Good Ventures(VGV) が無限スクロール用のパッケージを用意してくれてました。
今回もRiverpodと合わせて使ってみたいと思います。
同じくVGV製のformzを扱った記事を以前書いてます
[Flutter] formz+riverpodを使ったフォームの実装例
早速ですがサンプルコードです (Github):
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
part 'main.g.dart';
@riverpod
class TodoList extends _$TodoList {
@override
Future<List<String>> build() async {
await Future.delayed(const Duration(seconds: 1));
return List.generate(10, (i) => 'ToDo Item $i');
}
Future<void> fetchTodo() async {
final previousState = await future;
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await Future.delayed(const Duration(seconds: 1));
return previousState +
List.generate(10, (i) => 'ToDo Item ${previousState.length + i}');
});
}
}
void main() => runApp(const ProviderScope(child: MyApp()));
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Simple Example'),
),
body: Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
final AsyncValue<List<String>> val = ref.watch(todoListProvider);
return InfiniteList(
itemCount: val.value?.length ?? 0,
isLoading: val.isLoading,
onFetchData: ref.read(todoListProvider.notifier).fetchTodo,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
dense: true,
title: Text(val.value?[index] ?? ''),
);
},
);
},
),
),
);
}
}
例によって使いやすい印象です!
formzもそうですが、中の実装を覗いてみるとシンプルな作りになっているようです
幾つかInfiniteList
のオプションを試してみます。
スクロールの向き
reverse: true
で、スクロールが逆向きになります。チャットっぽいUIにも使えそう
return InfiniteList(
+ reverse: true,
itemCount: val.value?.length ?? 0,
isLoading: val.isLoading,
onFetchData: ref.read(todoListProvider.notifier).fetchTodo,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
ローディングのインジケータ
loadingBuilder
でローディング用のインジケータの見た目を変更出来ました。
centerLoading
は初期ロードのインジケータの位置の調整でしょうか...
return InfiniteList(
+ loadingBuilder: (context) => +const Center(
+ child: CircularProgressIndicator(
+ valueColor: AlwaysStoppedAnimation(Colors.purpleAccent),
+ )),
+ centerLoading: true,
itemCount: val.value?.length ?? 0,
isLoading: val.isLoading,
onFetchData: ref.read(todoListProvider.notifier).fetchTodo,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
エラー表示
3回目のfetchでエラーが出るようにしておきます
Future<void> fetchTodo() async {
final previousState = await future;
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await Future.delayed(const Duration(seconds: 1));
+ if (previousState.length >= 30) throw 'Unknown Error!!!';
return previousState +
List.generate(10, (i) => 'ToDo Item ${previousState.length + i}');
});
hasError
と errorBuilder
を使うことで、エラー表示をカスタマイズできました:
return InfiniteList(
+ hasError: val.hasError,
+ errorBuilder: (context) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Container(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8.0),
+ color: Colors.redAccent,
+ ),
+ child: Text(
+ val.error.toString(),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 16.0,
+ ),
+ ),
+ ),
+ );
+ },
itemCount: val.value?.length ?? 0,
まとめ
very_good_infinite_list
とRiverpod
で無限スクロールを実装してみました。
hasReachedMax
や debounceDuration
などを調整すれば、実際の業務にも使えそうです
もっと良い使い方があれば、ぜひ教えて下さい