Skip to content

Instantly share code, notes, and snippets.

@ellet0
Last active April 28, 2024 00:25
Show Gist options
  • Save ellet0/58d9620dd416504f0c4bdbccc93d3750 to your computer and use it in GitHub Desktop.
Save ellet0/58d9620dd416504f0c4bdbccc93d3750 to your computer and use it in GitHub Desktop.
Dart Bloc State Design Data Loss Issue

Click on this link for more details

You will have to join Bloc server on Discord

Solution 1 (by using subclasses that extends the state class):

Cubit/Bloc
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

part 'admin_user_state.dart';

class AdminUserCubit extends Cubit<AdminUserState> {
AdminUserCubit({
    required this.adminUserApi,
}) : super(const AdminUserInitial()) {
    loadUsers();
}

final AdminUserApi adminUserApi;

static const _limit = 10;

Future<void> loadUsers() async {
    try {
    emit(const AdminUserLoadUsersInProgress());
    final users = await adminUserApi.getAllUsers(
        search: '',
        page: 1,
        limit: _limit,
    );
    emit(AdminUserLoadUsersSuccess(
        usersState: AdminUserUsersState(
        users: users,
        hasReachedLastPage: users.isEmpty,
        ),
    ));
    } on AdminUserException catch (e) {
    emit(AdminUserLoadUsersFailure(e));
    }
}

Future<void> loadMoreUsers() async {
    if (state.usersState.hasReachedLastPage) {
    return;
    }
    if (state is AdminUserLoadMoreUsersInProgress) {
    // In case if the function called more than once
    return;
    }
    try {
    emit(AdminUserLoadMoreUsersInProgress(
        usersState: state.usersState.copyWith(
        page: state.usersState.page + 1,
        ),
    ));
    final moreUsers = await adminUserApi.getAllUsers(
        search: state.usersState.search,
        page: state.usersState.page,
        limit: _limit,
    );
    emit(AdminUserLoadUsersSuccess(
        usersState: state.usersState.copyWith(
        users: [
            ...state.usersState.users,
            ...moreUsers,
        ],
        hasReachedLastPage: moreUsers.isEmpty,
        ),
    ));
    } on AdminUserException catch (e) {
    emit(AdminUserLoadUsersFailure(e));
    }
}

Future<void> searchUsers({required String search}) async {
    try {
    emit(const AdminUserLoadUsersInProgress());
    final users = await adminUserApi.getAllUsers(
        search: search,
        page: 1,
        limit: _limit,
    );
    emit(AdminUserLoadUsersSuccess(
        usersState: AdminUserUsersState(
        users: users,
        search: search, // So pagination work when searching
    )));
    } on AdminUserException catch (e) {
    emit(AdminUserLoadUsersFailure(e));
    }
}

Future<void> setAccountActivated({
    required String userId,
    required bool value,
}) async {
    try {
    emit(AdminUserActionInProgress(
        userId: userId,
        usersState: state.usersState,
    ));
    await adminUserApi.setAccountActivated(
        userId: userId,
        value: value,
    );
    final users = state.usersState.users.map((user) {
        if (user.id == userId) {
        return user.copyWith(isAccountActivated: value);
        }
        return user;
    }).toList();
    emit(AdminUserActionSuccess(
        usersState: state.usersState.copyWith(
        users: users,
        ),
    ));
    } on AdminUserException catch (e) {
    emit(AdminUserActionFailure(
        e,
        usersState: state.usersState,
    ));
    }
}

Future<void> deleteUserAccount({
    required String userId,
}) async {
    try {
    emit(AdminUserActionInProgress(
        userId: userId,
        usersState: state.usersState,
    ));
    await adminUserApi.deleteUserAccount(
        userId: userId,
    );
    final users = [...state.usersState.users]..removeWhere(
        (user) => user.id == userId,
        );
    emit(AdminUserActionSuccess(
        usersState: state.usersState.copyWith(
        users: users,
    )));
    } on AdminUserException catch (e) {
    emit(AdminUserActionFailure(
        e,
        usersState: state.usersState,
    ));
    }
}

Future<void> sendNotificationToUser({
    required String userId,
    required String title,
    required String body,
}) async {
    try {
    emit(AdminUserActionInProgress(
        userId: userId,
        usersState: state.usersState,
    ));
    await adminUserApi.sendNotificationToUser(
        userId: userId,
        title: title,
        body: body,
    );
    emit(AdminUserActionSuccess(usersState: state.usersState));
    } on AdminUserException catch (e) {
    emit(AdminUserActionFailure(
        e,
        usersState: state.usersState,
    ));
    }
}
}

State
part of 'admin_user_cubit.dart';

