Skip to content
This repository has been archived by the owner on Jan 26, 2021. It is now read-only.

Commit

Permalink
feat: Add Pagination to Members Page (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jayesh Nirve committed Jun 11, 2020
1 parent 8da9923 commit 2b3568d
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 69 deletions.
1 change: 1 addition & 0 deletions android/settings_aar.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ':app'
20 changes: 9 additions & 11 deletions lib/remote/repositories/user_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ class UserRepository {

/// Returns home statistics for the current user
Future<HomeStats> getHomeStats() async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.getHomeStats());
final body = await ApiManager.callSafely(() => ApiManager.instance.userService.getHomeStats());
return HomeStats.fromJson(body);
}

/// Returns all users with email verified
Future<List<User>> getVerifiedUsers() async {
Future<List<User>> getVerifiedUsers(int page) async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.getVerifiedUsers());
() => ApiManager.instance.userService.getVerifiedUsers(page: page));
List<User> users = [];

for (var user in body) {
Expand All @@ -38,28 +37,27 @@ class UserRepository {

/// Returns current user profile
Future<User> getCurrentUser() async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.getCurrentUser());
final body =
await ApiManager.callSafely(() => ApiManager.instance.userService.getCurrentUser());
return User.fromJson(body);
}

/// Returns user profile with the specified id
Future<User> getUser(int userId) async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.getUser(userId));
final body = await ApiManager.callSafely(() => ApiManager.instance.userService.getUser(userId));
return User.fromJson(body);
}

/// Updates current user's profile
Future<CustomResponse> updateUser(User user) async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.updateUser(user));
final body =
await ApiManager.callSafely(() => ApiManager.instance.userService.updateUser(user));
return CustomResponse.fromJson(body);
}

