How can I use variable of CubitState inside Cubit? Flutter/Bloc - flutter

so I don't have any idea how to take argument from mine Cubit state which is AnswerPicked in this case, there is a code from states file.
part of 'answer_cubit.dart';
abstract class AnswerState extends Equatable {
const AnswerState();
#override
List<Object> get props => [];
}
class AnswerInitial extends AnswerState {}
class AnswerPicked extends AnswerState {
final String answer;
AnswerPicked({
this.answer,
});
String toString() => '{AnswerPicked: $answer}';
}
I want to use it in Cubit function right there:
part 'answer_state.dart';
class AnswerCubit extends Cubit<AnswerState> {
final ExamScoreCubit scoreCubit;
AnswerCubit({
#required this.scoreCubit,
}) : super(AnswerInitial());
List<String> userAnswersList = [];
void pickAnswer(String answer) {
emit(AnswerInitial());
emit(AnswerPicked(answer: answer));
}
void takeAnswer(String questionAnswer, int type) {
if(state is AnswerPicked){
userAnswersList.add(state.answer); // state.answer don't work
scoreCubit.checkAnswer(AnswerPicked().answer, questionAnswer, type); // AnswerPicked().answer don't work
}
emit(AnswerInitial());
}
}
In void takeAnswer() I don't want to pass it throw argument inside the widget tree using context. Any ideas how to do it?

userAnswersList.add((state as AnswerPicked) .answer);

Related

How do I update a Map with BLoC and equatable?

I am still a beginner with BLoC architecture. So far the UI updates when using int, bool, and other basic data types. But when it comes to Maps it really confuses me. My code basically looks like this:
my state
enum TalentStatus { initial, loading, loaded, error }
class TalentState extends Equatable {
const TalentState({
required this.talentStatus,
this.selectedService = const {},
required this.talents,
this.test = 0,
});
final TalentStatus talentStatus;
final Talents talents;
final Map<String, Service> selectedService;
final int test;
TalentState copyWith({
TalentStatus? talentStatus,
Talents? talents,
Map<String, Service>? selectedService,
int? test,
}) =>
TalentState(
selectedService: selectedService ?? this.selectedService,
talentStatus: talentStatus ?? this.talentStatus,
talents: talents ?? this.talents,
test: test ?? this.test,
);
#override
List<Object> get props => [talentStatus, talents, selectedService, test];
}
my event
abstract class TalentEvent extends Equatable {
const TalentEvent();
#override
List<Object> get props => [];
}
class TalentStarted extends TalentEvent {}
class TalentSelectService extends TalentEvent {
const TalentSelectService(
this.service,
this.talentName,
);
final Service service;
final String talentName;
}
and my bloc
class TalentBloc extends Bloc<TalentEvent, TalentState> {
TalentBloc(this._talentRepository)
: super(TalentState(
talentStatus: TalentStatus.initial, talents: Talents())) {
on<TalentSelectService>(_selectService);
}
final TalentRepository _talentRepository;
Future<void> _selectService(
TalentSelectService event,
Emitter<TalentState> emit,
) async {
state.selectedService[event.talentName] = event.service;
final selectedService = Map<String, Service>.of(state.selectedService);
emit(
state.copyWith(
selectedService: selectedService,
),
);
}
}
whenever an event TalentSelectService is called BlocBuilder doesn't trigger, what's wrong with my code?
Your Service object must be comparable. One suggestion is that it extends Equatable. Either way it have to implement (override) the == operator and hashCode
The reason your BlocBuilder doesn't trigger is (probably) that it doesn't recognize that there has been a change in the Map.

Access state from ui without an if statement

am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.

Issue calling a variable in the state class in Flutter

