Skip to content

Commit

Permalink
refactor(flutter_login): use emit.onEach in AuthenticationBloc (#…
Browse files Browse the repository at this point in the history
…4211)

Co-authored-by: Felix Angelov <[email protected]>
  • Loading branch information
LukasMirbt and felangel authored Jul 28, 2024
1 parent 59d7512 commit f82f896
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 170 deletions.
25 changes: 18 additions & 7 deletions docs/src/content/docs/tutorials/flutter-login.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ In the following tutorial, we're going to build a Login Flow in Flutter using th
## Key Topics

- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which provides a bloc to its children.
- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles building the widget in response to new states.
- Using Cubit instead of Bloc. [What's the difference?](/bloc-concepts#cubit-vs-bloc)
- Adding events with [context.read](/flutter-bloc-concepts#contextread).
- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).
Expand Down Expand Up @@ -192,8 +191,8 @@ Use the [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=F

In this application, the `AuthenticationBloc` will be reacting to two different events:

- `AuthenticationStatusChanged`: notifies the bloc of a change to the user's `AuthenticationStatus`
- `AuthenticationLogoutRequested`: notifies the bloc of a logout request
- `AuthenticationSubscriptionRequested`: initial event that notifies the bloc to subscribe to the `AuthenticationStatus` stream
- `AuthenticationLogoutPressed`: notifies the bloc of a user logout action

<RemoteCode
url="https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart"
Expand Down Expand Up @@ -232,15 +231,25 @@ The `AuthenticationBloc` manages the authentication state of the application whi

The `AuthenticationBloc` has a dependency on both the `AuthenticationRepository` and `UserRepository` and defines the initial state as `AuthenticationState.unknown()`.

In the constructor body, the `AuthenticationBloc` subscribes to the `status` stream of the `AuthenticationRepository` and adds an `AuthenticationStatusChanged` event internally in response to a new `AuthenticationStatus`.
In the constructor body, `AuthenticationEvent` subclasses are mapped to their corresponding event handlers.

In the `_onSubscriptionRequested` event handler, the `AuthenticationBloc` uses `emit.onEach` to subscribe to the `status` stream of the `AuthenticationRepository` and emit a state in response to each `AuthenticationStatus`.

`emit.onEach` creates a stream subscription internally and takes care of canceling it when either `AuthenticationBloc` or the `status` stream is closed.

If the `status` stream emits an error, `addError` forwards the error and stackTrace to any `BlocObserver` listening.

:::caution
The `AuthenticationBloc` overrides `close` in order to dispose both the `StreamSubscription` as well as the `AuthenticationRepository`.
If `onError` is omitted, any errors on the `status` stream are considered unhandled, and will be thrown by `onEach`. As a result, the subscription to the `status` stream will be canceled.
:::

Next, the `EventHandler` handles transforming the incoming `AuthenticationEvent` instances into new `AuthenticationState` instances.
:::tip
A [`BlocObserver`](/bloc-concepts/#blocobserver-1) is great for logging Bloc events, errors, and state changes especially in the context analytics and crash reporting.;
:::

When the `status` stream emits `AuthenticationStatus.unknown` or `unauthenticated`, the corresponding `AuthenticationState` is emitted.

When an `AuthenticationStatusChanged` event is added if the associated status is `AuthenticationStatus.authenticated`, the `AuthentictionBloc` queries the user via the `UserRepository`.
When `AuthenticationStatus.authenticated` is emitted, the `AuthentictionBloc` queries the user via the `UserRepository`.

## main.dart

Expand Down Expand Up @@ -272,6 +281,8 @@ We are injecting a single instance of the `AuthenticationRepository` and `UserRe
`RepositoryProvider` is used to provide the single instance of `AuthenticationRepository` to the entire application which will come in handy later on.
:::

By default, `BlocProvider` is lazy and does not call `create` until the first time the Bloc is accessed. Since `AuthenticationBloc` should always subscribe to the `AuthenticationStatus` stream immediately (via the `AuthenticationSubscriptionRequested` event), we can explicitly opt out of this behavior by setting `lazy: false`.

`AppView` is a `StatefulWidget` because it maintains a `GlobalKey` which is used to access the `NavigatorState`. By default, `AppView` will render the `SplashPage` (which we will see later) and it uses `BlocListener` to navigate to different pages based on changes in the `AuthenticationState`.

## Splash
Expand Down
3 changes: 2 additions & 1 deletion examples/flutter_login/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ class _AppState extends State<App> {
return RepositoryProvider.value(
value: _authenticationRepository,
child: BlocProvider(
lazy: false,
create: (_) => AuthenticationBloc(
authenticationRepository: _authenticationRepository,
userRepository: _userRepository,
),
)..add(AuthenticationSubscriptionRequested()),
child: const AppView(),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,40 @@ class AuthenticationBloc
}) : _authenticationRepository = authenticationRepository,
_userRepository = userRepository,
super(const AuthenticationState.unknown()) {
on<_AuthenticationStatusChanged>(_onAuthenticationStatusChanged);
on<AuthenticationLogoutRequested>(_onAuthenticationLogoutRequested);
_authenticationStatusSubscription = _authenticationRepository.status.listen(
(status) => add(_AuthenticationStatusChanged(status)),
);
on<AuthenticationSubscriptionRequested>(_onSubscriptionRequested);
on<AuthenticationLogoutPressed>(_onLogoutPressed);
}

final AuthenticationRepository _authenticationRepository;
final UserRepository _userRepository;
late StreamSubscription<AuthenticationStatus>
_authenticationStatusSubscription;

@override
Future<void> close() {
_authenticationStatusSubscription.cancel();
return super.close();
}

Future<void> _onAuthenticationStatusChanged(
_AuthenticationStatusChanged event,
Future<void> _onSubscriptionRequested(
AuthenticationSubscriptionRequested event,
Emitter<AuthenticationState> emit,
) async {
switch (event.status) {
case AuthenticationStatus.unauthenticated:
return emit(const AuthenticationState.unauthenticated());
case AuthenticationStatus.authenticated:
final user = await _tryGetUser();
return emit(
user != null
? AuthenticationState.authenticated(user)
: const AuthenticationState.unauthenticated(),
);
case AuthenticationStatus.unknown:
return emit(const AuthenticationState.unknown());
}
) {
return emit.onEach(
_authenticationRepository.status,
onData: (status) async {
switch (status) {
case AuthenticationStatus.unauthenticated:
return emit(const AuthenticationState.unauthenticated());
case AuthenticationStatus.authenticated:
final user = await _tryGetUser();
return emit(
user != null
? AuthenticationState.authenticated(user)
: const AuthenticationState.unauthenticated(),
);
case AuthenticationStatus.unknown:
return emit(const AuthenticationState.unknown());
}
},
onError: addError,
);
}

void _onAuthenticationLogoutRequested(
AuthenticationLogoutRequested event,
void _onLogoutPressed(
AuthenticationLogoutPressed event,
Emitter<AuthenticationState> emit,
) {
_authenticationRepository.logOut();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ sealed class AuthenticationEvent {
const AuthenticationEvent();
}

final class _AuthenticationStatusChanged extends AuthenticationEvent {
const _AuthenticationStatusChanged(this.status);
final class AuthenticationSubscriptionRequested extends AuthenticationEvent {}

final AuthenticationStatus status;
}

final class AuthenticationLogoutRequested extends AuthenticationEvent {}
final class AuthenticationLogoutPressed extends AuthenticationEvent {}
2 changes: 1 addition & 1 deletion examples/flutter_login/lib/home/view/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class _LogoutButton extends StatelessWidget {
return ElevatedButton(
child: const Text('Logout'),
onPressed: () {
context.read<AuthenticationBloc>().add(AuthenticationLogoutRequested());
context.read<AuthenticationBloc>().add(AuthenticationLogoutPressed());
},
);
}
Expand Down
Loading

0 comments on commit f82f896

Please sign in to comment.