Flutter无限滚动数据加载插件riverpod_infinite_scroll的使用

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

Flutter无限滚动数据加载插件riverpod_infinite_scroll的使用

概述

riverpod_infinite_scroll 是一个专门为 Riverpod 设计的插件,用于实现无限滚动的数据加载。它基于 infinite_scroll_pagination 插件,提供了一个名为 RiverPagedBuilder 的小部件来构建无限滚动列表。

安装和导入

首先,您需要在项目中添加依赖项:

flutter pub add riverpod_infinite_scroll
flutter pub add infinite_scroll_pagination

然后在 Dart 文件中导入所需的包:

import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:riverpod_infinite_scroll/riverpod_infinite_scroll.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

如何工作

RiverPagedBuilder 是这个插件的核心部件,它期望一个 Riverpod 的 StateNotifierProvider,该 Provider 必须实现两个方法:loadnextPageKeyBuilder

示例 - 简单版本

假设我们有一个返回 Post 对象列表的 API,并且我们需要显示这些 Post 的 feed。

Post 模型

class Post {
  final int id;
  final String title;
  final String image;

  const Post({required this.id, required this.title, required this.image});
}

StateNotifier 实现

class EasyExampleNotifier extends PagedNotifier<int, Post> {
  EasyExampleNotifier()
      : super(
          load: (page, limit) => Future.delayed(const Duration(seconds: 2), () {
            return [
              const Post(id: 1, title: "My first work", image: "https://www.mywebsite.com/image1"),
              const Post(id: 2, title: "My second work", image: "https://www.mywebsite.com/image2"),
              const Post(id: 3, title: "My third work", image: "https://www.mywebsite.com/image3"),
            ];
          }),
          nextPageKeyBuilder: NextPageKeyBuilderDefault.mysqlPagination,
        );

  void add(Post post) {
    state = state.copyWith(records: [...(state.records ?? []), post]);
  }

  void delete(Post post) {
    state = state.copyWith(records: [...(state.records ?? [])]..remove(post));
  }
}

final easyExampleProvider = StateNotifierProvider<EasyExampleNotifier, PagedState<int, Post>>(
  (_) => EasyExampleNotifier(),
);

使用 RiverPagedBuilder

class EasyExample extends StatelessWidget {
  const EasyExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: RiverPagedBuilder<int, Post>(
        firstPageKey: 0,
        provider: easyExampleProvider,
        itemBuilder: (context, item, index) => ListTile(
          leading: Image.network(item.image),
          title: Text(item.title),
        ),
        pagedBuilder: (controller, builder) =>
            PagedListView(pagingController: controller, builderDelegate: builder),
      ),
    );
  }
}

更复杂的示例

如果需要处理更复杂的状态,可以创建自定义的 StateNotifier 并使用 PagedNotifierMixin

User 模型

class User {
  final String id;
  final String name;
  final String profilePicture;

  const User({required this.id, required this.name, required this.profilePicture});
}

自定义 StateNotifier

class CustomExampleState extends PagedState<String, User> {
  final bool filterByCity;

  const CustomExampleState({
    this.filterByCity = false,
    List<User>? records,
    String? error,
    String? nextPageKey,
  }) : super(records: records, error: error, nextPageKey: nextPageKey);

  @override
  CustomExampleState copyWith({
    bool? filterByCity,
    List<User>? records,
    dynamic error,
    dynamic nextPageKey,
  }) {
    final sup = super.copyWith(records: records, error: error, nextPageKey: nextPageKey);
    return CustomExampleState(
      filterByCity: filterByCity ?? this.filterByCity,
      records: sup.records,
      error: sup.error,
      nextPageKey: sup.nextPageKey,
    );
  }
}

class CustomExampleNotifier extends StateNotifier<CustomExampleState>
    with PagedNotifierMixin<String, User, CustomExampleState> {
  CustomExampleNotifier() : super(const CustomExampleState());

  @override
  Future<List<User>?> load(String page, int limit) async {
    try {
      if (state.previousPageKeys.contains(page)) {
        await Future.delayed(const Duration(seconds: 0), () {
          state = state.copyWith();
        });
        return state.records;
      }
      var users = await Future.delayed(const Duration(seconds: 3), () {
        return [
          const User(
            id: "abcdef",
            name: "John",
            profilePicture: "https://www.mywebsite.com/images/1",
          ),
          const User(
            id: "asdfgh",
            name: "Mary",
            profilePicture: "https://www.mywebsite.com/images/2",
          ),
          const User(
            id: "qwerty",
            name: "Robert",
            profilePicture: "https://www.mywebsite.com/images/3",
          ),
        ];
      });

      state = state.copyWith(
        records: [...(state.records ?? []), ...users],
        nextPageKey: users.length < limit ? null : users[users.length - 1].id,
        previousPageKeys: {...state.previousPageKeys, page}.toList(),
      );
    } catch (e) {
      state = state.copyWith(error: e.toString());
    }
  }

  void add(User user) {
    state = state.copyWith(records: [...(state.records ?? []), user]);
  }

  void delete(User user) {
    state = state.copyWith(records: [...(state.records ?? [])]..remove(user));
  }
}

