What am I doing wrong here that my state in a Bloc Pattern changes only one time then mapEventToState doesn't react to BlocProvider.of<CounterBloc>(context).add(ActiveEvent()); request?
I am trying to get into the way of things with the Bloc Pattern but when I switch state in the switcher on a counter page the state changes and after that, it doesn't update at all. It's like don't go any further from onChanged switch function.
I guess the issue is in my stream subscription which is implemented in the CounterBloc contractor. Or I return the state incorrectly.
I would appreciate your help and if you explain to me the mistake.
my bloc
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterEvents.dart';
import 'package:practicing_bloc/blocs/counter/counterState.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
#override
CounterState get initialState => Active(active: true, count: 0);
CounterBloc() {
_counterStream = _counter.stream;
}
StreamController<CounterState> _counter = StreamController<CounterState>();
Stream<CounterState> _counterStream;
#override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
CounterState currentState = state;
print('currect: $currentState');
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
}
}
bloc state
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
abstract class CounterState extends Equatable {
final bool active;
final int count;
const CounterState({#required this.active, #required this.count});
#override
List<Object> get props => [active, count];
#override
String toString() => 'State { active : $active, count : $count }';
}
class Active extends CounterState {
const Active({#required bool active, #required int count})
: super(active: active, count: count);
}
class Inactive extends CounterState {
const Inactive({#required bool active, #required int count})
: super(active: active, count: count);
}
bloc Event
import 'package:equatable/equatable.dart';
abstract class CounterEvent extends Equatable {
const CounterEvent();
#override
List<Object> get props => [];
}
class Increase extends CounterEvent {}
class Decrease extends CounterEvent {}
class ActiveEvent extends CounterEvent {}
class InactiveEvent extends CounterEvent {}
counterPage
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterBloc.dart';
class CounterPage extends StatefulWidget {
#override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
bool stateActive = false;
#override
Widget build(BuildContext context) {
//ignore: close_sinks
dynamic counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Flutter Counter | Page title')),
body: SafeArea(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
String stateString = state.active ? 'Active' : 'Inactive';
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counter is : $stateString'),
Text('Current counter is : ${state.count}'),
Switch(
value: stateActive,
onChanged: (bool value) {
print(counterBloc.state);
setState(() {
stateActive = value;
});
CounterEvent newEvent =
value ? ActiveEvent() : InactiveEvent();
counterBloc.add(newEvent);
// print('BloC state: ${counterBloc.state.active} | switch state: ${state.active}');
},
)
],
),
);
},
),
),
);
}
}
Basically instead of yielding * _counterStream you need to yield the states in this i.e. Active or Inactive
Change this
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
to this
if (event is ActiveEvent) {
yield Inactive(active: false, count: currentState.count);
} else if (event is InactiveEvent) {
yield Active(active: true, count: currentState.count);
}
Related
I'm learning about Bloc and have a problem
How to change from loading state to loaded state?
My code here:
test.dart
Looking for help from everyone
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:helloapp/profile/bloc/popup/popup_bloc.dart';
class TestScreen extends StatelessWidget {
const TestScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PopupBloc(),
child: BlocBuilder<PopupBloc, PopupState>(
builder: (context, state) {
if (state is PopupLoadingState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.symmetric(vertical: 50),
child: CircularProgressIndicator()
),
],
);
}
if (state is PopupLoadedState) {
return Text(state.avatar + state.phone + state.name);
}
return Text('123');
},
),
);
}
}
popup_bloc.dart
Looking for help from everyone
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'popup_event.dart';
part 'popup_state.dart';
class PopupBloc extends Bloc<PopupEvent, PopupState> {
PopupBloc() : super(PopupLoadingState()) {
on<PopupEvent>((event, emit) {
emit(PopupLoadingState());
emit(PopupLoadedState(avatar: '123', name: 'name', phone: 'phone'));
});
}
}
popup_event.dart
part of 'popup_bloc.dart';
abstract class PopupEvent extends Equatable {
const PopupEvent();
#override
List<Object?> get props => [];
}
class PopupLoadingEvent extends PopupEvent{}
class PopupLoadedEvent extends PopupEvent{}
class PopupErrorEvent extends PopupEvent{}
popup_state.dart
Looking for help from everyone
part of 'popup_bloc.dart';
abstract class PopupState extends Equatable {
const PopupState();
}
class PopupLoadingState extends PopupState {
#override
// TODO: implement props
List<Object?> get props => [];
}
class PopupLoadedState extends PopupState {
final String avatar;
final String name;
final String phone;
PopupLoadedState({
required this.avatar,
required this.name,
required this.phone
});
#override
// TODO: implement props
List<Object?> get props => [avatar, name, phone];
}
class PopupErrorState extends PopupState {
#override
// TODO: implement props
List<Object?> get props => [];
}
try this
return BlocProvider(
create: (_) => PopupBloc()..add(PopupEvent.PopupLoadingEvent())..add(PopupEvent.PopupLoadedEvent()),
...
);
The event handlers should be registered for the specific event
class PopupBloc extends Bloc<PopupEvent, PopupState> {
PopupBloc() : super(PopupLoadingState()) {
on<PopupLoadingEvent>((event, emit) async {
emit(PopupLoadingState());
/// ... do some async operation and finally emit a loaded or error state
emit(PopupLoadedState(avatar: '123', name: 'name', phone: 'phone'));
});
}
}
And trigger this on the creation of the bloc
return BlocProvider(
create: (_) => PopupBloc()..add(PopupLoadingEvent()),
...
);
I wanted to make a timer based on the Internet cubit. when Internet Disconnected State is emitted timer should be reset. But after reset(when Internet Disconnect state emitted) it is emitting increment timer state again.
Home Cubit (timer cubit)
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(HomeState.initial());
void increment() {
Timer.periodic(const Duration(seconds: 1), (timer) {
emit(state.copyWith(counter: state.counter + 1));
});
}
void reset() {
emit(
state.copyWith(counter: 0),
);
}
}
Home State (timer state)
class HomeState extends Equatable {
final int counter;
const HomeState({
required this.counter,
});
factory HomeState.initial() {
return const HomeState(counter: 0);
}
HomeState copyWith({
int? counter,
}) {
return HomeState(
counter: counter ?? this.counter,
);
}
#override
List<Object?> get props => [counter];
}
Internet Cubit
class InternetCubit extends Cubit<InternetState> {
final Connectivity connectivity;
late StreamSubscription internetStreamSubscription;
InternetCubit({required this.connectivity}) : super(InternetInitial()) {
monitorInternet();
}
void monitorInternet() {
internetStreamSubscription = connectivity.onConnectivityChanged.listen((connectivityResult) {
if (connectivityResult == ConnectivityResult.wifi) {
emit(const InternetConnected(connectionType: ConnectionType.wifi));
} else if (connectivityResult == ConnectivityResult.mobile) {
emit(const InternetConnected(connectionType: ConnectionType.mobileData));
} else if (connectivityResult == ConnectivityResult.none) {
emit(InternetDisconnected());
}
});
}
#override
Future<void> close() {
internetStreamSubscription.cancel();
return super.close();
}
}
Internet State
enum ConnectionType { wifi, mobileData }
abstract class InternetState extends Equatable {
const InternetState();
#override
List<Object> get props => [];
}
class InternetInitial extends InternetState {}
class InternetConnected extends InternetState {
final ConnectionType connectionType;
const InternetConnected({required this.connectionType});
#override
List<Object> get props => [connectionType];
}
class InternetDisconnected extends InternetState {}
Homepage view
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
return BlocListener<InternetCubit, InternetState>(
listener: (context, state) {
if (state is InternetConnected) {
context.read<HomeCubit>().increment();
} else if (state is InternetDisconnected) {
context.read<HomeCubit>().reset();
}
},
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have been connected to the Internet for the past'),
Text(context.watch<HomeCubit>().state.counter.toString()),
],
),
),
),
);
}
}
I tried Cubit stream subscribe too. but why it is re-emitting old state again? why timer not staying in 0 or reset state?
If I understand you correctly you want the timer to stay 0 when you call reset() in HomeCubit().
Try this:
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(HomeState.initial());
Timer? timer;
void increment() {
timer ??= Timer.periodic(const Duration(seconds: 1), (timer) {
emit(state.copyWith(counter: state.counter + 1));
});
}
void reset() {
timer?.cancel();
timer = null;
emit(
state.copyWith(counter: 0),
);
}
}
I've just started with Flutter recently. BLoC does indeed has a steep learning curve...
As it is clear from the title, the BlocBuilder logic gets correctly executed only once, when the app is started. Afterwards, however, UI doesn't get rebuilt on state changes. Although all the events get emitted, and states change.
Thanks in advance for any help!
Here's my 'main.dart':
import 'package:co_flutter/auth/authentication_bloc.dart';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:co_flutter/auth/login/login_bloc.dart';
import 'package:co_flutter/auth/signup/signup_page.dart';
import 'package:co_flutter/loading_indicator.dart';
import 'package:co_flutter/splash_page.dart';
import 'package:co_flutter/user_repository.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth/login/login_page.dart';
class SimpleBlocObserver extends BlocObserver {
#override
void onCreate(BlocBase bloc) {
super.onCreate(bloc);
print('onCreate -- ${bloc.runtimeType}');
}
#override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
print('onEvent -- ${bloc.runtimeType}, $event');
}
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('onTransition -- ${bloc.runtimeType}, $transition');
}
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('onChange -- ${bloc.runtimeType}, $change');
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- ${bloc.runtimeType}, $error');
super.onError(bloc, error, stackTrace);
}
#override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- ${bloc.runtimeType}');
}
}
void main() {
Bloc.observer = SimpleBlocObserver();
runApp(MyApp(
userRepository: UserRepository(),
));
}
class MyApp extends StatefulWidget {
final UserRepository userRepository;
MyApp({Key? key, required this.userRepository}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late AuthenticationBloc authenticationBloc;
UserRepository get userRepository => widget.userRepository;
#override
void initState() {
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
authenticationBloc.add(AppStarted());
super.initState();
}
#override
void dispose() {
authenticationBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
BlocProvider<LoginBloc>(
create: (BuildContext context) => LoginBloc(
userRepository: userRepository,
authenticationBloc: authenticationBloc),
),
],
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (BuildContext context, AuthenticationState state) {
if (state is AuthenticationUninitialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return Dashboard(
title: 'Dashboard',
);
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(
userRepository: userRepository,
);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
// else {
// return Text('Error');
// }
return BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
if (state is LoginToSignup) {
return SignUpPage();
} else
return SizedBox.shrink();
},
);
},
),
),
);
}
}
class Dashboard extends StatelessWidget {
final String title;
const Dashboard({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
final AuthenticationBloc authenticationBloc =
BlocProvider.of<AuthenticationBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('Dashboard'),
),
body: Container(
child: Center(
child: ElevatedButton(
child: Text('logout'),
onPressed: () {
authenticationBloc.add(LoggedOut());
},
),
),
),
);
}
}
authentication_bloc
import 'dart:async';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:bloc/bloc.dart';
import '../user_repository.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
UserRepository userRepository;
AuthenticationBloc({required this.userRepository})
: super(AuthenticationUninitialized()) {
userRepository = UserRepository();
}
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(event.token, event.userId);
yield AuthenticationAuthenticated();
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
authentication_event
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
#override
List<Object> get props => [];
}
class AppStarted extends AuthenticationEvent {
#override
String toString() => 'AppStarted';
}
class LoggedIn extends AuthenticationEvent {
final String token;
final String userId;
LoggedIn({required this.token, required this.userId});
#override
String toString() => 'LoggedIn { token: $token}';
}
class LoggedOut extends AuthenticationEvent {
#override
String toString() => 'LoggedOut';
}
authentication_state
import 'package:equatable/equatable.dart';
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
}
class AuthenticationUninitialized extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationLoading extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationAuthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationUnauthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
The problem is here. You already created a bloc:
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
And here you are trying to create it again.
To fix it replace this code:
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
with this:
BlocProvider<AuthenticationBloc>.value(
value: authenticationBloc,
),
Pass bloc into bloc builder
BlocBuilder<AuthenticationBloc, AuthenticationState>(
bloc: authenticationBloc,
Don't forget to dispose bloc authenticationBloc after you used it in dispose function.
I'm trying to execute Bloc more times than one, however I don't know how to handle that.
When I click search, it shows circular progress indicator and then the "Sukces" text, but when i click it for the second time, the indicator doesn't show.
How to fix it?
Link to the video of my app: https://streamable.com/77jyf1
My code:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:jaki_to_bank/screens/home/bloc/search_bloc.dart';
import 'package:provider/provider.dart';
import 'package:jaki_to_bank/generated/l10n.dart';
import 'package:jaki_to_bank/screens/home/widgets/search_button.dart';
import 'package:jaki_to_bank/screens/home/widgets/search_text_field.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _formKey = GlobalKey<FormState>();
final SearchBloc _bloc = SearchBloc();
final TextEditingController _bankAccountController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.current.title),
),
body: Container(
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
SearchTextField(
controller: _bankAccountController,
validator: (text) {
if (text == null || text.isEmpty) {
return S.current.emptyFieldError;
}
return null;
},
),
SizedBox(height: 16.0),
SearchButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
final validationBloc = context.read<SearchBloc>();
validationBloc.add(GetText(_bankAccountController.text));
}
},
),
BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
if (state is SearchInitial) {
return Container();
}
if (state is SearchLoading) {
return CircularProgressIndicator();
}
if (state is SearchFinal) {
return Text('Sukces!');
}
return Container();
},
)
],
),
),
),
);
}
}
part of 'search_bloc.dart';
#immutable
abstract class SearchEvent {}
class GetText extends SearchEvent {
final String bankAccountNumber;
GetText(this.bankAccountNumber);
}
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:jaki_to_bank/data/repositories/bank_repository.dart';
import 'package:meta/meta.dart';
import 'package:xml/xml.dart';
part 'search_event.dart';
part 'search_state.dart';
class SearchBloc extends Bloc<SearchEvent, SearchState> {
SearchBloc() : super(SearchInitial());
#override
Stream<SearchState> mapEventToState(
SearchEvent event,
) async* {
if (event is GetText) {
yield SearchLoading();
final String bankAccountNumber = event.bankAccountNumber;
final String bankIdNumber = bankAccountNumber.substring(2, 5);
final BankRepository bankRepo = BankRepository();
final List<String> bankIdsList = <String>[];
final XmlDocument banks = await bankRepo.getParsedXmlText();
final Iterable<XmlElement> bankIds =
banks.findAllElements('NrInstytucji');
bankIds.map((node) => node.text).forEach((element) {
print(element);
});
yield SearchFinal();
}
}
}
part of 'search_bloc.dart';
#immutable
abstract class SearchState {}
class SearchInitial extends SearchState {
SearchInitial();
}
class SearchLoading extends SearchState {
SearchLoading();
}
class SearchFinal extends SearchState {
SearchFinal();
}
I'm facing a problem in BlocLibrary(https://bloclibrary.dev/)
I need to receive same state in BlocListner/BlocBuilder, let me explain in code:
This is Bloc(for little explain):
class CounterBloc extends Bloc<CounterEvent, AppState> {
#override
AppState get initialState => InititalState();
#override
Stream<AppState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.getState:
yield Counter(value: 0);
break;
}
HERE IS STATE CLASS:
import 'package:flutter_bloc/flutter_bloc.dart';
enum StateEvent { getState }
abstract class AppState extends Equatable {
const AppState();
#override
List<Object> get props => [];
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
List<Object> get props => [count];
#override
String toString() => 'Counter { count: $count }';
}
Here you go for my bloc listener/builder:
BlocListener<CounterBloc, AppState>(
listener: (context, state) {
if (state is Counter) {
**Here I needed same state, means if I press getState, it should print 0 everytime**
print(state. value);
}
},
child: BlocBuilder<CounterBloc, AppState>(
builder: (context, state) {
return Center(
child: Text(
'${value}',
style: TextStyle(fontSize: 24.0),
),
);
},
),
)
Change your AppState, you can read more about Equatable usage here. This post is mostly a duplication of this.
You should not use Equatable if you want the same state back-to-back to trigger multiple transitions.
abstract class AppState {
const AppState();
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
String toString() => 'Increment { count: $count }';
}