@immutable
class AdminUserUsersState extends Equatable {
const AdminUserUsersState({
    this.users = const [],
    this.page = 1,
    this.hasReachedLastPage = false,
    this.search = '',
});

final List<User> users;
final int page;
final bool hasReachedLastPage;
final String search;

@override
List<Object?> get props => [
        users,
        page,
        hasReachedLastPage,
        search,
    ];

AdminUserUsersState copyWith({
    List<User>? users,
    int? page,
    bool? hasReachedLastPage,
    String? search,
}) {
    return AdminUserUsersState(
    users: users ?? this.users,
    page: page ?? this.page,
    hasReachedLastPage: hasReachedLastPage ?? this.hasReachedLastPage,
    search: search ?? this.search,
    );
}
}

@immutable
sealed class AdminUserState extends Equatable {
const AdminUserState({
    this.usersState = const AdminUserUsersState(),
});

final AdminUserUsersState usersState;
@override
List<Object?> get props => [
        usersState,
    ];
}

class AdminUserInitial extends AdminUserState {
const AdminUserInitial();
}

// For loading the users

class AdminUserLoadUsersInProgress extends AdminUserState {
const AdminUserLoadUsersInProgress();
}

class AdminUserLoadUsersSuccess extends AdminUserState {
const AdminUserLoadUsersSuccess({required super.usersState});
}

class AdminUserLoadUsersFailure extends AdminUserState {
const AdminUserLoadUsersFailure(this.exception);

final AdminUserException exception;

@override
List<Object?> get props => [exception, ...super.props];
}

class AdminUserLoadMoreUsersInProgress extends AdminUserState {
const AdminUserLoadMoreUsersInProgress({required super.usersState});
}

// For user actions such as delete user etc...

class AdminUserActionInProgress extends AdminUserState {
const AdminUserActionInProgress({
    required super.usersState,
    required this.userId,
});

/// The user id for that each tile
final String userId;
}

class AdminUserActionSuccess extends AdminUserState {
const AdminUserActionSuccess({required super.usersState});
}

class AdminUserActionFailure extends AdminUserState {
const AdminUserActionFailure(
    this.exception, {
    required super.usersState,
});

final AdminUserException exception;

@override
List<Object?> get props => [exception, ...super.props];
}

Solution 2 (by using sealed class status inside single state class):

Cubit/Bloc
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

part 'admin_user_state.dart';

class AdminUserCubit extends Cubit<AdminUserState> {
AdminUserCubit({
   required this.adminUserApi,
}) : super(const AdminUserState()) {
   loadUsers();
}

final AdminUserApi adminUserApi;

static const _limit = 10;

Future<void> loadUsers() async {
   try {
   emit(const AdminUserState(status: AdminUserLoadUsersInProgress()));
   final users = await adminUserApi.getAllUsers(
       search: '',
       page: 1,
       limit: _limit,
   );
   emit(AdminUserState(
       status: const AdminUserLoadUsersSuccess(),
       usersState: AdminUserUsersState(
       users: users,
       hasReachedLastPage: users.isEmpty,
       ),
   ));
   } on AdminUserException catch (e) {
   emit(AdminUserState(
       status: AdminUserLoadUsersFailure(e),
   ));
   }
}

Future<void> loadMoreUsers() async {
   if (state.usersState.hasReachedLastPage) {
   return;
   }
   if (state is AdminUserLoadMoreUsersInProgress) {
   // In case if the function called more than once
   return;
   }
   try {
   emit(state.copyWith(
       status: const AdminUserLoadMoreUsersInProgress(),
       usersState: state.usersState.copyWith(
       page: state.usersState.page + 1,
       ),
   ));
   final moreUsers = await adminUserApi.getAllUsers(
       search: state.usersState.search,
       page: state.usersState.page,
       limit: _limit,
   );
   emit(state.copyWith(
       status: const AdminUserLoadUsersSuccess(),
       usersState: state.usersState.copyWith(
       users: [
           ...state.usersState.users,
           ...moreUsers,
       ],
       hasReachedLastPage: moreUsers.isEmpty,
       ),
   ));
   } on AdminUserException catch (e) {
   emit(state.copyWith(
       status: AdminUserLoadUsersFailure(e),
   ));
   }
}

Future<void> searchUsers({required String search}) async {
   try {
   emit(state.copyWith(
       status: const AdminUserLoadUsersInProgress(),
       usersState: AdminUserUsersState(
       search: search,
       ),
   ));
   final users = await adminUserApi.getAllUsers(
       search: state.usersState.search,
       page: state.usersState.page,
       limit: _limit,
   );
   emit(state.copyWith(
       status: const AdminUserLoadUsersSuccess(),
       usersState: state.usersState.copyWith(
       users: users,
       ),
   ));
   } on AdminUserException catch (e) {
   emit(state.copyWith(
       status: AdminUserLoadUsersFailure(e),
   ));
   }
}

Future<void> setAccountActivated({
   required String userId,
   required bool value,
}) async {
   try {
   emit(state.copyWith(
       status: AdminUserActionInProgress(userId: userId),
   ));
   await adminUserApi.setAccountActivated(
       userId: userId,
       value: value,
   );
   final users = state.usersState.users.map((user) {
       if (user.id == userId) {
       return user.copyWith(isAccountActivated: value);
       }
       return user;
   }).toList();
   emit(state.copyWith(
       status: const AdminUserActionSuccess(),
       usersState: state.usersState.copyWith(
       users: users,
       ),
   ));
   } on AdminUserException catch (e) {
   emit(state.copyWith(
       status: AdminUserActionFailure(e),
   ));
   }
}

Future<void> deleteUserAccount({
   required String userId,
}) async {
   try {
   emit(state.copyWith(
       status: AdminUserActionInProgress(userId: userId),
   ));
   await adminUserApi.deleteUserAccount(
       userId: userId,
   );
   final users = [...state.usersState.users]..removeWhere(
       (user) => user.id == userId,
       );
   emit(state.copyWith(
       status: const AdminUserActionSuccess(),
       usersState: state.usersState.copyWith(
       users: users,
       ),
   ));
   } on AdminUserException catch (e) {
   emit(state.copyWith(
       status: AdminUserActionFailure(e),
   ));
   }
}

Future<void> sendNotificationToUser({
   required String userId,
   required String title,
   required String body,
}) async {
   try {
   emit(state.copyWith(
       status: AdminUserActionInProgress(userId: userId),
   ));
   await adminUserApi.sendNotificationToUser(
       userId: userId,
       title: title,
       body: body,
   );
   emit(state.copyWith(
       status: const AdminUserActionSuccess(),
   ));
   } on AdminUserException catch (e) {
   emit(state.copyWith(
       status: AdminUserActionFailure(e),
   ));
   }
}
}

