Events and states for the sign-in form BLoC are in place after the previous part. It's time to finally write some logic in the application layer that will be responsible for gluing the UI presentation layer together with the other layers of the app.
Bloc Logic
The logic performed inside BLoCs is focused on transforming incoming events into states. For example, a raw String
will come in from the UI and a validated EmailAddress
will come out.
We have created 5 events which upon which we should perform some logic inside the mapEventToState
method. Let's utilize the power of unions which will make sure we don't forget to handle any of them.
sign_in_form_bloc.dart
@override
Stream<SignInFormState> mapEventToState(
SignInFormEvent event,
) async* {
yield* event.map(
emailChanged: (e) async* {},
passwordChanged: (e) async* {},
registerWithEmailAndPasswordPressed: (e) async* {},
signInWithEmailAndPasswordPressed: (e) async* {},
signInWithGooglePressed: (e) async* {},
);
}
Field updates
The simplest events to implement are those which simply receive unvalidated raw data from the UI and transform it into validated ValueObject
s.
sign_in_form_bloc.dart
emailChanged: (e) async* {
yield state.copyWith(
emailAddress: EmailAddress(e.emailStr),
authFailureOrSuccessOption: none(),
);
},
passwordChanged: (e) async* {
yield state.copyWith(
password: Password(e.passwordStr),
authFailureOrSuccessOption: none(),
);
},
authFailureOrSuccessOption
field whenever we emit a new state. This field holds a "response" from the previous call to sign in/register using IAuthFacade
. Surely, when the email address changes, it's not correct to associate the old "auth response" with the updated email address.Sign in with Google
We're finally going to call a method on the IAuthFacade
from this event handler. First, we'll indicate that the form is in the process of being submitted and once the signInWithGoogle
method had a chance to run, we'll yield a state containing either a failure (AuthFailure
) or success (Unit
).
sign_in_form_bloc.dart
signInWithGooglePressed: (e) async* {
yield state.copyWith(
isSubmitting: true,
authFailureOrSuccessOption: none(),
);
final failureOrSuccess = await _authFacade.signInWithGoogle();
yield state.copyWith(
isSubmitting: false,
authFailureOrSuccessOption: some(failureOrSuccess));
},
Register & sign in with email and password
These last two event handlers contain the largest amount of code. It's still simple though and the logic can be broken down into a couple of steps. Let's focus on the registration at first.
- Check if the entered
EmailAddress
andPassword
are valid. - If valid, register using
IAuthFacade
and yieldSome<Right<Unit>>
in theauthFailureOrSuccessOption
state field. - If invalid, indicate to start showing error messages and keep the
authFailureOrSuccessOption
set toNone
.
We know that ValueObject
s have a value
property which is of type Either
. Therefore, to check if the inputted email address is valid, we can simply call myEmailAddress.value.isRight()
. Wouldn't it be more expressive though to call myEmailAddress.isValid()
?
Let's create such a method in the ValueObject
super class:
domain/core/value_objects.dart
@immutable
abstract class ValueObject<T> {
const ValueObject();
Either<ValueFailure<T>, T> get value;
bool isValid() => value.isRight();
...
}
With this, we can go ahead and implement the sign in event handler.
sign_in_form_bloc.dart
registerWithEmailAndPasswordPressed: (e) async* {
final isEmailValid = state.emailAddress.isValid();
final isPasswordValid = state.password.isValid();
if (isEmailValid && isPasswordValid) {
yield state.copyWith(
isSubmitting: true,
authFailureOrSuccessOption: none(),
);
final failureOrSuccess =
await _authFacade.registerWithEmailAndPassword(
emailAddress: state.emailAddress,
password: state.password,
);
yield state.copyWith(
isSubmitting: false,
authFailureOrSuccessOption: some(failureOrSuccess),
);
}
yield state.copyWith(
showErrorMessages: true,
authFailureOrSuccessOption: none(),
);
},
In my opinion, we have way too many state.copyWith
calls in the code above. We can simplify it a bit with the optionOf
method, which turns null
into none()
.
sign_in_form_bloc.dart
registerWithEmailAndPasswordPressed: (e) async* {
Either<AuthFailure, Unit> failureOrSuccess;
final isEmailValid = state.emailAddress.isValid();
final isPasswordValid = state.password.isValid();
if (isEmailValid && isPasswordValid) {
yield state.copyWith(
isSubmitting: true,
authFailureOrSuccessOption: none(),
);
failureOrSuccess = await _authFacade.registerWithEmailAndPassword(
emailAddress: state.emailAddress,
password: state.password,
);
}
yield state.copyWith(
isSubmitting: false,
showErrorMessages: true,
// optionOf is equivalent to:
// failureOrSuccess == null ? none() : some(failureOrSuccess)
authFailureOrSuccessOption: optionOf(failureOrSuccess),
);
},
Awesome! We can now implement the signInWithEmailAndPasswordPressed
event handler but it will be suspiciously simple ?
sign_in_form_bloc.dart
signInWithEmailAndPasswordPressed: (e) async* {
Either<AuthFailure, Unit> failureOrSuccess;
final isEmailValid = state.emailAddress.isValid();
final isPasswordValid = state.password.isValid();
if (isEmailValid && isPasswordValid) {
yield state.copyWith(
isSubmitting: true,
authFailureOrSuccessOption: none(),
);
failureOrSuccess = await _authFacade.signInWithEmailAndPassword(
emailAddress: state.emailAddress,
password: state.password,
);
}
yield state.copyWith(
isSubmitting: false,
showErrorMessages: true,
authFailureOrSuccessOption: optionOf(failureOrSuccess),
);
},
Only one change! As is usually the case, this means only one thing - refactoring.
Refactoring
The only thing that changed between the sign in and register event handlers was the method which is being called on the IAuthFacade
. This is a great opportunity to utilize the fact that Dart supports higher-order functions (a function which takes in another function as an argument).
We can break out the common code into a private method on the SignInFormBloc
and then invoke a Function
parameter which adheres to the signature of the IAuthFacade
methods. In our case, the method has to return Future<Either<AuthFailure, Unit>>
and take in an EmailAddress
and a Password
.
sign_in_form_bloc.dart
Stream<SignInFormState> _performActionOnAuthFacadeWithEmailAndPassword(
Future<Either<AuthFailure, Unit>> Function({
@required EmailAddress emailAddress,
@required Password password,
}) forwardedCall,
) async* {
Either<AuthFailure, Unit> failureOrSuccess;
final isEmailValid = state.emailAddress.isValid();
final isPasswordValid = state.password.isValid();
if (isEmailValid && isPasswordValid) {
yield state.copyWith(
isSubmitting: true,
authFailureOrSuccessOption: none(),
);
failureOrSuccess = await forwardedCall(
emailAddress: state.emailAddress,
password: state.password,
);
}
yield state.copyWith(
isSubmitting: false,
showErrorMessages: true,
authFailureOrSuccessOption: optionOf(failureOrSuccess),
);
}
Boom! This common private method allows us to cut the length of our code in half. This is how the two event handlers now look like:
sign_in_form_bloc.dart
registerWithEmailAndPasswordPressed: (e) async* {
yield* _performActionOnAuthFacadeWithEmailAndPassword(
_authFacade.registerWithEmailAndPassword,
);
},
signInWithEmailAndPasswordPressed: (e) async* {
yield* _performActionOnAuthFacadeWithEmailAndPassword(
_authFacade.signInWithEmailAndPassword,
);
},
FirebaseAuth is coming!
By now, we have implemented the domain and application layer classes responsible for signing in or registering. In the next part, we're going to set up FirebaseAuth
and hop into the infrastructure layer to implement the IAuthFacade
.
Hi Mark,
Great job, thank you for all the work your are putting in these tutorials they are really really well presented and provide deep knowledge of how to develop professional and well architected App. I am following the series related to “Flutter Firebase” and I was wondering when are the next tutorials going to be published I can image it take a lot of work but I am can not wait for it.
One is out now and you can always refer to the fully finished project on GitHub if you’re impatient ?
Sorry about the last post where I wrote Mark instead of Matt
No problem ?
hi, congratulations, this is amazing. This course has
finished or other part (6) coming soon?
It’s out now!
Hi Matt, a pleasure to greet you. First of all, thank you for all the content that echo has and congratulate you for the classes that you share with us. In addition to this, I would like to know how I can buy the complete Flutter and firebase course, or will each of the videos just come out? in how much time?
Hi Matt, I can’t find the part 6 of this series 🙁
Hi!
First of all thank you for your amazing tutorials.
I have a question about clean architecture, i would like to use retrofit in order to avoid writing http calls. But i dont really know where can i put my interface file ? Domain layer? Infrastructure layer?
Thank you in advance 🙂
Hey! Retrofit or chopper interfaces should go into the infrastructure layer. That’s because they should be used from a repository, unless you’re 100% sure that you don’t want to have any data-fetching logic such as filtering or caching.
Thanks Matt!
Have a nice day 🙂
Hi Matt, thanks for working on this series at such a fast pace. Great stuff!
I was wondering if you could provide the snippet for
bloceventhandler? That would be super helpful.
Thanks 😀
Hello Jason,
Here it is :
“Bloc Event”: {
“prefix”: “blocevent”,
“body”: [
“(e) async* {${1}}”
],
“description”: “Bloc Event”
}
I have question:
About using freezed and Either is it better than using rxdart (stream /controller/observer) ?
Sorry,I forget to say”Thanks Matt for your videos”
Anyone help why my VS Code don’t have code complete for the fields in the `state.copyWith`?
same here, don’t know why
Remember to run:
flutter pub run build_runner watch –delete-conflicting-outputs
after that, you should be able to see state.copyWith() autofilled
how can we enable Dart: New Completion Placeholders?
Matt, awesome! Thanks for your generosity in sharing your knowledge and inspiring a next generation of developers.
I would like to know if you have touched about the ideal flutter development practices, consisting in how to use Git effectively, consistent and clear commit messages (conventional commits), planning/breaking the project into issues, branching and merging. Also how to integrate automated tests to the git repository, tools like Travis CI, code linting, code test coverage and automated detection of packages upgrade/testing.
Thanks
Roberto
Hi Matt,
Just starting this course; can’t seem to find any tutorials after number 5. Is this course finished?
Many thanks,
George
HI, one question about the streams, as you are using StatelessWidgets, how are closing the streams to avoid memory leak, and in firebase case, reaching the Perrmission denied error?
Thanks
For someone who uses provider/riverprod this was a bit confusing.
In your architecture diagram you mentioned to use ViewModels and UseCases when one does not want to use BLoC.
I struggle to implement it using riverprod (or provider). Could you show an example?
hi, matt,
I am facing some dart error I think or because the version is different, in the sign_in_form_bloc.dart were the :
SignInFormBloc(this._authFacade);
the error is :
The superclass ‘Bloc’ doesn’t have a zero argument constructor.
Try declaring a zero argument constructor in ‘Bloc’, or explicitly invoking a different constructor in ‘Bloc’
and if I press ctrl +. I got many fixes but don’t know which one to press.
may you help me to continue with these lessons and thanks a lot I am very comfortable with your courses !
You need to pass in the initial state to the super constructor. SignInFormBloc(this._authFacade) : super(SignInFormState.initial());
Hello , I just saw that you called a method on the IAuthFacade , for example:
_authFacade.getSignedInUser()
But i don’t understand how it is possible since _authFacade is a IAuthFacade (an abstract class). I thought that abstract classes were just contracts for the classes that will implement them. So how is it possible to use _authFacade.getSignedInUser() ? Furthermore, the method getSignedInUser() inside IAuthFacade doesn’t even have a body, since it is abstract.
Can you please explain that to me or refer me to some links that explain that point ? Thank you very much.
Hello Sina,
As I can see in the part 4, the _authFacade is injected. It means, some class (not abstract anymore) implemented it, which was then injected into SingInFormBloc.
class SignInFormBloc extends Bloc {
final IAuthFacade _authFacade;
SignInFormBloc(this._authFacade);
…
BR,
Viktor
The DDD course is helpful to me. I followed all of 5 episodes, the result of my code is clear and understandable. Thank you so much for your sharing it. I’m eager to wait for the next episodes you will to post. It’s specially on Infrastructure with local database and APIs, and also Presentation with BLOCs.
Flutter bloc has been upgraded to version 8 most of these functions are no more working how do we go about this.
Hello, No Doubt this has been a most useful series, but can you please make few updates in article as bloc has been updated to version 8 and most of the implementations are old in this series.
If you update article please ping me on my email.
Thank you.