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)
Related
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.
I'm trying to figure out if my updated code is the correct way to use a factory constructor with null safety. I reviewed stackoverflow and the Dart.dev language tour to try to better understand factory constructors. I have struggled to apply the concepts outlined to my code. I'm new to Flutter, Dart and coding. This is my first attempt at using a factory constructor so the primary issue is my lack of understanding and not any issues with the answers on stackoverflow or elsewhere.
After reading a lot I settled on the approach in the code below marked as Updated. The errors are now all gone and my app is behaving as I want but my fear is throwing an error instead of returning null may not be a sound approach. My approach just looks wrong to my beginner eyes. My goal is for my code to work and to also understand why I am using whatever approach I am using so that I can apply that knowledge to future situations. I can provide any additional code that may be needed to comment. Thanks in advance for the help.
Original Code that throws an error
class Job {
Job({required this.name, required this.ratePerHour});
factory Job.fromMap(Map<String, dynamic>? data) {
if (data == null) {
return null;
}
final String name = data['name'];
final int ratePerHour = data['ratePerHour'];
return Job(name: name, ratePerHour: ratePerHour);
}
final String name;
final int ratePerHour;
Map<String, dynamic> toMap() {
return {
'name': name,
'ratePerHour': ratePerHour,
};
}
}
Updated code that works
class Job {
Job({required this.name, required this.ratePerHour});
factory Job.fromMap(Map<String, dynamic>? data) {
if (data != null) {
final String name = data['name'];
final int ratePerHour = data['ratePerHour'];
return Job(name: name, ratePerHour: ratePerHour);
} else {
throw ArgumentError('Data is null');
}
}
final String name;
final int ratePerHour;
Map<String, dynamic> toMap() {
return {
'name': name,
'ratePerHour': ratePerHour,
};
}
}
Your own solution works fine however, I would check data before calling the factory.
factory Job.fromMap(Map<String, dynamic> data) {
return Job(name: data['name'], ratePerHour: data['ratePerHour']);
}
But then again why not use data and call the regular constructor?
Job? job;
if (data != null) {
job = Job(name: data['name'], ratePerHour: data['ratePerHour'])
}
https://dart.dev/guides/language/language-tour#factory-constructors
Factory constructors
Use the factory keyword when implementing a
constructor that doesn’t always create a new instance of its class.
For example, a factory constructor might return an instance from a
cache, or it might return an instance of a subtype. Another use case
for factory constructors is initializing a final variable using logic
that can’t be handled in the initializer list.
You've pointed out several true and good facts, and I feel like that you're on the right way to implement this.
I also feel like there's no straight "right" answer to this question; I think this also connects to concepts as clean code and clean architecture, which are broader than Dart and Flutter themselves
You can either:
Throw and let the caller (upper layer) handle that problem;
Print some logs and return a zero-value to the caller (in your case, an "empty" object).
Case 1 is desirable if you don't want to handle cases like that one.
Case 2 is desirable if you can afford to return something weird like a Job("job name",0) and still be good.
It really depends on what you're building. By looking at your context, I'd probably go with option 1 and try/catch that in a middle layer (maybe you want to show your user "An error occured" whenever data is null)?
Nonetheless, you might need to refactor this feature in a way that allows you not to encounter these edge cases. There's a good chance dependency inversion is your friend, here.
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'm having a small issue converting this class using freezed since is not possible to have a default value which is not constant, so the line DateTime nocache= DateTime.now() is not possible to be transformed into #Default(DateTime.now()) DateTime nocache
Here the full code
import 'package:equatable/equatable.dart';
abstract class DynamicLinkState extends Equatable {
const DynamicLinkState();
#override
List<Object> get props => [];
}
class DynamicLinkInitial extends DynamicLinkState {
#override
String toString() => 'DynamicLinkInitial';
}
class DynamicLinkToNavigate extends DynamicLinkState {
final String path;
final DateTime nocache = DateTime.now();
DynamicLinkToNavigate({this.path});
#override
List<Object> get props => [path, nocache];
#override
String toString() => 'DynamicLinkToNavigate';
}
How can I eventually do that?
Additional context
I'm using a nocache attribute here because bloc is optimize to not send the same event multiple times, but this is a valid use case in this situation since i might expect the user to receive more then one time the same dynamic link. So the solution we found is simply to invalidate this optimization by passing an always changing nocache parameter.
So a valid solution to this question might also be to simply remove this workaround in favor of a more solid solution.
I ran into the same problem this morning. There's not a direct way that I could see to achieve this. However, a different approach to the problem may be in order.
Hopefully you are using a state management solution in your app. In my case it's RiverPod. So, rather than having the model know directly how to generate a default value for a property I elected to have my RiverPod state provider generate the value when creating the model.
So with a model like this...
#freezed class Pix with _$Pix {
const factory Pix({String? id, String? description, String? uri}) = _Pix;
}
...where I need the id to be generated (in my case it's a uuid), I have a StateNotifier handle that for me
class PixList extends StateNotifier<List<Pix>> {
PixList([List<Pix>? initialPixList]) : super(initialPixList ?? []);
void add(String description, String uri) {
state = [...state,
Pix(
id: _uuid.v4(),
description: description,
uri: uri
)
];
}
}
Since it's my state provider that should be handling creating objects, I'm effectively delegating responsibility for assigning that initial id value to the provider, not the model itself which remains nice and thin
I have an Api class that accepts an (optional) authentication token that it uses for making authenticated requests e.g. Api(token: 'a9sa2ksas12').getUserDetails().
If the token is not passed, it has to perform the relatively expensive operation of reading it from sharedPreferences.
class Api {
static const BASEURL = "https://api.google.com/";
final String token;
Api({ this.token });
Future<http.response> getUserDetails() async {
return http.get('$BASEURL/user/', headers: { 'Authorization': token });
}
}
How can I setup my app so that the token is read only once from sharedPreferences and used throughout the app for all future Api() requests?
Some ideas I've considered and think that may work:
Have token be a global variable
Make the API class a singleton, and pass it around between "screens"
Well in general there's nothing bad in making you Repositories a singletons. But on the other hand, I don't like the concept of passing the API classs between the screens.
When widgets use your data source directly without any middleman, like Bloc or Provider, they tend to be polluted with a lot of presentation logic. I personally prefer to separate those concerns and let widgets do what they are made for: rendering the UI. This makes them smaller and easier to test.
What's more the API class should be responsible only for the network calls. It shouldn't be responsible for managing the token. I'd inject to the API class something like:
class TokenProvider
{
Future<String> getToken();
}
The responsibility of this class would be, you guessed it, to provide a token. It can return cached value or get it from the SharedPreferences. Thanks to this the API doesn't have to care where the token comes and how to handle it. It will do just one thing: api calls.
I ended up using the "Locator" pattern, via get_it.
The code was pretty simple.
Step 1: Setup a top-level locator.dart in lib/
// ./lib/locator.dart
import 'package:my_app/services/api.dart';
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => Api());
}
Step 2: Use api anywhere in the app by just importing the locator:
// ./lib/widgets/some_screen.dart
class _SomeScreenState extends State<SomeScreen> {
Api api = locator<Api>();
#override
void initState() {
api.getUserDetails().then((response) => {
// do anything you like with the response
});
super.initState();
}
The beauty of this approach is that Api is only ever initialized ONCE in the lifetime of the app, so I simply assign a token to it on initState without worrying about the widget getting disposed/rebuilt and repeated fetches to SharedPreferences.