Hi I'm new to flutter and dart. I'm following a lesson on internet which is practicing to use bloc to control states. First lesson is after showing appStart animation, turn to a login page.
the lesson was using 'mapEventToState':
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository? _userRepository;
AuthenticationBloc({UserRepository? userRepository})
: assert(userRepository != null),
_userRepository = userRepository, super(Uninitialized());
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
yield* _mapAppStartedToState();
} else if (event is LoggedIn) {
yield* _mapLoggedInToState();
} else if (event is LoggedOut) {
yield* _mapLoggedOutToState();
}
Stream<AuthenticationState> _mapAppStartedToState() async* {
log('_mapAppStartedToState is running.');
try {
final bool? isSigned = await _userRepository?.isSignedIn();
if (isSigned != null) {
if (isSigned) {
final String? name = await _userRepository?.getUser();
yield Authenticated(name);
}
else {
yield Unauthenticated();
}
}
} catch (_) {
yield Unauthenticated();
}
}
Stream<AuthenticationState> _mapLoggedInToState() async* {
log('_mapLoggedInToState is running.');
yield Authenticated(await _userRepository?.getUser());
}
Stream<AuthenticationState> _mapLoggedOutToState() async* {
log('_mapLoggedOutToState is running.');
yield Unauthenticated();
_userRepository?.signOut();
}
}
turns out 'mapEventToState' was removed.
According to this page(https://github.com/felangel/bloc/issues/2526), I try to use on< event > instead:
#override
AuthenticationBloc({UserRepository? userRepository})
: assert(userRepository != null, 'userRepository == null'),
_userRepository = userRepository,
super(Uninitialized()) {
log('AuthenticationBloc is running.');
on<AppStarted>(_appStarted);
on<LoggedIn>(_loggedIn);
on<LoggedOut>(_loggedOut);
}
Stream<AuthenticationState> _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) async* {
log('_appStarted is running.');
yield* _mapAppStartedToState();
}
But it didn't work. Even log('_appStarted is running.'); didn't show at console.
I tried to change type and aync*. It would show console log if _appStarted isn't aync.
void _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) {
log('_appStarted is running.');
// yield* _mapAppStartedToState();
}
However, it can't yield to stream as _appStarted isn't aync. Makes me confused.
Please let me know if I got some misunderstand about bloc and stream. Happy to see any solution or advise.
You no longer need your one function per event, because you already have it:
void _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) {
log('_appStarted is running.');
try {
final bool? isSigned = await _userRepository?.isSignedIn();
if (isSigned != null) {
if (isSigned) {
final String? name = await _userRepository?.getUser();
emit(Authenticated(name));
}
else {
emit(Unauthenticated());
}
}
} catch (_) {
emit(Unauthenticated());
}
}
If you want to delegate this to another function, just remove the stream return value and pass the emitter.
Related
I have a problem, my database has data, but I can't list this data in the application, can you see where the problem is?
Here the database query is being implemented
#override
Stream<Either<TodoFailures, List<Todo>>> watchAll() async* {
//yield left(const InsufficientPermissions());
// users/{user ID}/notes/{todo ID}
final userDoc = await firestore.userDocument();
yield* userDoc.todoCollection
.snapshots()
.map((snapshot) => right<TodoFailures, List<Todo>>(snapshot.docs
.map((doc) => TodoModel.fromFirestore(doc).toDomain()).toList()))
.handleError((e) {
if (e is FirebaseException) {
if (e.code.contains('permission-denied') || e.code.contains("PERMISSION_DENIED")) {
return left(InsufficientPermisssons());
} else {
return left(UnexpectedFailure());
}
} else {
// ? check for the unauthenticated error
// ! log.e(e.toString()); // we can log unexpected exceptions
return left(UnexpectedFailure());
}
});
}
Below is where I capture the integrated query through the BloC
#injectable
class ObserverBloc extends Bloc<ObserverEvent, ObserverState> {
final TodoRepository todoRepository;
StreamSubscription<Either<TodoFailures, List<Todo>>>? todoStreamSubscription;
ObserverBloc({required this.todoRepository}) : super(ObserverInitial()) {
on<ObserverEvent>((event, emit) async {
emit(ObserverLoading());
await todoStreamSubscription?.cancel();
todoStreamSubscription = todoRepository
.watchAll()
.listen((failureOrTodos) => add(TodosUpdatedEvent(failureOrTodos: failureOrTodos)));
});
on<TodosUpdatedEvent>((event, emit) {
event.failureOrTodos.fold((failures) => emit(ObserverFailure(todoFailure: failures)),
(todos) => emit(ObserverSuccess(todos: todos)));
});
}
#override
Future<void> close() async {
await todoStreamSubscription?.cancel();
return super.close();
}
}
Even containing data in the database it comes empty, I need help to know where the problem is.
I am using a MultiBlocProvider which is working for all Bloc before I migrate it to v8.0.1. Now, only the first Bloc (SignInBloc) is working.
This is on my main.dart
return MultiBlocProvider(
providers: [
BlocProvider<SignInBloc>(
create: (context) => SignInBloc(
authenticationRepository: authenticationRepository,
userDataRepository: userDataRepository,
),
),
BlocProvider<SignUpBloc>(
create: (context) => SignUpBloc(
authenticationRepository: authenticationRepository,
userDataRepository: userDataRepository,
),
),
Edit: here is my SignInBloc
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<CheckIfSignedInEvent>(mapCheckIfSignedInEventToState);
}
Future<void> mapCheckIfSignedInEventToState(
CheckIfSignedInEvent event,
Emitter<SignInState> emit,
) async {
try {
bool isSignedIn = await authenticationRepository.checkIfSignedIn();
if (isSignedIn) {
emit(CheckIfSignedInEventCompletedState(true));
} else {
emit(CheckIfSignedInEventCompletedState(false));
}
} catch (e) {
print(e);
emit(CheckIfSignedInEventFailedState());
}
}
I am not sure what to show but here is my SignUpBloc which is similar to my SignInBloc
SignUpBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignUpInitialState()) {
on<SignUpWithGoogle>(mapSignUpWithGoogleEventToState);
}
Stream<SignUpState> mapSignUpWithGoogleEventToState(
SignUpWithGoogle event,
Emitter<SignUpState> emit,
) async* {
emit(SignUpInProgressState());
try {
User? checkUser = await authenticationRepository.checkIfUserExists();
if (checkUser != null) {
emit(SignUpWithGoogleInitialExistState());
} else {
bool checkDup =
await authenticationRepository.checkIfUserDup(event.name);
if (checkDup == true) {
emit(SignUpWithNameExistState());
} else {
User firebaseUser = await authenticationRepository.signUpWithGoogle();
emit(SignUpWithGoogleInitialCompletedState(firebaseUser));
}
}
} catch (e) {
print(e);
emit(SignUpWithGoogleInitialFailedState());
}
}
My main.dart will call the splash screen which has the declaration of the bloc
late SignInBloc signInBloc;
late SignUpBloc signupBloc;
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
signInBloc = BlocProvider.of<SignInBloc>(context);
signupBloc = BlocProvider.of<SignUpBloc>(context);
What I tried to do it to put alot of Print statement in order to check which part is getting called but I don't get why the SignUpBloc is not getting called anymore. Please help. Thanks!
Edit: I tried to debug.
This will trigger my SignInBloc. I'm able to listen to my SignInBloc.
signInBloc.add(CheckIfSignedInEvent());
This should trigger my SignUpBloc. But it doesn't do anything similar to my SignInBloc.
signupBloc.add(SignUpWithGoogle(name: selectedName));
Here's both of my events for comparison:
class CheckIfSignedInEvent extends SignInEvent {
#override
String toString() => 'CheckIfSignedInEvent';
}
class SignUpWithGoogle extends SignUpEvent {
final String name;
SignUpWithGoogle({required this.name});
#override
String toString() => 'SignUpWithGoogleEvent';
}
This is the part where I listen to the states which is both in my splash screen. Only signInBloc is able to listen.
signupBloc.stream.listen((state) {
print('BLOC: signupBloc splash screen init : $state');
});
signInBloc.stream.listen((state) {
print('BLOC: signinBloc splash screen init : $state');
});
It turns out that changing the Stream to Future will fix my issue. async* should also be changed to async
Future<void> mapSignUpWithGoogleEventToState(
SignUpWithGoogle event,
Emitter<SignUpState> emit,
) async {
emit(SignUpInProgressState());
try {
User? checkUser = await authenticationRepository.checkIfUserExists();
if (checkUser != null) {
emit(SignUpWithGoogleInitialExistState());
} else {
bool checkDup =
await authenticationRepository.checkIfUserDup(event.name);
if (checkDup == true) {
emit(SignUpWithNameExistState());
} else {
User firebaseUser = await authenticationRepository.signUpWithGoogle();
emit(SignUpWithGoogleInitialCompletedState(firebaseUser));
}
}
} catch (e) {
print(e);
emit(SignUpWithGoogleInitialFailedState());
}
}
I am following the migration to the new bloc 8.0.0. I am trying to remove the mapEventToState but I am having a difficulty in doing so. Can you help me how to do it. I have tried it below but it won't work.
Old code:
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState());
SignInState get initialState => SignInInitialState();
#override
Stream<SignInState> mapEventToState(
SignInEvent event,
) async* {
if (event is SignInWithGoogle) {
yield* mapSignInWithGoogleToState();
}
}
Stream<SignInState> mapSignInWithGoogleToState() async* {
yield SignInWithGoogleInProgressState();
try {
String res = await authenticationRepository.signInWithGoogle();
yield SignInWithGoogleCompletedState(res);
} catch (e) {
print(e);
yield SignInWithGoogleFailedState();
}
}
...
Migration code (does not work):
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState())
{
SignInState get initialState => SignInInitialState();
on<SignInWithGoogle>((event, emit) => emit(mapSignInWithGoogleToState()));
}
Stream<SignInState> mapSignInWithGoogleToState() async* {
yield SignInWithGoogleInProgressState();
try {
String res = await authenticationRepository.signInWithGoogle();
yield SignInWithGoogleCompletedState(res);
} catch (e) {
print(e);
yield SignInWithGoogleFailedState();
}
}
...
Your issue is that mapSignInWithGoogleToState() is returning a Stream of States, but the new structure needs explicit invocations of emit each time a new state needs to be emitted.
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc({required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<SignInWithGoogle>(mapSignInWithGoogleToState);
}
Future<void> mapSignInWithGoogleToState(
SignInWithGoogle event,
Emitter<SignInState> emit,
) async {
emit(SignInWithGoogleInProgressState());
try {
String res = await authenticationRepository.signInWithGoogle();
emit(SignInWithGoogleCompletedState(res));
} catch (e) {
print(e);
emit(SignInWithGoogleFailedState());
}
}
}
Here is some more information as to "why?": https://bloclibrary.dev/#/migration?id=rationale-8
The getter does not belong in the constructor body and I guess it isn't really needed anymore. You could solve the problem like this:
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc({required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<SignInWithGoogle>(_handleSignInWithGoogleEvent);
}
Future<void> _handleSignInWithGoogleEvent(
SignInWithGoogle event,
Emitter<SignInState> emit,
) async {
// TODO do your thing and create and emit the SignInWithGoogleState
emit(SignInWithGoogleState());
}
}
Be careful, the events in bloc v8 are not handled sequentially anymore, but concurrently. Give this blog post a read: https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency
How to transfer data from one screen to another using bloc and save , I would like to create a user profile where I have two screens, two steps to creating a profile. I created two blocs for each class, in one I have an avatar, city and name, in the other only description. I used an amplify and when I save the first screen and when I go to the second one, delete the data from the first screen. How do I save everything? without delete? after save second screen.
First screen:
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final DataRepository dataRepo;
final StorageRepository storageRepo;
final _picker = ImagePicker();
ProfileBloc(
{User? user,
required bool isCurrentUser,
required this.storageRepo,
required this.dataRepo})
: super(ProfileState(user: user, isCurrentUser: isCurrentUser)) {
// storageRepo
// .getUrlForFile(user!.avatarKey)
// .then((url) => add(ProvideImagePath(avatarPath: url)));
ImageUrlCache.instance
.getUrl(user!.avatarKey)
.then((url) => add(ProvideImagePath(avatarPath: url)));
}
#override
Stream<ProfileState> mapEventToState(ProfileEvent event) async* {
if (event is ChangeAvatarRequest) {
yield state.copyWith(isImageSourceActionSheetVisible: true);
} else if (event is OpenImagePicker) {
yield state.copyWith(isImageSourceActionSheetVisible: false);
try {
final selectedImage =
await _picker.pickImage(source: event.imageSource);
if (selectedImage == null) return;
final imageKey = await storageRepo.uploadFile(File(selectedImage.path));
final user = state.user!.copyWith(avatarKey: imageKey);
String? imageUrl;
await Future.wait<void>([
dataRepo.updateUser(user),
storageRepo.getUrlForFile(imageKey).then((value) => imageUrl = value)
]);
yield state.copyWith(avatarPath: imageUrl);
} catch (e) {
throw e;
}
} else if (event is ProvideImagePath) {
if (event.avatarPath != null)
yield state.copyWith(avatarPath: event.avatarPath);
} else if (event is ProfileCityChanged) {
yield state.copyWith(userCity: event.city);
} else if (event is ProfileNameChanged) {
yield state.copyWith(userName1: event.name);
} else if (event is SaveProfileChanges) {
// handle save changes
yield state.copyWith(formStatus: FormSubmitting());
final updatedUser2 =
state.user!.copyWith(city: state.userCity, name: state.userName1);
try {
await dataRepo.updateUser(updatedUser2);
print(updatedUser2);
yield state.copyWith(formStatus: SubmissionSuccess());
} on Exception catch (e) {
yield state.copyWith(formStatus: SubmissionFailed(e));
} catch (e) {
print(e);
}
}
}
}
Second screen:
class Profile2Bloc extends Bloc<Profile2Event, Profile2State> {
final DataRepository dataRepo;
// User? user;
// String ?get userCity => user!.city;
// String? get userName1 => user!.name;
Profile2Bloc(
{User? user, required bool isCurrentUser, required this.dataRepo})
: super(Profile2State(user: user, isCurrentUser: isCurrentUser));
#override
Stream<Profile2State> mapEventToState(Profile2Event event) async* {
if (event is ProfileDescriptionChanged) {
yield state.copyWith(userDescription: event.description);
} else if (event is SaveProfile2Changes) {
yield state.copyWith(formStatus: FormSubmitting());
final updatedUser =
state.user!.copyWith(description: state.userDescription);
try {
await dataRepo.updateUser(updatedUser);
print(updatedUser);
// print(userDescribe);
yield state.copyWith(formStatus: SubmissionSuccess());
} on Exception catch (e) {
yield state.copyWith(formStatus: SubmissionFailed(e));
} catch (e) {
print(e);
}
}
}
}
Data Repo:
Future<User> updateUser(User updatedUser) async {
try {
await Amplify.DataStore.save(updatedUser);
return updatedUser;
} catch (e) {
throw e;
}
}
My service _session try to log in and return true if succeed or an error message from catchError if fail. I would like to yield this message and so, call yield from the catch block, but it's not possible, so I did:
Will this work as I expected or is there another way to do this?
#override
Stream<DgState> mapEventToState(DgEvent event) async* {
if (event is LoginDgEvent) {
yield LoadingState();
String errMessage;
bool hasLogged = await _session
.login(event.userCredential.login, event.userCredential.password)
.catchError((err) {
errMessage = err;
});
yield LoginState(hasLogged ? 'Ok': errMessage);
}
}
You can create a event for update state.
#override
Stream<DgState> mapEventToState(DgEvent event) async* {
//Event for update state
if(event is LoginUpdateStateEvent){
yield event.state;
}
if (event is LoginDolceGustoEvent) {
yield LoadingState();
String errMessage;
bool hasLogged = await _session
.login(event.userCredential.login, event.userCredential.password)
.catchError((err) {
//dispatch
dispatch(LoginUpdateStateEvent(state:LoginErrorState(errMessage)));
});
yield LoginState(hasLogged ? 'Ok': errMessage);
}
}