Future<CustomResponse> changePassword(ChangePassword changePassword) async {
final body = await ApiManager.callSafely(
() => ApiManager.instance.userService.changePassword(changePassword));
() => ApiManager.instance.userService.changePassword(changePassword));
return CustomResponse.fromJson(body);
}
}
1 change: 1 addition & 0 deletions lib/remote/services/auth_service.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions lib/remote/services/relation_service.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions lib/remote/services/task_service.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions lib/remote/services/user_service.chopper.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion lib/remote/services/user_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ abstract class UserService extends ChopperService {

/// Returns all users, with email verified, of the system
@Get(path: "users/verified")
Future<Response<List<dynamic>>> getVerifiedUsers();
Future<Response<List<dynamic>>> getVerifiedUsers({
@Query("page") int page,
@Query("per_page") int perPage = 20,
});

/// Returns the current user profile
@Get(path: "user")
Expand Down
28 changes: 22 additions & 6 deletions lib/screens/home/pages/members/bloc/members_page_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,39 @@ import './bloc.dart';

class MembersPageBloc extends Bloc<MembersPageEvent, MembersPageState> {
final UserRepository userRepository;

int pageNumber = 1;
MembersPageBloc({@required this.userRepository}) : assert(userRepository != null);

//TODO: debounce the Events in order to prevent spamming our API
@override
MembersPageState get initialState => MembersPageInitial();

@override
Stream<MembersPageState> mapEventToState(MembersPageEvent event) async* {
if (event is MembersPageShowed) {
yield MembersPageLoading();
final currentState = state;

if (event is MembersPageShowed && !_hasReachedMax(currentState)) {
try {
final List<User> users = await userRepository.getVerifiedUsers();
yield MembersPageSuccess(users);
if (currentState is MembersPageInitial) {
yield MembersPageLoading();
final List<User> users = await userRepository.getVerifiedUsers(pageNumber);
yield MembersPageSuccess(users: users, hasReachedMax: false);
}
if (currentState is MembersPageSuccess) {
final users =
await userRepository.getVerifiedUsers((currentState.users.length ~/ 10) + 1);
yield users.isEmpty
? currentState.copyWith(hasReachedMax: true)
: MembersPageSuccess(
users: currentState.users + users,
hasReachedMax: false,
);
}
} on Failure catch (failure) {
Logger.root.severe(failure.message);
yield MembersPageFailure(failure.message);
}
}
}
}

bool _hasReachedMax(MembersPageState state) => state is MembersPageSuccess && state.hasReachedMax;
25 changes: 22 additions & 3 deletions lib/screens/home/pages/members/bloc/members_page_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,30 @@ class MembersPageLoading extends MembersPageState {}

class MembersPageSuccess extends MembersPageState {
final List<User> users;
final bool hasReachedMax;

MembersPageSuccess({
this.users,
this.hasReachedMax,
});

// We implemented copyWith so that we can copy an instance of MembersPageSuccess
// and update zero or more properties conveniently.
MembersPageSuccess copyWith({
List<User> users,
bool hasReachedMax,
}) {
return MembersPageSuccess(
users: users ?? this.users,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}

MembersPageSuccess(this.users);

@override
List<Object> get props => [users];
List<Object> get props => [users, hasReachedMax];
@override
String toString() =>
'MembersPageSuccess { users: ${users.length}, hasReachedMax: $hasReachedMax }';
}

class MembersPageFailure extends MembersPageState {
Expand Down
125 changes: 86 additions & 39 deletions lib/screens/home/pages/members/members_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,69 @@ import 'package:mentorship_client/remote/repositories/user_repository.dart';
import 'package:mentorship_client/screens/home/pages/members/bloc/bloc.dart';
import 'package:mentorship_client/screens/home/pages/members/widgets/member_list_tile.dart';
import 'package:mentorship_client/screens/member_profile/member_profile.dart';
import 'package:mentorship_client/widgets/loading_indicator.dart';

class MembersPage extends StatefulWidget {
class MembersPage extends StatelessWidget {
@override
_MembersPageState createState() => _MembersPageState();
Widget build(BuildContext context) {
return BlocProvider<MembersPageBloc>(
create: (context) =>
MembersPageBloc(userRepository: UserRepository.instance)..add(MembersPageShowed()),
child: _MembersPage());
}
}

class _MembersPageState extends State<MembersPage> {
class _MembersPage extends StatefulWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<MembersPageBloc>(
create: (context) =>
MembersPageBloc(userRepository: UserRepository.instance)..add(MembersPageShowed()),
child: BlocBuilder<MembersPageBloc, MembersPageState>(
builder: (context, state) {
if (state is MembersPageSuccess) {
return ListView.builder(
itemCount: state.users.length,
itemBuilder: (context, index) {
User user = state.users[index];
_MembersPageState createState() => _MembersPageState();
}

return InkWell(
onTap: () => _openMemberProfileScreen(context, user),
child: MemberListTile(user: user),
);
},
);
}
class _MembersPageState extends State<_MembersPage> {
final _scrollController = ScrollController();
// ignore: dart.core.Sink
MembersPageBloc _membersPageBloc;
final _scrollThreshold = 10.0;

if (state is MembersPageFailure) {
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
_membersPageBloc = BlocProvider.of<MembersPageBloc>(context);
}

@override
Widget build(BuildContext context) {
return BlocBuilder<MembersPageBloc, MembersPageState>(
builder: (context, state) {
if (state is MembersPageFailure) {
return Center(
child: Text('failed to get users'),
);
}
if (state is MembersPageSuccess) {
if (state.users.isEmpty) {
return Center(
child: Column(
children: [
Text(state.message),
RaisedButton(
child: Text("Retry"),
onPressed: () =>
BlocProvider.of<MembersPageBloc>(context).add(MembersPageShowed()),
)
],
),
child: Text('no users'),
);
}
if (state is MembersPageLoading) {
return LoadingIndicator();
} else
return Text("an error occurred");
},
),
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
bool _reachedEnd = index > state.users.length - 1;
User user = (!_reachedEnd) ? state.users[index] : null;
return _reachedEnd
? BottomLoader()
: InkWell(
onTap: () => _openMemberProfileScreen(context, user),
child: MemberListTile(user: user),
);
},
itemCount: state.hasReachedMax ? (state.users.length) : (state.users.length + 1),
controller: _scrollController,
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}

Expand All @@ -64,4 +78,37 @@ class _MembersPageState extends State<MembersPage> {
),
);
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

void _onScroll() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll <= _scrollThreshold) {
_membersPageBloc.add(MembersPageShowed());
}
}
}

class BottomLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(bottom: 8.0),
alignment: Alignment.center,
child: Center(
child: SizedBox(
width: 33,
height: 33,
child: CircularProgressIndicator(
strokeWidth: 1.5,
),
),
),
);
}
}

0 comments on commit 2b3568d

Please sign in to comment.