Flutter响应式状态管理插件rx_bloc_list的使用

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

Flutter响应式状态管理插件rx_bloc_list的使用

rx_bloc_list简介

CI codecov style license

rx_bloc_list包简化了在Flutter应用中实现无限滚动下拉刷新功能。它旨在与RxBloc生态系统一起使用。

使用方法

添加依赖

pubspec.yaml文件中添加依赖:

dependencies:
  rx_bloc_list: any

导入包

确保在项目中导入该包:

import 'package:rx_bloc_list/rx_bloc_list.dart';

示例代码

以下是一个完整的示例,演示如何使用rx_bloc_list来创建一个带有无限滚动和下拉刷新功能的列表页面:

主入口

import 'package:flutter/material.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';
import 'package:rxdart/rxdart.dart';

void main() {
  runApp(const MyApp());
}

/// App entry
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'RxBlocList Example',
      home: RxBlocProvider<UserBlocType>(
        create: (context) => UserBloc(repository: UserRepository()),
        child: const PaginatedListPage(),
      ),
    );
  }
}

分页列表页面

/// region Paginated List page

class PaginatedListPage extends StatelessWidget {
  const PaginatedListPage({super.key});

  @override
  Widget build(BuildContext context) => Scaffold(
        body: SafeArea(
          child: RxPaginatedBuilder<UserBlocType, User>.withRefreshIndicator(
            state: (bloc) => bloc.states.paginatedList,
            onBottomScrolled: (bloc) => bloc.events.loadPage(),
            onRefresh: (bloc) async {
              bloc.events.loadPage(reset: true);
              return bloc.states.refreshDone;
            },
            buildSuccess: (context, list, bloc) => ListView.builder(
              itemBuilder: (context, index) {
                final user = list.getItem(index);

                if (user == null) {
                  return const YourProgressIndicator();
                }

                return YourListTile(user: user);
              },
              itemCount: list.itemCount,
            ),
            buildLoading: (context, list, bloc) =>
                const YourProgressIndicator(),
            buildError: (context, list, bloc) =>
                YourErrorWidget(error: list.error!),
          ),
        ),
      );
}

/// App specific list tile
class YourListTile extends StatelessWidget {
  /// Default constructor
  const YourListTile({
    required this.user,
    super.key,
  });

  /// The model
  final User user;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        leading: CircleAvatar(
          child: Text(user.id.toString()),
        ),
        title: Text(user.name),
      ),
    );
  }
}

/// App specific error widget
class YourErrorWidget extends StatelessWidget {
  /// Default constructor
  const YourErrorWidget({
    required this.error,
    super.key,
  });

  /// The error presented in the widget
  final Exception error;

  @override
  Widget build(BuildContext context) => Text(error.toString());
}

/// App specific progress indicator
class YourProgressIndicator extends StatelessWidget {
  /// Default constructor
  const YourProgressIndicator({
    super.key,
  });

  @override
  Widget build(BuildContext context) => const Center(
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 12),
          child: CircularProgressIndicator(),
        ),
      );
}

/// endregion

用户BLoC

/// region User Bloc

/// A contract class containing all events of the UserBloC.
abstract class UserBlocEvents {
  /// Load the next page of data. If reset is true, refresh the data and load
  /// the very first page
  void loadPage({bool reset = false});
}

/// A contract class containing all states of the UserBloC.
abstract class UserBlocStates {
  /// The loading state
  Stream<bool> get isLoading;

  /// The error state
  Stream<String> get errors;

  /// The paginated list data
  Stream<PaginatedList<User>> get paginatedList;

  /// Returns when the data refreshing has completed
  @RxBlocIgnoreState()
  Future<void> get refreshDone;
}

/// User Bloc
@RxBloc()
class UserBloc extends $UserBloc {
  /// UserBloc default constructor
  UserBloc({required UserRepository repository}) {
    _$loadPageEvent
        // Start the data fetching immediately when the page loads
        .startWith(true)
        .fetchData(repository, _paginatedList)
        // Enable state handling by the current bloc
        .setResultStateHandler(this)
        // Merge the data in the _paginatedList
        .mergeWithPaginatedList(_paginatedList)
        .bind(_paginatedList)
        // Make sure we dispose the subscription
        .addTo(_compositeSubscription);
  }

