Flutter学习 - Bloc - 06 列表刷新与加载
本文主要下使用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中的逻辑,PostBloc
将 PostEvents
作为输入并输出 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。
如果我们无法检索到这些帖子,我们会抛出一个异常并且emit
PostFailure()
.
如果我们可以检索到帖子,我们会返回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());
}
},
);
}
}
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监听事件。
实现下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
渲染,它只是将事件转换为状态。
转载自:https://juejin.cn/post/7133523771457011749