携手创造,共同成长!这是我参加「日新计划 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中的逻辑,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());
}
},
);
}
}
咱们根据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监听事情。
完成下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
烘托,它仅仅将事情转换为状况。