  /// Internal paginated list stream
  final _paginatedList = BehaviorSubject<PaginatedList<User>>.seeded(
    PaginatedList<User>(
      list: [],
      pageSize: 50,
    ),
  );

  @override
  Future<void> get refreshDone async => _paginatedList.waitToLoad();

  @override
  Stream<PaginatedList<User>> _mapToPaginatedListState() => _paginatedList;

  @override
  Stream<String> _mapToErrorsState() =>
      errorState.map((error) => error.toString());

  @override
  Stream<bool> _mapToIsLoadingState() => loadingState;

  /// Disposes of all streams to prevent memory leaks
  @override
  void dispose() {
    _paginatedList.close();
    super.dispose();
  }
}

/// Utility extensions for the Stream<bool> streams used within User Bloc
extension UserBlocStreamExtensions on Stream<bool> {
  /// Fetches appropriate data from the repository
  Stream<Result<PaginatedList<User>>> fetchData(
    UserRepository repository,
    BehaviorSubject<PaginatedList<User>> paginatedList,
  ) =>
      switchMap(
        (reset) {
          if (reset) paginatedList.value.reset();
          return repository
              .fetchPage(
                paginatedList.value.pageNumber + 1,
                paginatedList.value.pageSize,
              )
              .asResultStream();
        },
      );
}

/// endregion

模型和仓库

/// region Models and repositories

class User {
  /// User constructor
  User({
    required this.id,
    this.name = '',
  });

  /// The id of the user
  final int id;

  /// The name of the user
  final String name;
}

/// User repository which represents a mock repository which simulates retrieval
/// of mock data, adding a custom delay.
class UserRepository {
  /// Fetches a specific page from the repository with the given size
  Future<PaginatedList<User>> fetchPage(int page, int pageSize) async {
    await Future.delayed(const Duration(seconds: 2));

    if (page > 10) {
      return PaginatedList(
        list: [],
        pageSize: pageSize,
      );
    }

    return PaginatedList(
      list: List.generate(
        pageSize,
        (index) {
          final realIndex = ((page - 1) * pageSize) + index;
          return User(
            id: realIndex,
            name: 'User #$realIndex',
          );
        },
      ),
      pageSize: pageSize,
    );
  }
}

/// endregion

生成的代码

/// region Generated code

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// Generator: RxBlocGeneratorForAnnotation
// **************************************************************************

/// Used as a contractor for the bloc, events and states classes
/// @nodoc
abstract class UserBlocType extends RxBlocTypeBase {
  /// Events of the bloc
  UserBlocEvents get events;

  /// States of the bloc
  UserBlocStates get states;
}

/// [$UserBloc] extended by the [UserBloc]
/// @nodoc
abstract class $UserBloc extends RxBlocBase
    implements UserBlocEvents, UserBlocStates, UserBlocType {
  final _compositeSubscription = CompositeSubscription();

  /// Тhe [Subject] where events sink to by calling [loadPage]
  final _$loadPageEvent = PublishSubject<bool>();

  /// The state of [isLoading] implemented in [_mapToIsLoadingState]
  late final Stream<bool> _isLoadingState = _mapToIsLoadingState();

  /// The state of [errors] implemented in [_mapToErrorsState]
  late final Stream<String> _errorsState = _mapToErrorsState();

  /// The state of [paginatedList] implemented in [_mapToPaginatedListState]
  late final Stream<PaginatedList<User>> _paginatedListState =
      _mapToPaginatedListState();

  @override
  void loadPage({bool reset = false}) => _$loadPageEvent.add(reset);

  @override
  Stream<bool> get isLoading => _isLoadingState;

  @override
  Stream<String> get errors => _errorsState;

  @override
  Stream<PaginatedList<User>> get paginatedList => _paginatedListState;

  Stream<bool> _mapToIsLoadingState();

  Stream<String> _mapToErrorsState();

  Stream<PaginatedList<User>> _mapToPaginatedListState();

  @override
  UserBlocEvents get events => this;

  @override
  UserBlocStates get states => this;

  @override
  void dispose() {
    _$loadPageEvent.close();
    _compositeSubscription.dispose();
    super.dispose();
  }
}

