I'm using flutter_bloc library to manage the state of the widgets. I'm trying to update a variable in the state that is a List. When I capture the state and modify the desired value, then yield the new state, the state doesn't update, but the clone of the state is changed.
Stream<ItemState> _updateRestrictions(ItemUpdateRestrictionValue event) async * {
if (state is ItemLoaded) {
final restrictions = (state as ItemLoaded).restrictions;
final newState = restrictions.map((restriction) {
if (restriction.id == event.restrictionId) {
return ItemRestriction(
id: restriction.id,
name: restriction.name,
restrictionType: restriction.restrictionType,
restrictionValues: restriction.restrictionValues,
restrictionValue: event.restrictionValueId
);
}
return restriction;
}).toList();
yield ItemLoaded(restrictions: newState);
}
}
Am I doing something wrong? Or how do you update the state using flutter_bloc correctly?
That probably happens because you use Equatable on your ItemState class and yield the same state back-to-back which is IteamLoaded().
You may wanna read this https://bloclibrary.dev/#/faqs?id=when-to-use-equatable
Related
I want to keep my return value as AsyncValue rather than Stream so I am returning StreamProvider from build method of Notifier. After reading the codebase of riverpod I can't see any drawback of this, but I have never come across any project doing something like this. Is this fine, or is there any straight forward way to convert Stream to AsyncValue.
final _userProvider = StreamProvider.autoDispose<User?>((ref) {
final repository = ref.watch(repositoryProvider);
return repository.getUser(); //returns Stream<User?>
});
class AuthNotifier extends AutoDisposeNotifier<AsyncValue<User?>> {
#override
AsyncValue<User?> build() {
return ref.watch(_userProvider);
}
Future<void> singOut() {
return ref.read(repositoryProvider).signOut();
}
}
final authProvider =
AutoDisposeNotifierProvider<AuthNotifier, AsyncValue<User?>>(
AuthNotifier.new);
This is fine, yes.
Being able to do such a thing is the goal of the build method & ref.watch
As long as you don't return the provider itself but the value exposed by the provider, there is no problem:
build() {
return ref.watch(provider); // OK
}
build() {
return provider // KO
}
I am using riverpod for my state manegement in my flutter app.
Riverpod offers a feature for combined providers, but my dependent provider does not update and always returns null.
By clicking one of the pins (secrets) on the map, my "selectedSecretProvider" is updated (default is null). This should trigger the initialization of my audio player. And by clicking play, the sound of the current _selectedSecret should play. So my "selectedTrackProvder" is dependent on my "selectedSecretProvider":
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
Secret? selectedSecret = ref.watch(selectedSecretProvider);
return SelectedTrack(selectedSecret);
});
Here is my selectedTrack class:
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.selectedSecret) : super(Track.initial());
Secret? selectedSecret;
#override
void dispose() {
...
}
void initAudioPlayer() {
...
}
Future<int> play() async {
print(selectedSecret);
return ...
}
}
So why does it always print null, when clicking play?
(Btw. my bottom_panel_sheet is showing the correct data and also consumes the "selectedSecretProvider".)
I wouldn't say the way you're creating your StateNotifierProvider is wrong, but I think the following is a better approach that should solve your problem.
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
return SelectedTrack(ref);
});
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.ref) : super(Track.initial());
final ProviderReference ref;
Future<int> play() async {
final selectedSecret = ref.read(selectedSecretProvider);
print(selectedSecret);
return ...
}
}
This way you don't create a new StateNotifier every time the selectedSecretProvider updates, instead opting to read the current value of the selectedSecretProvider when attempting to call play.
I wanna check users internet connection and firebase auth state changes in my app. I am using flutter bloc for my app's state management. But when call different 2 .add(event) in one initstate always the first one is run and changes states but second one didnt run didnt change state. What is the my wrong ?
my bloc:
class ControllerBloc extends Bloc<ControllerEvent, ControllerState> {
ControllerBloc() : super(ControllerInitial());
AuthApiClient _authApiClient = getIt<AuthApiClient>();
#override
Stream<ControllerState> mapEventToState(
ControllerEvent event,
) async* {
if (event is ControllInternetConnection) {
yield* internetControll();
}
if (event is ControllUserAuth) {
debugPrint("wwwwgeldi");
yield* userAuthControl();
}
// TODO: implement mapEventToState
}
Stream<ControllerState> internetControll() async* {
Stream<DataConnectionStatus> connectionState =
DataConnectionChecker().onStatusChange;
await for (DataConnectionStatus status in connectionState) {
switch (status) {
case DataConnectionStatus.connected:
debugPrint("Bağlandı");
yield InternetConnectedState();
break;
case DataConnectionStatus.disconnected:
debugPrint("Kesildi");
yield InternetConnectionLostState();
break;
}
}
}
Stream<ControllerState> userAuthControl() async* {
FirebaseAuth firebaseAuth = _authApiClient.authInstanceAl();
debugPrint("geldi");
Stream<User> authStream = firebaseAuth.authStateChanges();
_authApiClient.authInstanceAl().signOut();
await for (User authUserResult in authStream) {
if (authUserResult == null) {
yield UserAuthControlError();
}
}
}
}
my page where call my events
class _NavigationPageState extends State<NavigationPage> {
ControllerBloc controllerBloc;
#override
void initState() {
controllerBloc= BlocProvider.of<ControllerBloc>(context);
controllerBloc.add(ControllInternetConnection());
controllerBloc.add(ControllUserAuth());
super.initState();
}
If I am understanding this right, it looks to me that you are trying to solve two different problems with one BLoC. I don't see a reason why internet connection and user auth have to be in one BLoC, rather I would just separate the two in separate BLoCs.
As the discussion in this thread points out, the point of using a BLoC revolves around the idea of predictability purposes. You can override the existing BLoC event stream, but I personally think that is too complicated for what you are trying to do.
So I would suggest, either make two separate BLoCs or combine the entire process into one event, where the internet connection would be checked before the user is authenticated, you will then return different states depending on the errors.
I yield same state but with different object in my bloc but BlocBuilder not called again.
How I can do this scenario ?
My mapEventToState is
if (event is EditUserProfileImageChanged) {
UserProfile newUserProfile = state.userProfile;
newUserProfile.avatar = event.imgSrc;
yield EditUserProfileTotalState(userProfile: newUserProfile);
}
When we yield a state in the private mapEventToState handlers, we are always yielding a new state instead of mutating the state. This is because every time we yield, bloc will compare the state to the nextState and will only trigger a state change (transition) if the two states are not equal. If we just mutate and yield the same instance of state, then state == nextState would evaluate to true and no state change would occur.
If you want to change the value of the state, make a copyWith function for your model class.
class UserProfile extends Equatable {
final String name;
final Image avatar;
const UserProfile({this.name, this.avatar});
UserProfile copyWith({String name, Image avatar,}) {
return UserProfile(
name: name ?? this.name,
avatar: avatar?? this.avatar,
);
}
#override
List<Object> get props => [name, avatar];
}
if (event is EditUserProfileImageChanged) {
var newState = state.userProfile.copyWith(avatar: event.imgSrc);
yield EditUserProfileTotalState(userProfile: newState);
}
Removing Equatable solve the problem.
Removing equatbale rebuilds every time even values of the properties are not changed. Instead create new state instance every time.
I am trying to create a BLOC which depends on two other time based bloc and a non-time based bloc. What i mean with time based is, for example they are connecting a remote server so it takes time. It's working just like this:
Login (It's of course taking some time)
If login is successful
Do another process (This is something takes time also. It returns a future.)
After login and another process finishes, let the page know it.
My BLOC depends on these three:
final UserBloc _userBloc;
final AnotherBloc _anotherBloc;
final FinishBloc _finishBloc;
Inside the map event to state method I should dispatch relevant events. However i cannot await if they are finished.
_userBloc.dispatch(
Login(),
);
_anotherBloc.dispatch(
AnotherProcess(),
);
//LetThePageKnowIt should work after login and another process
_finishBloc.dispatch(
LetThePageKnowIt(),
);
Is there a clean way to await some others before dispatching something?
Right know I use a way that i don't like. In the main bloc's state which i connect other blocs in it, I have bools.
class CombinerState {
bool isLoginFinished = false;
bool isAnotherProcessFinished = false;
I am listening the time dependent blocs' states in constructor of main bloc. When they yield "i am finished" I just mark the bools "true".
MainBloc(
this._userBloc,
this._anotherBloc,
this._pageBloc,
); {
_userBloc.state.listen(
(state) {
if (state.status == Status.finished) {
dispatch(FinishLogin());
}
},
);
_anotherBloc.state.listen(
(state) {
if (state.status == AnotherStatus.finished) {
dispatch(FinishAnotherProcess());
}
},
);
}
and I dispatch another event for main bloc to check if all the bools are true after setting a bool to true.
else if (event is FinishAnotherProcess) {
newState.isAnotherProcessFinished = true;
yield newState;
dispatch(CheckIfReady());
}
If the bools are true, i dispatch LetThePageKnowIt()
else if (event is CheckIfReady) {
if (currentState.isAnotherProcessFinished == true &&
currentState.isLoginFinished == true) {
_pageBloc.dispatch(LetThePageKnowIt());
}
}
I am not satisfied with this code. I am looking a way to await other BLOCs send a state with "finished". After that I want to dispatch my LetThePageKnowIt()
#pskink 's suggestion solved my problem.
I have created two methods which return a future. Inside of them, I just await for my streams. Here is the example of login stream.
In map event to state, after the dispatches, I await an async method.
_userBloc.dispatch(
Login(),
);
_anotherBloc.dispatch(
AnotherProcess(),
);
await loginProcess();
await otherProcess();
_finishBloc.dispatch(
LetThePageKnowIt(),
);
Inside the method I just await for userbloc to finish its job and yields about it. Then return.
Future loginProcess() async {
await for (var result in _userBloc.state) {
if (result.status == Status.finished) {
return;
}
}
}