Skip to content

Commit

Permalink
use emit.onEach instead of handling stream subscription manually
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasMirbt committed Jul 21, 2024
1 parent 8bbd6c4 commit 59c7317
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,43 @@ 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());
}
await 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: (error, stackTrace) {
addError(error, stackTrace);
emit(const AuthenticationState.unauthenticated());
},
);
}

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 @@ -29,7 +29,7 @@ class HomePage extends StatelessWidget {
onPressed: () {
context
.read<AuthenticationBloc>()
.add(AuthenticationLogoutRequested());
.add(AuthenticationLogoutPressed());
},
),
],
Expand Down
247 changes: 127 additions & 120 deletions examples/flutter_login/test/authentication/authentication_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ void main() {
userRepository = _MockUserRepository();
});

AuthenticationBloc buildBloc() {
return AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
);
}

group('AuthenticationBloc', () {
test('initial state is AuthenticationState.unknown', () {
final authenticationBloc = AuthenticationBloc(
Expand All @@ -33,135 +40,135 @@ void main() {
authenticationBloc.close();
});

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is unauthenticated',
setUp: () {
when(() => authenticationRepository.status).thenAnswer(
(_) => Stream.value(AuthenticationStatus.unauthenticated),
);
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.unauthenticated(),
],
);
group('AuthenticationSubscriptionRequested', () {
final error = Exception('oops');

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [authenticated] when status is authenticated',
setUp: () {
when(() => authenticationRepository.status).thenAnswer(
(_) => Stream.value(AuthenticationStatus.authenticated),
);
when(() => userRepository.getUser()).thenAnswer((_) async => user);
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.authenticated(user),
],
);
});
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is unauthenticated',
setUp: () {
when(() => authenticationRepository.status).thenAnswer(
(_) => Stream.value(AuthenticationStatus.unauthenticated),
);
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unauthenticated(),
],
);

group('AuthenticationStatusChanged', () {
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [authenticated] when status is authenticated',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenAnswer((_) async => user);
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.authenticated(user),
],
);
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [authenticated] when status is authenticated',
setUp: () {
when(() => authenticationRepository.status).thenAnswer(
(_) => Stream.value(AuthenticationStatus.authenticated),
);
when(() => userRepository.getUser()).thenAnswer((_) async => user);
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.authenticated(user),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is unauthenticated',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.unauthenticated));
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.unauthenticated(),
],
);
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [authenticated] when status is authenticated',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenAnswer((_) async => user);
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.authenticated(user),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is authenticated but getUser fails',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenThrow(Exception('oops'));
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.unauthenticated(),
],
);
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is unauthenticated',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer(
(_) => Stream.value(AuthenticationStatus.unauthenticated));
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unauthenticated(),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is authenticated '
'but getUser returns null',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenAnswer((_) async => null);
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.unauthenticated(),
],
);
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is authenticated but getUser fails',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenThrow(Exception('oops'));
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unauthenticated(),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unknown] when status is unknown',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.unknown));
},
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
expect: () => const <AuthenticationState>[
AuthenticationState.unknown(),
],
);
blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status is authenticated '
'but getUser returns null',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));
when(() => userRepository.getUser()).thenAnswer((_) async => null);
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unauthenticated(),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unknown] when status is unknown',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.value(AuthenticationStatus.unknown));
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unknown(),
],
);

blocTest<AuthenticationBloc, AuthenticationState>(
'emits [unauthenticated] when status stream emits an error',
setUp: () {
when(
() => authenticationRepository.status,
).thenAnswer((_) => Stream.error(error));
},
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),
expect: () => const [
AuthenticationState.unauthenticated(),
],
errors: () => [error],
);
});
});

group('AuthenticationLogoutRequested', () {
group('AuthenticationLogoutPressed', () {
blocTest<AuthenticationBloc, AuthenticationState>(
'calls logOut on authenticationRepository '
'when AuthenticationLogoutRequested is added',
build: () => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
act: (bloc) => bloc.add(AuthenticationLogoutRequested()),
'calls logOut on authenticationRepository ',
build: buildBloc,
act: (bloc) => bloc.add(AuthenticationLogoutPressed()),
verify: (_) {
verify(() => authenticationRepository.logOut()).called(1);
},
Expand Down

0 comments on commit 59c7317

Please sign in to comment.