Pagination (Second Practical) with Cubit in Flutter
import "package:flutter/material.dart";
import "package:flutter_bloc/flutter_bloc.dart";
import "package:pagination_practice/Pagination_with_Cubit/cubit/posts_cubit.dart";
import "package:pagination_practice/Pagination_with_Cubit/data/repositories/posts_repository.dart";
import "package:pagination_practice/Pagination_with_Cubit/data/services/posts_service.dart";
import "package:pagination_practice/Pagination_with_Cubit/presentation/posts_screen.dart";
void main() {
runApp(
MyApp(
repository: PostsRepository(PostsService()),
),
);
}
class MyApp extends StatelessWidget {
final PostsRepository repository;
const MyApp({super.key, required this.repository});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => PostsCubit(repository),
child: MaterialApp(
title: "Pagination",
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Colors.grey,
centerTitle: true,
),
),
home: PostView(),
),
);
}
}
import "dart:async";
import "package:flutter/material.dart";
import "package:flutter_bloc/flutter_bloc.dart";
import "package:pagination_practice/Pagination_with_Cubit/cubit/posts_cubit.dart";
import "package:pagination_practice/Pagination_with_Cubit/data/models/post.dart";
class PostView extends StatelessWidget {
PostView({super.key});
final scrollController = ScrollController();
void setupScrollController(context) {
scrollController.addListener(
() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels != 0) {
BlocProvider.of<PostsCubit>(context).loadPosts();
}
}
},
);
}
@override
Widget build(BuildContext context) {
setupScrollController(context);
BlocProvider.of<PostsCubit>(context).loadPosts();
return Scaffold(
appBar: AppBar(
title: const Text(
"Posts",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
body: _postList(),
);
}
Widget _postList() {
return BlocBuilder<PostsCubit, PostsState>(
builder: (context, state) {
if (state is PostsLoading && state.isFirstFetch) {
return _loadingIndicator();
}
List<Post> posts = [];
bool isLoading = false;
if (state is PostsLoading) {
posts = state.oldPosts;
isLoading = true;
} else if (state is PostsLoaded) {
posts = state.posts;
}
return ListView.separated(
controller: scrollController,
itemBuilder: (context, index) {
if (index < posts.length) {
return _post(posts[index], context);
} else {
Timer(
const Duration(milliseconds: 30),
() {
scrollController
.jumpTo(scrollController.position.maxScrollExtent);
},
);
return _loadingIndicator();
}
},
separatorBuilder: (context, index) {
return Divider(
color: Colors.grey[400],
);
},
itemCount: posts.length + (isLoading ? 1 : 0),
);
},
);
}
Widget _loadingIndicator() {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
Widget _post(Post post, BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${post.id}. ${post.title}",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 10.0),
Text(post.body),
],
),
);
}
}
part of 'posts_cubit.dart';
@immutable
sealed class PostsState {}
final class PostsInitial extends PostsState {}
class PostsLoaded extends PostsState {
final List<Post> posts;
PostsLoaded(this.posts);
}
class PostsLoading extends PostsState {
final List<Post> oldPosts;
final bool isFirstFetch;
PostsLoading(this.oldPosts, {required this.isFirstFetch});
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart';
import 'package:pagination_practice/Pagination_with_Cubit/data/models/post.dart';
import 'package:pagination_practice/Pagination_with_Cubit/data/repositories/posts_repository.dart';
part 'posts_state.dart';
class PostsCubit extends Cubit<PostsState> {
PostsCubit(this.repository) : super(PostsInitial());
int page = 1;
final PostsRepository repository;
void loadPosts() {
if (state is PostsLoading) return;
final currentState = state;
var oldPosts = <Post>[];
if (currentState is PostsLoaded) {
oldPosts = currentState.posts;
}
emit(PostsLoading(oldPosts, isFirstFetch: page == 1));
repository.fetchPosts(page).then((newPosts) {
page++;
final posts = (state as PostsLoading).oldPosts;
posts.addAll(newPosts);
emit(PostsLoaded(posts));
});
}
}
class Post {
final String title;
final String body;
final int id;
Post.fromJson(Map json)
: title = json["title"],
body = json["body"],
id = json["id"];
}
import 'package:pagination_practice/Pagination_with_Cubit/data/models/post.dart';
import 'package:pagination_practice/Pagination_with_Cubit/data/services/posts_service.dart';
class PostsRepository {
final PostsService service;
PostsRepository(this.service);
Future<List<Post>> fetchPosts(int page) async {
final posts = await service.fetchPosts(page);
return posts.map(
(e) {
return Post.fromJson(e);
},
).toList();
}
}
import 'dart:convert';
import 'package:http/http.dart';
class PostsService {
static const FETCH_LIMIT = 15;
final baseUrl = "https://jsonplaceholder.typicode.com/posts";
Future<List<dynamic>> fetchPosts(int page) async {
try {
final response =
await get(Uri.parse(baseUrl + "?_limit=$FETCH_LIMIT&_page=$page"));
return jsonDecode(response.body) as List<dynamic>;
} catch (error) {
return [];
}
}
}
Comments
Post a Comment