I am trying to create a fetchUserOrders() method in an OrderCubit. To do so, I need the userId which is held in the OrderState of the OrderCubit.
Both Cubits are setup in a MultiBlocProvider in main.dart.
How do I make a call like:
Future<void> fetchOrders() async {
emit(OrderState.loading());
final uid = context.read<AuthCubit>().state.uid;
final orders = await OrderRepo().getUserOrders(userId: uid);
emit(OrderState.loaded(orders));
}
within the OrderCubit?
I don't know of a way to get a context within a Cubit, so the above is not an option thus far. I have looked at using BlocListener, but as I understand it that only emits a new state in response to a change from AuthCubit - which is not refreshing a new value.
I appreciate any insight on best practice for reading values from one Cubit to another.
Related
So this is a Flutter application using Dart language with the Riverpod package for state management. The intent is that an application has a user if, they are signed in, and is set to null if they are not (or signed out). I found that setting the user to null did not notify the listeners, so I tried with a basic nullable String field, called name. I received the same result.
Example below is for the simple nullable String field. Another thing is, I use the standard data class generator plugin to generate all the boiler plate code such as equality, copywith, hashcode and so on.
So let's assume I have the following using flutter_riverpod: 2.1.3
class AppSettings {
User? user;
String? name;
AppSettings({
this.user,
this.name,
});
AppSettings copyWith({
User? user,
String? name,
}) {
return AppSettings(
user: user ?? this.user,
name: name ?? this.name,
);
}
// Additional data class generator methods
}
class AppSettingsNotifier extends StateNotifier<AppSettings> {
AppSettingsNotifier() : super(AppSettings());
void updateUser(User? user) {
state = state.copyWith(user: user);
}
void updateName(String? name) {
state = state.copyWith(name: name);
}
}
final appSettingProvider =
StateNotifierProvider<AppSettingsNotifier, AppSettings>(
(ref) => AppSettingsNotifier());
Then when I set the name field as follows:
ref.read(appSettingProvider.notifier).updateName(null);
Then my listeners aren't reacting and the widgets aren't rebuilt.
On the other hand, if I set the name to an actual string:
ref.read(appSettingProvider.notifier).updateName("Bruce Lee");
It would instantly update. Even with empty string it will also notify the listeners. So it seems something special is happening with null specifically.
Why is "null" not causing a notification to listeners?
What am I missing here?
I've tried reading the manual, googling and countless attempts at debugging. Unfortunately, I do not understand enough of the underlying Riverpod/Flutter code to get to the bottom of it.
By looking at the provided code, So far what I can see a potential issue in your copyWith method. Bcz in flutter, when using a copyWith method the common convention is to keep the instances past data if the data given inside the copyWith is null.
Right now, it looks like that can be the issue here. So when you pass a null data, The new instance comes with the name value from your past object. So try to check if it's the case in your AppSettings model class.
In my Flutter app I am using two providers to manage a list display...
a 'goalListFutureProvider ' Future provider that retrieves the list of entries to display
a 'goalCrudProvider' StateNotifier provider that handles add/edit/delete actions on entries in the list
The code for the list provider is shown below, where state changes in the CRUD provider will trigger a refresh of the future list provider
final goalListFutureProvider = FutureProvider<List<GdGoal>>((ref) {
ref.watch(goalSchedulingProvider);
ref.watch(goalCrudProvider);
final aSearch = ref.watch(goalListSearchProvider);
return ref.watch(goalServiceProvider).find(aSearch);
});
Within my CRUD provider I am calling a state change as part of the process logic, the 'update' code is shown below..
class GdGoalNotifier extends StateNotifier<bool> {
Future<bool> update(GdGoal aGoal) async {
final aRes = await _goalService.update(aGoal);
state = !state;
return aRes;
}
Within the 'update' method is it correct to call 'state = !state' before the end of the function call? Or should I break out this state refresh to a separate CRUD provider method?
The way I structured my firebase firestore and storage is that once I get a uid of an item in firestore, I take that id to my storage and retrieve a list of downloadUrls to display images. so retrieving the downloadUrls will depend on the uid, two of which are async calls with a dependency.
All of this is for purpose of displaying a very standard card widget with a picture and description at the bottom.
In my FutureBuilder future parameter, I tried using Future.wait api but then I don't know how I can pass the information retrieved from one future onto the next future.
Future.wait is not meant for this purpose. It's meant for independent futures. If you have two futures and one needs the return value of the other to start, wrap them both into one future:
//First future for uid
Future<String> uid() async {
return id;
}
//Second future for URL
Future<String> downloadURL(String uid) async {
//Use the uid and get the download URL future
}
//Wrapper to combine them
Future<String> combined() async {
final uid = await uid();
return downloadUrl(uid);
}
Then you can pass the combined future to your FutureBuilder.
I'm new to flutter and I have experience in web application using state managements like Redux or Vuex where the initial state of a module might be something like:
{
value1: 0,
value2: 10,
aBool: false,
aString: 'Hello'
}
Then based on Reducers or Mutations we can update a single or multiple properties of the state.
Now, learning Flutter I decided to use Bloc/Cubit and online I cannot find the right answer to my problem, even because the majority of the example are always based on the crappy counter app and never on a more realistic scenario.
All I can see is something based on 4 states in Bloc: initial, loading, success and error.
This is fine when fetching data from an API, but what if my state has also more properties?
how to update those properties?
Actually I created my test Cubit to fetch something from my API, it works. Now I wish to add more properties on the state and update it based on actions, how can I do that?
Example state:
#freezed
abstract class TestState with _$TestState {
const factory TestState.initial() = _Initial;
const factory TestState.loading() = _Loading;
const factory TestState.success(UserData user) = _Success;
const factory TestState.error(String message) = _Error;
}
Example Cubit:
class TestCubit extends Cubit<TestCubit> {
TestCubit(this._testClient)
: super(TestState.initial());
final TestClient _testClient;
String greet = 'Hi';
Future<void> testFetchData() async {
...
emit(TestState.success(testData));
...
}
}
I can successfully handle the varioud initial, loading, etc... states.
I can correctly watch at the greet property: context.read<TestCubit>().greet
How should I now update that value with 'hello!'?
// TestCubit
updateGreet(String text) {
emit(I don't know);
}
I omitted all my various tries to update that value.
Thanks
I use that structure for getting data from blocs.
class CheckOutBloc extends Bloc<CheckOutEvent, CheckOutState> {
CheckOutBloc(
{#required this.orderRepository,
#required this.addressBloc,
#required this.cartBloc})
: super(null);
final MyOrderRepository orderRepository;
final AddressBloc addressBloc;
final CartBloc cartBloc;
#override
Stream<CheckOutState> mapEventToState(
CheckOutEvent event,
) async* {
if (event is CreateOrder) {
try {
yield CheckOutInProgress();
final address = (addressBloc.state as AddressLoadSuccess).getPrimary();
final items = (cartBloc.state as CartLoadSuccess).items;
final Map<String, dynamic> order = {
'items': items.map((e) => e.toJson()).toList(),
'addressUUID': address.uuid,
'description': '',
'storeUUID': items[0].uuid,
};
await orderRepository.createOrder(order);
yield CheckOutLoadSuccess();
} catch (_) {
yield CheckOutFailed(_?.toString());
}
}
}
}
However, I think, It is not better way to get data.
I think get data using stream of bloc like bloc.listiner(() => add()) or use repository provider where we send request to another bloc for getting data of other blocs. I can't decide which way is the best.
How you think which way is better or my option is good way.
Hard to tell from the info given. If you see all the data needed from addressBloc and from CartLoadSuccess in your ui, I would maybe send the data via the event.
It also depends on the complexity of your app and if complexity increase and data store changes in the future are likely. If not, you may also think of a repository shared by different blocs. On the other hand, if costs from requests and network traffic play a role, I guess I would use the event or your approach shown (though you may have to catch situations where the other bloc has an unexpected state (still in progress or with error)