携手创造,共同成长!这是我参加「日新计划 8 月更文挑战」的第20天,点击检查活动概况

本文主要下运用Bloc对列表进行加载和展现

咱们运用完成一个列表的上拉和下拉功能,终究效果如下。

关于这个演示应用程序,咱们将运用jsonplaceholder作为咱们的数据源。

浏览器中打开一个新选项卡并访问jsonplaceholder.typicode.com/posts?_star…以检查 API 回来的内容。


[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  }
]

1. 数据模型

创立 Post 对象的模型。Post仅仅一个带有id,title和的类body

import 'package:equatable/equatable.dart';
class Post extends Equatable {
  final int id;
  final String title;
  final String body;
  const Post({required this.id, required this.title, required this.body});
  @override
  List<Object> get props => [id, title, body];
}

咱们扩展Equatable以便咱们可以比较Posts。如果没有这个,咱们将需求手动更改咱们的类以掩盖持平性和 hashCode,以便咱们可以区分两个Posts对象之间的差异。

2. PostEvent

在高层次上,它将呼应用户输入(上拉)并获取更多帖子,以便表明层显示它们。让咱们从创立咱们的Event.

咱们PostBloc只会回应一个事情;PostLoad每逢需求更多帖子来呈现时,表明层将增加它。因为咱们的PostLoad事情是一种类型,PostEvent咱们可以像这样创立bloc/post_event.dart和完成事情。

part of 'post_bloc.dart';
abstract class PostEvent extends Equatable {
  const PostEvent();
  @override
  List<Object> get props => [];
}
class PostLoad extends PostEvent {}

3. PostState

  • PostInitial– 将告知表明层它需求在加载初始批次的帖子时呈现加载指示器

  • PostSuccess– 将告知表明层它有要烘托的内容

  • posts– 将是List<Post>将显示的

  • isLoadMore– 将告知表明层它是否可以加载更多

  • PostFailure– 将告知表明层在获取帖子时发生了错误

咱们现在可以bloc/post_state.dart像这样创立和完成它。

part of 'post_bloc.dart';
enum PostStatus {initial, success, failure }
class PostState extends Equatable {
  final PostStatus status;
  final List<Post> posts;
  final bool isLoadMore;
  const PostState({this.status = PostStatus.initial,this.posts = const <Post>[], this.isLoadMore = true});
  /// 咱们完成copyWith了这样咱们可以PostSuccess方便地仿制和更新零个或多个特点的实例
  PostState copyWith({
    PostStatus? status,
    List<Post>? posts,
    bool? isLoadMore,
  }) {
    return PostState(
      status: status ?? this.status,
      posts: posts ?? this.posts,
      isLoadMore: isLoadMore ?? this.isLoadMore,
    );
  }
  @override
  List<Object> get props => [status, posts, isLoadMore];
}

4. Bolc

咱们完成下bloc中的逻辑,PostBlocPostEvents 作为输入并输出 PostStates

  • 恳求

首先咱们完成下恳求,这里就运用下Dio,简略

Future<List<Post>> _loadPosts ([int page = 0 ]) async {
  var response = await Dio().get('https://jsonplaceholder.typicode.com/posts'
      ,queryParameters: {'_start':'$page', '_limit':'$_pageCount'});
  if(response.statusCode == 200) {
    final body = response as List;
    return body.map((dynamic json) {
      final map = json as Map<String, dynamic>;
      return Post(
        id: map['id'] as int,
        title: map['title'] as String,
        body: map['body'] as String,
      );
    }).toList();
  }
  throw Exception('error');
}
  • loadPost

咱们需求注册一个事情处理程序来处理传入的PostLoad事情。为了呼应PostLoad事情,咱们将调用_loadPosts从 API 获取帖子。

Future<void> _onPostLoad(PostLoad event, Emitter<PostState> emit) async {
  if(!state.isLoadMore) return;
  try {
    if(state.status == PostStatus.initial) {
      final posts = await _loadPosts();
      return emit(state.copyWith(
        status:  PostStatus.success,
        posts:  posts,
        isLoadMore: posts.length = _pageCount
      ));
    }
    final posts = await _loadPosts(state.posts.length);
    emit(posts.isEmpty ? state.copyWith(isLoadMore: false): state.copyWith(
      status: PostStatus.success,
      posts: List.of(state.posts)..addAll(posts),
    ));
  }catch (_) {
     emit(state.copyWith(status: PostStatus.failure));
  }
}