I want to trigger a boolean variable 'runBackdropBlur' in the state class as per below code.
To do this I want to be able to call the method turnOnBackdropBlur() (from another widget), and this method in turn, when called will change this variable in it's state class by use of global key.
I have been following this tutorial, to achieve a simple state management solution for this case:
tutorial
However, I run into these two errors in flutter, i cannot fix...
"Named parameters must be enclosed in curly braces ('{' and '}')."
"The default value of an optional parameter must be constant."
class Backdrop extends StatefulWidget {
Backdrop(key : _myKey);
GlobalKey<_BackdropState> _myKey = GlobalKey<_BackdropState>();
void turnOnBackdropBlur() {
_myKey.currentState!.runBackdropBlur = true;
}
#override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop> {
bool runBackdropBlur = false;
//etc
With some tricks you can do everything you want. You can give a pointer of a method to the owner widget as follows:
import 'dart:developer';
import 'package:flutter/cupertino.dart';
class Backdrop extends StatefulWidget {
final Map<String, Function> listener = new Map<String, Function>();
void setnBackdropBlurTrue() => listener.containsKey("BackdropBlurTrue")
? listener["BackdropBlurTrue"].call()
: log("BackdropBlurTrue key is null");
void setnBackdropBlurFalse() => listener.containsKey("BackdropBlurFalse")
? listener["BackdropBlurFalse"].call()
: log("BackdropBlurFalse key is null");
#override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop> {
bool runBackdropBlur = false;
void setRunBackdropBlurTrue() => runBackdropBlur = true;
void setRunBackdropBlurFalse() => runBackdropBlur = false;
#override
Widget build(BuildContext context) {
return Container();
}
_BackdropState() {
widget.listener.putIfAbsent("BackdropBlurTrue", () => setRunBackdropBlurTrue);
widget.listener.putIfAbsent("BackdropBlurFalse", () => setRunBackdropBlurFalse);
}
}

Flutter Bloc Equatable state with one mutual property across all states classes

I am trying to figure out the best way to define my Search Bloc's State to preserve text property (search key) across all state classes.
Currently, it looks like this:
import 'package:equatable/equatable.dart';
import 'package:project/models/searchResults.dart';
class SearchState extends Equatable {
SearchState([List props = const []]) : super(props);
}
class SearchStateEmpty extends SearchState {
final String text;
SearchStateEmpty({this.text});
#override
String toString() => 'SearchStateEmpty';
}
class SearchStateLoading extends SearchState {
final String text;
SearchStateLoading({this.text});
#override
String toString() => 'SearchStateLoading';
}
class SearchStateSuccess extends SearchState {
final String text;
final List<RestaurantSearchItem> items;
SearchStateSuccess({this.text, this.items}) : super([text, items]);
#override
String toString() => 'SearchStateSuccess { items: ${items.length} }';
}
class SearchStateError extends SearchState {
final String text;
final String error;
SearchStateError({this.text, this.error}) : super([text, error]);
#override
String toString() => 'SearchStateError';
}
Is there a better way of using text property than defining it throughout all state classes?
This would not be as bad as it is now if I wouldn't have to use currentState property every time an event does not have it. For example:
SearchStateEmpty(text: currentState.text);
...
SearchStateLoading(text: event.text);
...
SearchStateSuccess(text: currentState.text, items: results.items);
I was looking for examples in Flutter docs but all I was able to find out was that I should either use different blocs for it or ditch equatable (which I want to keep since it's pretty nice to have).
Any suggestions with examples would be highly appreciated. Thanks.
The way I was doing it was sort of antipattern. With some help, I was able to find a more clean way to use the current state in my state classes.
Code now looks like this:
class SearchState extends Equatable {
final String text;
SearchState(this.text,
[List<RestaurantSearchItem> items = const [], String error = ''])
: super([text, items, error]);
}
class SearchStateEmpty extends SearchState {
SearchStateEmpty({String text})
: super(text); // Here I want to use text from SearchState
SearchStateEmpty.fromState(SearchState state) : super(state.text);
#override
String toString() => 'SearchStateEmpty';
}
class SearchStateLoading extends SearchState {
final String text;
SearchStateLoading({this.text})
: super(text); // Here I want to set text that comes from event
#override
String toString() => 'SearchStateLoading';
}
class SearchStateError extends SearchState {
final String error;
SearchStateError({this.error, String text}) : super(text, [], error);
// Text comes from SearchState, error comes from event
SearchStateError.fromState(SearchState state, {this.error})
: super(state.text, [], error);
#override
String toString() => 'SearchStateError';
}
class SearchStateSuccess extends SearchState {
final List<RestaurantSearchItem> items;
SearchStateSuccess({this.items, String text}) : super(text, items, null);
SearchStateSuccess.fromState(SearchState state, {this.items})
: super(state.text, items, null);
#override
String toString() => 'SearchStateSuccess { items: ${items.length} }';
}

Using argument in initializer list with flutter

I want to make a data list page. It needs a reference based on user id passed by argument. However, the argument can't be accessed because it is not static.
I tried to pass reference itself by argument. But it also falls into the same problem.
class DataViewHome extends StatefulWidget{
final String userId;
DataViewHome({this.userId});
#override
State<StatefulWidget> createState() => DataViewHomeState();
}
class DataViewHomeState extends State<DataViewHome>{
final dataReference = FirebaseDatabase.instance.reference().child("users").child(widget.userId);
List<String> dataList = new List();
StreamSubscription<Event> _onDataAddedSubscription;
DataViewHomeState(){
_onDataAddedSubscription = dataReference.onChildAdded.listen(_onDataAdded);
}
_onDataAdded(Event event){
setState(() {
dataList.add(event.snapshot.value);
});
}
}