Flutter状态管理插件offset_iterator_riverpod的使用
Flutter状态管理插件offset_iterator_riverpod的使用
offset_iterator_riverpod
是一个用于创建基于 OffsetIterator
的 Riverpod 提供器的辅助函数库。它可以帮助你在应用中高效地处理分页数据。
示例代码
以下是一个完整的示例,展示了如何使用 offset_iterator_riverpod
来实现分页加载功能。
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:offset_iterator_riverpod/offset_iterator_riverpod.dart';
/// 类型别名,使代码更易读
typedef Post = Map<String, dynamic>;
/// 获取一页帖子的函数
Future<List<Post>> fetchPosts(int page, int limit) =>
Dio().get('https://jsonplaceholder.typicode.com/posts', queryParameters: {
'_limit': '$limit',
'_page': '$page',
}).then((r) => List<Post>.from(r.data));
/// [OffsetIterator] 的帖子
OffsetIterator<List<Post>> postsIterator() => OffsetIterator(
init: () => 1,
process: (page) async {
// 忽略:避免打印
print('Fetching page $page...');
final posts = await fetchPosts(page, 30);
return OffsetIteratorState(
acc: page + 1, // 增加页码
chunk: [posts], // 发射帖子列表
hasMore: posts.length == 30, // 如果只收到部分页面,则停止
);
},
)
// 累积将所有页面合并为一个长列表
.accumulate()
// 预取会多获取一页,以加快列表加载速度
.prefetch();
/// 使用 [iteratorValueProvider] 暴露 [OffsetIteratorAsyncValue] 的 [Provider]
final postsProvider = Provider.autoDispose(
(AutoDisposeProviderRef<OffsetIteratorAsyncValue<List<Post>>> ref) =>
iteratorValueProvider<List<Post>>(ref)(postsIterator()));
/// 使用 [postsProvider] 中的值的包装小部件
/// 它处理数据 / 错误 / 加载状态
class PostsListContainer extends ConsumerWidget {
const PostsListContainer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(postsProvider);
return state.value.when(
data: (posts) => PostsList(
posts: posts,
hasMore: state.hasMore,
loadMore: state.pull,
),
error: (err, stack) => SliverFillRemaining(
child: Center(child: Text('Error: $err')),
),
loading: () => const SliverFillRemaining(
child: Center(child: Text('Loading...')),
),
);
}
}
/// 使用 [ListView.builder] 显示帖子列表的小部件
class PostsList extends StatelessWidget {
PostsList({
Key? key,
required this.posts,
required this.hasMore,
required this.loadMore,
}) : super(key: key);
final List<Post> posts;
final bool hasMore;
final void Function() loadMore;
late final postsLength = posts.length;
@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final post = posts[index];
// 在列表末尾加载更多帖子
if (hasMore && index == postsLength - 1) {
loadMore();
}
return ListTile(
title: Text(post['title']),
subtitle: Text(post['body']),
);
},
childCount: postsLength,
),
);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// 这个小部件是你的应用程序的根。
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'offset_iterator_riverpod'),
),
);
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => ref.refresh(postsProvider),
child: const Icon(Icons.refresh),
),
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text(title),
floating: true,
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
bottom: 5,
left: 16,
top: 25,
),
child: Text('Posts:', style: theme.textTheme.headlineSmall!),
),
),
const PostsListContainer(),
const SliverToBoxAdapter(child: SizedBox(height: 30)),
],
),
);
}
}
解释
-
定义类型别名:
typedef Post = Map<String, dynamic>;
这里定义了一个类型别名
Post
,简化了后续代码的可读性。 -
获取一页帖子的函数:
Future<List<Post>> fetchPosts(int page, int limit) => Dio().get('https://jsonplaceholder.typicode.com/posts', queryParameters: { '_limit': '$limit', '_page': '$page', }).then((r) => List<Post>.from(r.data));
这个函数通过
Dio
库从 API 获取帖子数据,并根据指定的页码和每页条目数进行查询。 -
定义
OffsetIterator
:OffsetIterator<List<Post>> postsIterator() => OffsetIterator( init: () => 1, process: (page) async { print('Fetching page $page...'); final posts = await fetchPosts(page, 30); return OffsetIteratorState( acc: page + 1, chunk: [posts], hasMore: posts.length == 30, ); }, ).accumulate().prefetch();
这里定义了一个
OffsetIterator
,用于分页获取数据。init
函数初始化页码,process
函数获取并处理每一页的数据。accumulate
和prefetch
方法分别用于累积数据和预取数据。 -
定义 Riverpod Provider:
final postsProvider = Provider.autoDispose( (AutoDisposeProviderRef<OffsetIteratorAsyncValue<List<Post>>> ref) => iteratorValueProvider<List<Post>>(ref)(postsIterator()));
使用
iteratorValueProvider
创建一个 Riverpod provider,该 provider 使用OffsetIterator
来管理分页数据的状态。 -
展示数据的小部件:
class PostsListContainer extends ConsumerWidget { const PostsListContainer({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(postsProvider); return state.value.when( data: (posts) => PostsList( posts: posts, hasMore: state.hasMore, loadMore: state.pull, ), error: (err, stack) => SliverFillRemaining( child: Center(child: Text('Error: $err')), ), loading: () => const SliverFillRemaining( child: Center(child: Text('Loading...')), ), ); } }
PostsListContainer
小部件使用ConsumerWidget
监听postsProvider
的变化,并根据数据状态(数据、错误或加载)渲染相应的 UI。 -
显示帖子列表的小部件:
class PostsList extends StatelessWidget { PostsList({ Key? key, required this.posts, required this.hasMore, required this.loadMore, }) : super(key: key); final List<Post> posts; final bool hasMore; final void Function() loadMore; late final postsLength = posts.length; @override Widget build(BuildContext context) { return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final post = posts[index]; if (hasMore && index == postsLength - 1) { loadMore(); } return ListTile( title: Text(post['title']), subtitle: Text(post['body']), ); }, childCount: postsLength, ), ); } }
PostsList
小部件使用ListView.builder
构建帖子列表,并在列表末尾自动加载更多帖子。 -
主应用小部件:
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ProviderScope( child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'offset_iterator_riverpod'), ), ); } }
MyApp
是应用的入口点,使用ProviderScope
包裹整个应用,以便在整个应用中使用 Riverpod 提供器。 -
主页小部件:
class MyHomePage extends ConsumerWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => ref.refresh(postsProvider), child: const Icon(Icons.refresh), ), body: CustomScrollView( slivers: [ SliverAppBar( title: Text(title), floating: true, ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only( bottom: 5, left: 16, top: 25, ), child: Text('Posts:', style: theme.textTheme.headlineSmall!), ), ), const PostsListContainer(), const SliverToBoxAdapter(child: SizedBox(height: 30)), ], ), ); } }
更多关于Flutter状态管理插件offset_iterator_riverpod的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter状态管理插件offset_iterator_riverpod的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 offset_iterator_riverpod
插件进行状态管理的 Flutter 代码示例。offset_iterator_riverpod
是一个用于处理分页数据的 Riverpod 状态管理插件。
首先,确保你已经在 pubspec.yaml
文件中添加了必要的依赖:
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.18.0
flutter_riverpod: ^1.0.0
offset_iterator_riverpod: ^最新版本号 # 请替换为实际最新版本号
然后,运行 flutter pub get
来获取这些依赖。
接下来,我们创建一个 Flutter 应用,展示如何使用 offset_iterator_riverpod
插件进行分页数据加载。
1. 定义数据模型和 API 服务
假设我们有一个简单的 API,返回分页的用户数据:
import 'dart:convert';
class User {
final int id;
final String name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
class ApiService {
static Future<List<User>> fetchUsers(int offset, int limit) async {
// 模拟API请求,实际情况应替换为真实API调用
String jsonData = '''
[
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 3, "name": "Charlie"},
// 更多用户数据...
]
''';
List<dynamic> body = jsonDecode(jsonData);
return body.map((e) => User.fromJson(e)).toList().sublist(offset, offset + limit);
}
}
2. 创建 Riverpod 提供者
使用 offset_iterator_riverpod
创建分页数据提供者:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:offset_iterator_riverpod/offset_iterator_riverpod.dart';
import 'api_service.dart';
final userProvider = OffsetIteratorProvider<List<User>>(
(ref) async* {
int offset = 0;
int limit = 10; // 每页加载10个用户
while (true) {
List<User> users = await ApiService.fetchUsers(offset, limit);
if (users.isEmpty) break; // 如果没有更多数据,则停止加载
yield users;
offset += limit;
}
},
);
3. 使用 Riverpod 提供者在 Flutter 应用中显示数据
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:offset_iterator_riverpod/offset_iterator_riverpod.dart';
import 'providers.dart'; // 假设你把提供者放在这个文件里
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final AsyncValue<List<User>> users = useProvider(userProvider.stream);
return Scaffold(
appBar: AppBar(
title: Text('User List'),
),
body: users.when(
data: (userList) {
return ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
User user = userList[index];
return ListTile(
title: Text('${user.id}: ${user.name}'),
);
},
);
},
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stackTrace) => Center(child: Text('Error: $error')),
),
);
}
}
总结
以上代码展示了如何使用 offset_iterator_riverpod
插件进行分页数据加载和管理。我们定义了一个 API 服务来模拟数据获取,创建了一个 Riverpod 提供者来处理分页逻辑,并在 Flutter 应用中使用这个提供者来显示数据。
请根据实际情况调整 API 请求部分,并确保你使用的是 offset_iterator_riverpod
的最新版本。