咱们PostBloc将经过事情处理程序emit中供给的新状况。Emitter<PostState>

现在每次PostEvent增加 a 时,如果它是一个PostFetched事情并且有更多帖子要获取,咱们PostBloc将获取接下来的 20 个帖子。

如果咱们测验获取超过最大帖子数 (100),API 将回来一个空数组,因而如果咱们回来一个空数组,咱们的 bloc 将emit是 currentState,除非咱们设置isLoadMore为 true。

如果咱们无法检索到这些帖子,咱们会抛出一个异常并且emitPostFailure().

如果咱们可以检索到帖子,咱们会回来PostSuccess()包含整个帖子列表的内容。

5. UI

首先仍是构建BlocProvider


class PostPage extends StatelessWidget {
  const PostPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_)=> PostBloc()..add(const PostLoadMore()),
      child: const PostsList(),
    );
  }
}

咱们运用一个三方的改写组件,供给上拉和下拉

class _PostsListState extends State<PostsList> {
  final RefreshController _refreshController =
  RefreshController(initialRefresh: false);
  @override
  void initState() {
    super.initState();
    // _scrollController.addListener(_onScroll);
  }
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PostBloc, PostState>(
      builder: (context, state) {
        switch (state.status) {
          case PostStatus.failure:
            return const Center(child: Text('failed to fetch posts'));
          case PostStatus.success:
            if (state.posts.isEmpty) {
              return const Center(child: Text('no posts'));
            }
            return  Scaffold(
              appBar: AppBar(title: const Text('Post'),),
              body: SmartRefresher(
                enablePullDown: true,
                enablePullUp: true,
                controller: _refreshController,
                onRefresh: (){
                     context.read<PostBloc>().add(PostRefresh(refreshController: _refreshController));},
                onLoading:  (){context.read<PostBloc>().add(PostLoadMore(refreshController: _refreshController));},
                child:  ListView.builder(
                  itemBuilder: (BuildContext context, int index) {
                    return PostListItem(post: state.posts[index]);
                  },
                  itemCount:state.posts.length
                )
              ),
            );
          case PostStatus.initial:
            return const Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}

咱们根据PostStatus来展现不同的页面,每逢咱们的PostBloc状况发生变化时,咱们的 builder 函数都会被调用 newPostState
这里咱们运用下pull_to_refresh: ^2.0.0 感兴趣看下之前我的介绍 改写组件-pull_to_refresh。 因而咱们修改下PostEvent

part of 'post_bloc.dart';
abstract class PostEvent extends Equatable {
 final RefreshController? refreshController;
  const PostEvent({this.refreshController});
  @override
  List<Object> get props => [];
}
class PostLoadMore extends PostEvent {
  const PostLoadMore({super.refreshController});
}
class PostRefresh extends PostEvent {
  const PostRefresh({super.refreshController});
}

之后把咱们的行为拆分为改写和加载更多,并增加on监听事情。

Flutter学习 - Bloc - 06 列表刷新与加载

完成下PostListItem

class PostListItem extends StatelessWidget {
  const PostListItem({super.key, required this.post});
  final Post post;
  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Material(
      child: ListTile(
        leading: Text('${post.id}', style: textTheme.caption),
        title: Text(post.title),
        isThreeLine: true,
        subtitle: Text(post.body),
        dense: true,
      ),
    );
  }
}

6. 小结

尽管在这个应用程序中咱们只有一个块,但在较大的应用程序中,很多块办理应用程序状况的不同部分是相当常见的。 咱们可以经过创立不同的BlocObservers,每个状况变化都被记录下来,咱们可以十分轻松地检测咱们的应用程序并在一个当地跟踪一切用户交互和状况变化! 咱们PostsPage不知道Posts它们来自哪里或怎么检索它们。相反,咱们PostBloc不知道怎么State烘托,它仅仅将事情转换为状况。