/// endregion

通过以上代码,你可以创建一个包含无限滚动和下拉刷新功能的分页列表页面。希望这个示例能帮助你更好地理解和使用rx_bloc_list插件。如果你有任何问题或需要进一步的帮助,请随时提问!


更多关于Flutter响应式状态管理插件rx_bloc_list的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter响应式状态管理插件rx_bloc_list的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 rx_bloc_list 插件进行 Flutter 响应式状态管理的代码示例。rx_bloc_list 结合了 flutter_blocget_it,并利用 rxdart 库提供的响应式编程功能来管理列表状态。

首先,确保你的 pubspec.yaml 文件中包含以下依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0
  get_it: ^7.2.0
  rxdart: ^0.27.2
  rx_bloc_list: ^0.2.0  # 请注意版本号,根据实际情况调整

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

1. 设置 GetIt 容器

在你的项目根目录创建一个 dependency_injection.dart 文件来设置 GetIt 容器:

import 'package:get_it/get_it.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';

final GetIt sl = GetIt.instance;

void setupLocator() {
  sl.registerLazySingleton<MyBloc>(() => MyBloc());
}

在你的 main.dart 文件中初始化 GetIt 容器:

import 'package:flutter/material.dart';
import 'dependency_injection.dart';

void main() {
  setupLocator();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

2. 创建 Model 和 State

创建一个简单的 Model 类,例如 Item

class Item {
  final String id;
  final String name;

  Item({required this.id, required this.name});
}

3. 创建 Bloc 和 Event/State

创建一个 MyEvent 类来定义事件:

import 'package:equatable/equatable.dart';

abstract class MyEvent extends Equatable {
  const MyEvent();
}

class FetchItemsEvent extends MyEvent {
  @override
  List<Object?> get props => [];
}

创建 MyState 类来定义状态:

import 'package:equatable/equatable.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';
import 'item.dart';

abstract class MyState extends Equatable {
  const MyState();
}

class MyInitialState extends MyState {
  @override
  List<Object?> get props => [];
}

class MyLoadedState extends MyState with RxListState<Item> {
  MyLoadedState({required List<Item> items}) : super(items: items);

  @override
  List<Object?> get props => [items];
}

4. 创建 Bloc

创建 MyBloc 类来处理事件并生成状态:

import 'package:bloc/bloc.dart';
import 'package:rxdart/rxdart.dart';
import 'my_event.dart';
import 'my_state.dart';
import 'item.dart';

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyInitialState()) {
    on<FetchItemsEvent>((event, emit) async {
      // 模拟从服务器获取数据
      final items = List.generate(10, (index) => Item(id: index.toString(), name: 'Item $index'));
      emit(MyLoadedState(items: items));
    });
  }
}

5. 使用 RxBlocListBuilder 构建 UI

在你的 MyHomePage 中使用 RxBlocListBuilder 来构建响应式列表:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';
import 'my_bloc.dart';
import 'item.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Rx Bloc List Example'),
      ),
      body: BlocProvider(
        create: (context) => sl<MyBloc>(),
        child: BlocListener<MyBloc, MyState>(
          listener: (context, state) {},
          child: RxBlocListBuilder<MyBloc, MyState, Item>(
            bloc: context.read<MyBloc>(),
            itemBuilder: (context, item, index) {
              return ListTile(
                title: Text(item.name),
              );
            },
            emptyWidget: Center(child: Text('No items found')),
            loadingWidget: Center(child: CircularProgressIndicator()),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<MyBloc>().add(FetchItemsEvent());
        },
        tooltip: 'Fetch Items',
        child: Icon(Icons.add),
      ),
    );
  }
}

总结

以上代码展示了如何使用 rx_bloc_list 插件来管理 Flutter 应用中的响应式列表状态。通过定义事件、状态和 Bloc 类,并使用 RxBlocListBuilder 来构建 UI,你可以实现一个简洁且响应式的列表管理解决方案。

回到顶部