final customExampleProvider = StateNotifierProvider<CustomExampleNotifier, CustomExampleState>(
  (_) => CustomExampleNotifier(),
);

使用 RiverPagedBuilder 显示用户列表

class CustomExample extends StatelessWidget {
  const CustomExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: RiverPagedBuilder<String, User>(
        firstPageKey: 'FirstPage',
        provider: customExampleProvider,
        itemBuilder: (context, item, index) => ListTile(
          leading: Image.network(item.profilePicture),
          title: Text(item.name),
        ),
        pagedBuilder: (controller, builder) => PagedGridView(
          pagingController: controller,
          builderDelegate: builder,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
        ),
      ),
    );
  }
}

主应用程序入口

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: 'Flutter Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(builder: (context) => const EasyExample()));
            },
            child: const Text('Easy example'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(builder: (context) => const CustomExample()));
            },
            child: const Text('Custom example'),
          ),
        ],
      ),
    );
  }
}

通过上述示例,您可以轻松地在 Flutter 应用中实现无限滚动列表并使用 Riverpod 进行状态管理。希望这对您有所帮助!


更多关于Flutter无限滚动数据加载插件riverpod_infinite_scroll的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter无限滚动数据加载插件riverpod_infinite_scroll的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用 riverpod_infinite_scroll 插件在 Flutter 中实现无限滚动数据加载的示例代码。riverpod_infinite_scroll 是一个结合 Riverpod 状态管理和无限滚动功能的插件。

首先,确保你的 pubspec.yaml 文件中已经添加了必要的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^1.0.0  # 确保使用最新版本的 Riverpod
  riverpod_infinite_scroll: ^0.1.0  # 确保使用最新版本的 riverpod_infinite_scroll

然后,运行 flutter pub get 来获取这些依赖。

接下来,我们编写一个示例应用,展示如何使用 riverpod_infinite_scroll 实现无限滚动。

主文件 main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_infinite_scroll/riverpod_infinite_scroll.dart';

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Infinite Scroll Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: InfiniteScrollPage(),
    );
  }
}

class InfiniteScrollPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final controller = ref.watch(infiniteScrollControllerProvider);
    final items = ref.watch(itemsProvider(controller.page));

    return Scaffold(
      appBar: AppBar(
        title: Text('Infinite Scroll Demo'),
      ),
      body: RefreshIndicator(
        onRefresh: () async {
          controller.reset();
          await controller.loadMore();
        },
        child: InfiniteScrollListView.builder(
          controller: controller,
          itemCount: items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('Item ${items[index]}'),
            );
          },
          onLoading: () => Center(child: CircularProgressIndicator()),
          onError: (error, stackTrace) => Center(child: Text('Error: $error')),
        ),
      ),
    );
  }
}

final itemsProvider = Provider.autoDispose<List<int>>((ref, [int? page]) {
  page ??= 1;
  return ref.watch(paginatedItemsProvider(page));
});

final paginatedItemsProvider = Provider.autoDispose<Future<List<int>>>((ref, int page) async {
  // Simulate network fetch with a delay
  await Future.delayed(Duration(seconds: 1));
  final start = (page - 1) * 20;
  return List.generate(20, (index) => start + index + 1);
});

final infiniteScrollControllerProvider = Provider<InfiniteScrollController>((ref) {
  return InfiniteScrollController(
    pageCallback: (page) async {
      final items = await ref.read(paginatedItemsProvider(page + 1).future);
      ref.read(itemsProvider.notifier(page + 1)).state = items;
    },
    errorWidget: (error, stackTrace) => Text('Error: $error'),
  );
});

解释

  1. 依赖注入

    • 使用 ProviderScope 包裹根应用,确保 Riverpod 的依赖注入正常工作。
  2. 状态管理

    • itemsProvider:根据当前页码提供当前页的数据列表。
    • paginatedItemsProvider:模拟分页数据获取,返回一个 Future,代表网络请求或数据库查询。
    • infiniteScrollControllerProvider:创建 InfiniteScrollController 实例,并定义加载更多数据的逻辑。
  3. UI 构建

    • InfiniteScrollPage:使用 ConsumerWidget 监听 Riverpod 状态。
    • InfiniteScrollListView.builder:构建无限滚动列表视图,并处理加载更多、加载错误等状态。
  4. 刷新功能

    • 使用 RefreshIndicator 实现下拉刷新功能,当触发刷新时重置控制器并重新加载数据。

这个示例展示了如何使用 riverpod_infinite_scroll 插件在 Flutter 应用中实现无限滚动数据加载。你可以根据自己的需求调整数据获取逻辑和 UI 样式。

回到顶部