Flutter状态管理插件offset_iterator_riverpod的使用

发布于 1周前 作者 phonegap100 来自 Flutter

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

解释

  1. 定义类型别名:

    typedef Post = Map<String, dynamic>;
    

    这里定义了一个类型别名 Post,简化了后续代码的可读性。

  2. 获取一页帖子的函数:

    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 获取帖子数据,并根据指定的页码和每页条目数进行查询。

  3. 定义 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 函数获取并处理每一页的数据。accumulateprefetch 方法分别用于累积数据和预取数据。

  4. 定义 Riverpod Provider:

    final postsProvider = Provider.autoDispose(
        (AutoDisposeProviderRef<OffsetIteratorAsyncValue<List<Post>>> ref) =>
            iteratorValueProvider<List<Post>>(ref)(postsIterator()));
    

    使用 iteratorValueProvider 创建一个 Riverpod provider,该 provider 使用 OffsetIterator 来管理分页数据的状态。

  5. 展示数据的小部件:

    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。

  6. 显示帖子列表的小部件:

    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 构建帖子列表,并在列表末尾自动加载更多帖子。

  7. 主应用小部件:

    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 提供器。

  8. 主页小部件:

    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

1 回复

更多关于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 的最新版本。

回到顶部