Understanding Cubit/Bloc state management - flutter

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

Related

Why when using Riverpod with StateNotifier, setting a (state) field to null, the listeners are not notified?

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.

How to communicate between two stateProviders in river pod?

i have just recently stated working with riverpod state mangement in flutter.
i have issue related to comunicate between to state providers.
here is my sample code:
class SomeClass_ONE extends stateNotifer <SomeState> {
SomeClass_ONE({required this.somevalue}):super(null);
final SomeCustomClass somevalue;
void methodOne(SomeState newstatevalue){
state = newstatevalue;
}
}
final someClassOneProvider =
StateNotifierProvider<SomeClass_ONE,SomeState>.
((ref)=>SomeClass_ONE(somevalue: SomeCustomClass()));
now i have another state provider class as below
class SomeClass_Two extends stateNotifer <SomeStateTwo> {
SomeClass_ONE({required this.somevalue}):super(null);
final SomeCustomClass somevalue;
void methodtwo(SomeState newstatevalue){
state = newstatevalue;
}
}
final someClassTwoProvider =
StateNotifierProvider<SomeClass_Two,SomeStateTwo>
((ref)=>someClassTwoProvider(somevalue: SomeCustomClass()));
now what i want to achhive is that on methodOne execution i have to listen that state cahnge and have to trigger methodTow and have to upate secondproviders state as well.
so how can i achive this without using Ref in class cunstroctors?
i have tried with ref.listner to trigger and have passed Ref in both class constructors. but as per some condition i can't use Ref directly in constructors as per some guideline followed by seniors.
You can pass a Ref ref object to the methodtwo method and then call the necessary methods from other StateNotifierProvider. In any case, to refer to other methods of other classes, you need to have a Ref object.
Try to use watch provided by StateNotifierProvider
Try this code:
class SomeClass_ONE extends stateNotifer <SomeState> {
SomeClass_ONE({required this.somevalue}):super(null);
final SomeCustomClass somevalue;
void methodOne(SomeState newstatevalue){
state = newstatevalue;
// Listen to the changes in the state of the first provider and call the methodtwo of the second provider
someClassTwoProvider.watch((_) => _.methodtwo(newstatevalue));
}
}

Have OrderCubit access the userId stored in AuthCubit

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.

How to mock database in flutter

I tried to mock database to test my local api, i search in official document finding mockito which can work with remote api fine, but also can not work with local database out of box, is there any way to work around of it?
In these cases, you have two options (among many others). Even if my examples assume you're making HTTP calls, it doesn't matter. You can use these strategies regardless the specific use case I'm exposing!
The first one is using the "Strategy pattern" to create an interface for the API and then switch between a test and a production API. Here's a simple example:
abstract class HttpRepository {
const HttpRepository();
Future<Something> sendRequest();
}
You can now create 2 concrete classes: one is for the actual API call and the other is just a mock for tests.
/// Use this in your tests
class MockHttpRepository extends HttpRepository {
const MockHttpRepository();
#override
Future<Something> sendRequest() async {
// Simulating the HTTP call
await Future.delayed(const Duration(seconds: 2));
return Something();
}
}
/// Use this in your Flutter code to make the actual HTTP call or whatever else
class ApiHttpRepository extends HttpRepository {
const ApiHttpRepository();
#override
Future<Something> sendRequest() async {
// Doing a real HTTP call
final response = await makeGetOrPost();
return Something.withData(response);
}
}
In this way, you'll use ApiHttpRepository in your Flutter app and MockHttpRepository in tests. Use const constructors whenever possible.
The other way is using mocks to simulate fake HTTP calls or anything else. Basically, you're using when to "trap" a method call and return a fake response you can control.
// 1. "Enable" mocking on your type
class MockRepo extends Mock implements ApiHttpRepository {}
// 2. Mock methods
const ApiHttpRepository repo = MockRepo();
when(repo.sendRequest()).thenAnswer((_) async => Something());
In this case, we're using thenAnswer because the return type of sendRequest() is of type Future<T>. In your case, if you are reading data from a database you just need to:
Make your class "mockable" using extends Mock implements YourClass
Use when on the mockable instance and control the output
Make sure to use thenAnswer if the method returns a Future<T> and thenReturn in all the other cases.

Arguments of a constant creation must be constant expressions while implement a model using freezed

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