I'm trying to implement blocTesting for my flutter app starting with authentication feature. Below are the Authentication and login related files required for this. I'd really appreciate if someone could show me on how I can implement blocTesting based on my code because I've been facing problems in doing so. Below are the bloc, state and event files for the auth bloc.
Authbloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'authentication_event.dart';
part 'authentication_state.dart';
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final AuthenticationRepository authenticationRepository = AuthenticationRepository();
final SettingsRepository _settingsRepository = SettingsRepository();
AuthenticationBloc() : super(AuthenticationInitial()) {
// Register events here
on<AuthenticationStarted>(_onAuthenticationStarted);
on<AuthenticationLoggedIn>(_onAuthenticationLoggedIn);
on<AuthenticationLoggedOut>(_onAuthenticationLoggedOut);
}
Future<void> _onAuthenticationStarted(AuthenticationStarted event, Emitter<AuthenticationState> emit) async {
try {
final bool hasToken = await authenticationRepository.hasToken();
if (hasToken) {
final Settings _settings = await _settingsRepository.getSettings();
final SysConfig _sysConfig = await _settingsRepository.getSysconfig();
final CountriesModelList _countries = await _settingsRepository.getCountries();
final ReasonsModelList _reasons = await _settingsRepository.getReasons();
final NotificationOptionsList _notificationOptions = await _settingsRepository.getNotificationOptions();
emit(
AuthenticationLoadSuccess(
settings: _settings,
sysConfig: _sysConfig,
countries: _countries,
reasons: _reasons,
notificationOptions: _notificationOptions,
),
);
} else {
emit(AuthenticationUnauthenticated());
}
} catch (e) {
final MYException _exception = e as MYException;
emit(AuthenticationLoadFailure(exception: _exception));
}
}
Future<void> _onAuthenticationLoggedIn(AuthenticationLoggedIn event, Emitter<AuthenticationState> emit) async {
emit(AuthenticationLoadInProgress());
await authenticationRepository.persistToken(event.token);
final Settings _settings = await _settingsRepository.getSettings();
final SysConfig _sysConfig = await _settingsRepository.getSysconfig();
final CountriesModelList _countries = await _settingsRepository.getCountries();
final ReasonsModelList _reasons = await _settingsRepository.getReasons();
final NotificationOptionsList _notificationOptions = await _settingsRepository.getNotificationOptions();
emit(
AuthenticationLoadSuccess(
settings: _settings,
sysConfig: _sysConfig,
countries: _countries,
reasons: _reasons,
notificationOptions: _notificationOptions,
),
);
}
Future<void> _onAuthenticationLoggedOut(AuthenticationLoggedOut event, Emitter<AuthenticationState> emit) async {
await authenticationRepository.deleteToken();
await Future<dynamic>.delayed(const Duration(seconds: 2));
emit(AuthenticationUnauthenticated());
add(AuthenticationStarted());
}
}
Authstate.dart
part of 'authentication_bloc.dart';
abstract class AuthenticationEvent extends Equatable {
const AuthenticationEvent();
#override
List<Object> get props => <Object>[];
}
class AuthenticationStarted extends AuthenticationEvent {}
class AuthenticationLoggedIn extends AuthenticationEvent {
final String token;
const AuthenticationLoggedIn({required this.token});
#override
List<Object> get props => <Object>[token];
}
class AuthenticationLoggedOut extends AuthenticationEvent {}
AuthEvent.dart
part of 'authentication_bloc.dart';
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
#override
List<Object> get props => <Object>[];
}
class AuthenticationInitial extends AuthenticationState {}
class AuthenticationUnauthenticated extends AuthenticationState {}
class AuthenticationLoadSuccess extends AuthenticationState {
final SysConfig sysConfig;
final Settings settings;
final CountriesModelList countries;
final ReasonsModelList reasons;
final NotificationOptionsList notificationOptions;
const AuthenticationLoadSuccess({required this.sysConfig, required this.settings, required this.countries, required this.reasons, required this.notificationOptions});
#override
List<Object> get props => <Object>[sysConfig, settings, countries, reasons, notificationOptions];
}
class AuthenticationLoadInProgress extends AuthenticationState {}
class AuthenticationLoadFailure extends AuthenticationState {
final MYException exception;
const AuthenticationLoadFailure({required this.exception});
#override
List<Object> get props => <Object>[exception];
}
you have to change a lot of thinks.
First of all you need to add the repository/ies to your bloc constructor to inject the mocks.
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
late final AuthenticationRepository authenticationRepository;
final SettingsRepository _settingsRepository = SettingsRepository();
AuthenticationBloc({required this.authenticationRepository}) : super(AuthenticationInitial()) {
// Register events here
on<AuthenticationStarted>(_onAuthenticationStarted);
on<AuthenticationLoggedIn>(_onAuthenticationLoggedIn);
on<AuthenticationLoggedOut>(_onAuthenticationLoggedOut);
}
Then you can use the mock when creating the bloc in the setup method
setUp(() {
authenticationRepositoryMock = MockWeatherRepository();
authenticationBloc = AuthenticationBloc(authenticationRepository: authenticationRepositoryMock );
});
Then you have to return that bloc in the build function of your blocTest and also you have to setup the mock behavior there
build: () {
when(() => authenticationRepositoryMock .hasToken()).thenAnswer((_) async => true);
return bloc;
},
Then add an event to your bloc in the act function
act: (dynamic b) => b.add(AuthenticationStarted()),
And then you can check the result in the expect function. (i think the initial state will not be emitted here)
expect: () => [
AuthenticationLoadSuccess(...),
It also a good idea to mock the SettingsRepository.
Related
i am Working on Flutter Chat Application and when i tried to implement the MessageBloc i found that mapEventToState is not Working anymore in Version 8.0 of Bloc
This is MessageEvent
import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';
abstract class MessageEvent extends Equatable {
const MessageEvent();
factory MessageEvent.onSubscribed(User user) => Subscribed(user);
factory MessageEvent.onMessageSent(List<Message> messages) =>
MessageSent(messages);
#override
List<Object?> get props => [];
}
class Subscribed extends MessageEvent {
final User user;
Subscribed(this.user);
#override
List<Object?> get props => [user];
}
class MessageSent extends MessageEvent {
final List<Message> messages;
MessageSent(this.messages);
#override
List<Object> get props => [messages];
}
class MessageReceived extends MessageEvent {
MessageReceived(this.message);
final Message message;
#override
List<Object> get props => [message];
}
And this is MessageState
import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';
abstract class MessageState extends Equatable {
const MessageState();
factory MessageState.initial() => MessageInitial();
factory MessageState.sent(Message message) => MessageSentSuccesfully(message);
factory MessageState.received(Message message) =>
MessageReceivedSuccesfully(message);
#override
List<Object> get props => [];
}
class MessageInitial extends MessageState {}
class MessageSentSuccesfully extends MessageState {
final Message message;
const MessageSentSuccesfully(this.message);
#override
List<Object> get props => [message];
}
class MessageReceivedSuccesfully extends MessageState {
final Message message;
const MessageReceivedSuccesfully(this.message);
#override
List<Object> get props => [message];
}
and finally this is MessageBloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:chat/chat.dart';
import 'package:flutter_newapp/src/blocs/message/message_event.dart';
import 'package:flutter_newapp/src/blocs/message/message_state.dart';
class MessageBloc extends Bloc<MessageEvent, MessageState> {
final IMessageService _messageService;
StreamSubscription? _subscription;
MessageBloc(this._messageService) : super(MessageState.initial());
#override
Stream<MessageState> mapEventToState(MessageEvent event) async* {
if (event is Subscribed) {
await _subscription?.cancel();
_subscription = _messageService
.messages(activeUser: event.user)
.listen((message) => add(MessageReceived(message)));
}
if (event is MessageReceived) {
yield MessageState.received(event.message);
}
if (event is MessageSent) {
final message = await _messageService.send(event.messages);
yield MessageState.sent(message);
}
}
#override
Future<void> close() {
_subscription?.cancel();
_messageService.dispose();
return super.close();
}
}
Can anyone propose me a Solution to Rewrite the MessageBloc in the new Version of Bloc ?
i am trying to move from version 7.1 of Bloc to 8.1 or the Latest version of Bloc and try to learn the new implementation of Blocs in Flutter
I have a bloc listening to chats. Through the debugger, I can see that the bloc is catching live updates. However, the ui is not rebuilding when these changes occur. To see a change, I have to leave and reload the screen.
My chat state:
part of 'chat_bloc.dart';
abstract class ChatState extends Equatable {
const ChatState();
#override
List<Object?> get props => [];
}
class ChatLoading extends ChatState {}
class ChatLoaded extends ChatState {
final List<Chat?>? compiledChats;
const ChatLoaded({required this.compiledChats});
#override
List<Object?> get props => [compiledChats];
}
My chat events:
part of 'chat_bloc.dart';
abstract class ChatEvent extends Equatable {
const ChatEvent();
#override
List<Object?> get props => [];
}
class LoadChat extends ChatEvent {
const LoadChat();
#override
List<Object> get props => [];
}
class CloseChat extends ChatEvent {
const CloseChat();
#override
List<Object?> get props => [];
}
class UpdateChat extends ChatEvent {
final List<List<Chat?>> chats;
const UpdateChat({required this.chats});
#override
List<Object> get props => [chats];
}
My actual bloc:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hero/blocs/auth/auth_bloc.dart';
import 'package:hero/models/chat_model.dart';
import 'package:hero/models/user_model.dart';
import 'package:hero/repository/firestore_repository.dart';
part 'chat_event.dart';
part 'chat_state.dart';
class ChatBloc extends Bloc<ChatEvent, ChatState> {
final FirestoreRepository _firestoreRepository;
late StreamSubscription _chatListener;
ChatBloc({
required FirestoreRepository firestoreRepository,
}) : _firestoreRepository = firestoreRepository,
super(ChatLoading()) {
on<LoadChat>(_onLoadChat);
on<UpdateChat>(_onUpdateChat);
on<CloseChat>(_onCloseChat);
}
void _onLoadChat(
LoadChat event,
Emitter<ChatState> emit,
) {
_chatListener = _firestoreRepository.chats.listen((chats) {
add(
UpdateChat(
chats: chats,
),
);
});
}
void _onUpdateChat(
UpdateChat event,
Emitter<ChatState> emit,
) {
//generate compiledChats from event.chats
List<Chat?>? compiledChats = [];
for (List<Chat?> chatList in event.chats) {
for (Chat? chat in chatList) {
if (chat != null) {
compiledChats.add(chat);
}
}
}
emit(ChatLoaded(compiledChats: compiledChats));
}
void _onCloseChat(
CloseChat event,
Emitter<ChatState> emit,
) {
_chatListener.cancel();
print('ChatBloc disposed');
emit(ChatLoading());
}
#override
Future<void> close() async {
super.close();
}
}
For the scope of this problem, all that really matters is just looking at the updateChat and LoadChat methods. Any ideas? Thanks!
I want to write a bloc test. I have a bloc that calls the provider. The provider accesses the repository, where I take a list of groups from the database, convert and return. I don't understand how to write a bloc test for this. How can I implement this?
My Bloc:
class GroupBloc extends Bloc<GroupEvent, GroupState> {
GroupBloc() : super(const NewGroupInitial()) {
on<GetAllGroupsEvent>(
(event, emit) async => emit(
await _getAllGroups(),
),
);
}
final _provider = ProviderInjector.instance.groupProvider;
Future<GroupState> _getAllGroups() async =>
_provider.getGroupList().then(GetAllGroupsState.new);
}
My Provider:
class GroupProvider implements BaseProvider<GroupRepository> {
#override
GroupRepository get repository => injector<GroupRepository>();
Future<List<Group>> getGroupList() => repository.getGroupList();
}
My Repository:
class GroupRepository {
GroupDao dao = GroupDao();
static BaseConverter<GroupEntity, Group> converter = GroupDbConverter();
Future<List<Group>> getGroupList() async {
final getGroupsList = await dao.getAll();
final groupsList = converter.listInToOut(getGroupsList);
return groupsList;
}
}
I just started learning bloc and I'm trying to make a cart bloc but I'm getting a state error:
Error: Unhandled error Expected a value of type 'CartSuccess', but got one of type 'CartLoading' occurred in bloc Instance of 'CartBloc'.
Even if I delete the yield CartLoading(), the error change to:
Error: Unhandled error Expected a value of type 'CartSuccess', but got one of type 'CartInitial' occurred in bloc Instance of 'CartBloc'.
Cart State
abstract class CartState extends Equatable {
const CartState();
#override
List<Object> get props => [];
}
class CartInitial extends CartState {}
class CartLoading extends CartState {}
class CartSuccess extends CartState {
final List<ProductModel> carts;
const CartSuccess(this.carts);
#override
List<Object> get props => [carts];
}
class CartFailed extends CartState {}
Cart Event
abstract class CartEvent extends Equatable {
const CartEvent();
#override
List<Object> get props => [];
}
class FetchCart extends CartEvent {}
class AddToCart extends CartEvent {
final ProductModel product;
const AddToCart(this.product);
#override
List<Object> get props => [product];
}
class RemoveFromCart extends CartEvent {
final ProductModel product;
const RemoveFromCart(this.product);
#override
List<Object> get props => [product];
}
Cart Bloc
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc() : super(CartInitial());
#override
Stream<CartState> mapEventToState(CartEvent event) async* {
yield CartLoading();
if(event is FetchCart){
final List<ProductModel> carts = List.from((state as CartSuccess).carts);
yield CartSuccess(carts);
}
if(event is AddToCart){
try{
final List<ProductModel> carts = List.from((state as CartSuccess).carts)
..add(event.product);
yield CartSuccess(carts);
}catch(e){
yield CartFailed();
print(e);
}
}
if(event is RemoveFromCart){
try{
final carts = (state as CartSuccess)
.carts
.where((product) => product.id != event.product.id)
.toList();
yield CartSuccess(carts);
}catch(e){
yield CartFailed();
print(e);
}
}
}
}
I guess this comes from here: List.from((state as CartSuccess).carts). You yield yield CartLoading();before this, so your state is no longer CartSuccess but CartLoading. You previous CartSuccess state was lost with yielding CartLoading
i am calling this method(sendOtp) from another class but getting error: the method "sendOtp" isn't defined for class 'LoginPage'
it is a phone authentication loginpage method to sendOtp
class LoginPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return LoginPageState();
}
}
class LoginPageState extends State<LoginPage> {
Future<void> sendOtp() async {
final PhoneVerificationCompleted phoneVerificationCompleted =
(AuthCredential credential) {};
final PhoneVerificationFailed phoneVerificationFailed =
(AuthException exception) {
print("Login Faild due to $exception");
};
final PhoneCodeSent phoneCodeSent =
(String verificationId, [int forceResendingToken]) {
this.verificationId = verificationId;
};
final PhoneCodeAutoRetrievalTimeout phoneCodeAutoRetrievalTimeout =
(String verificationId) {
this.verificationId = verificationId;
print("time out");
};
await _auth.verifyPhoneNumber(
phoneNumber: _numberController.text,
timeout: Duration(seconds: 120),
verificationCompleted: phoneVerificationCompleted,
verificationFailed: phoneVerificationFailed,
codeSent: phoneCodeSent,
codeAutoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
}
----------------ANOTHER CLASS-------------------
class OtpScreen extends StatefulWidget {
final String _name,_number, _id;
OtpScreen(this._name, this._number, this._id);
#override
OtpScreenState createState() {
return OtpScreenState();
}
}
class OtpScreenState extends State<OtpScreen> {
final TextEditingController _otpController = TextEditingController();
var _auth = FirebaseAuth.instance;
LoginPage loginPage = LoginPage();
Widget resend() {
return FlatButton(
child: Text("Send Otp again"),
onPressed: () => loginPage.sendOtp(), //ERROR IS HERE: sendOtp isn't defined for class LoginPage
);
}
}
You can't just call a method of another class without any reference. If the method is in the parent widget, you can pass it down when calling the child widget. If the method is in the child, you can pass a GlobalKey from the parent to the child, add that key to the child, and then through the key call the methods of the child.
If the two widgets have no relation what-so-ever, then you would benefit from using an InheritedWidget or a State Management solution that allows you to pass methods around in your app.