State
part of 'admin_user_cubit.dart';

@immutable
class AdminUserUsersState extends Equatable {
const AdminUserUsersState({
   this.users = const [],
   this.page = 1,
   this.hasReachedLastPage = false,
   this.search = '',
});

final List<User> users;
final int page;
final bool hasReachedLastPage;
final String search;

@override
List<Object?> get props => [
       users,
       page,
       hasReachedLastPage,
       search,
   ];

AdminUserUsersState copyWith({
   List<User>? users,
   int? page,
   bool? hasReachedLastPage,
   String? search,
}) {
   return AdminUserUsersState(
   users: users ?? this.users,
   page: page ?? this.page,
   hasReachedLastPage: hasReachedLastPage ?? this.hasReachedLastPage,
   search: search ?? this.search,
   );
}
}

@immutable
class AdminUserState extends Equatable {
const AdminUserState({
   this.usersState = const AdminUserUsersState(),
   this.status = const AdminUserInitial(),
});

final AdminUserUsersState usersState;
final AdminUserStatus status;
@override
List<Object?> get props => [
       usersState,
       status,
   ];

AdminUserState copyWith({
   AdminUserUsersState? usersState,
   AdminUserStatus? status,
}) {
   return AdminUserState(
   usersState: usersState ?? this.usersState,
   status: status ?? this.status,
   );
}
}

@immutable
sealed class AdminUserStatus extends Equatable {
const AdminUserStatus();

@override
List<Object?> get props => [];
}

class AdminUserInitial extends AdminUserStatus {
const AdminUserInitial();
}

// For loading the users

class AdminUserLoadUsersInProgress extends AdminUserStatus {
const AdminUserLoadUsersInProgress();
}

class AdminUserLoadUsersSuccess extends AdminUserStatus {
const AdminUserLoadUsersSuccess();
}

class AdminUserLoadUsersFailure extends AdminUserStatus {
const AdminUserLoadUsersFailure(this.exception);

final AdminUserException exception;

@override
List<Object?> get props => [exception, ...super.props];
}

class AdminUserLoadMoreUsersInProgress extends AdminUserStatus {
const AdminUserLoadMoreUsersInProgress();
}

// For user actions such as delete user etc...

class AdminUserActionInProgress extends AdminUserStatus {
const AdminUserActionInProgress({
   required this.userId,
});

/// The user id for that tile
final String userId;

@override
List<Object?> get props => [userId, ...super.props];
}

class AdminUserActionSuccess extends AdminUserStatus {
const AdminUserActionSuccess();
}

class AdminUserActionFailure extends AdminUserStatus {
const AdminUserActionFailure(
   this.exception,
);

final AdminUserException exception;

@override
List<Object?> get props => [exception, ...super.props];
}


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment