How to communicate between two stateProviders in river pod? - flutter

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));
}
}

Related

Understanding Cubit/Bloc state management

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

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

How to mock dispatch methods in ReduxAction from flutter package `async_redux`

I am developing an android / ios application in flutter, and I have chosen to use redux for my state management.
I am writing unit tests for my redux actions, which have been implemented using the async_redux package.
I am following the excellent guidelines set out for testing by the author of the package, but I am not sure how to mock the dispatch of further actions from my action under test.
For example, the below LogoutAction dispatchs a DeleteDatabaseAction and waits for it to complete:
class LogoutAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() async {
await dispatchFuture(DeleteDatabaseAction());
return AppState.initialState();
}
}
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
FutureOr<AppState> reduce() {
throw StateError(
'Unwanted call to runtime implementation of DeleteDatabaseAction',
);
}
}
void main() {
final store = Store<AppState>(initialState: AppState(loggedIn: true));
final storeTester = StoreTester.from(store);
test('Logout action should return correct state and not throw StateError', () async {
storeTester.dispatch(LogoutAction());
TestInfo<AppState> info = await storeTester.wait(LogoutAction);
expect(info.state.loggedIn, false);
});
}
I want to test only the action under test, and stub out all further action calls.
i.e. How can I mock / stub the dispatch and dispatchFuture methods on ReduxAction, so that the runtime DeleteDatabaseAction implementation is not run?
So far I have attempted:
Inject DeleteDatabaseAction using get_it and inject a mock during test
I have 100+ actions that will now need to be added to my context
Some actions have parameters that change based on where they are called from, so cannot be registered at app startup
Subclass Store, override the above methods and use the subclass in my test here final store = Store<AppState>(initialState: AppState(loggedIn: true))
I will not be able to dispatch my action under test, as it uses the same store in the async_redux test implementation
Here: storeTester.dispatch(LogoutAction());
Create a separate Dispatcher implementation, inject this and override with a mock during tests
This will work, but it is new framework, I can go this route but now I am deviating from the well documented framework provided by asyn_redux
This wasn't available when you asked this question. But now the answer is here: https://pub.dev/packages/async_redux#mocking-actions-and-reducers
To mock an action and its reducer, start by creating a MockStore in your tests, instead of a regular Store. The MockStore has a mocks parameter which is a map where the keys are action types, and the values are the mocks. For example:
var store = MockStore<AppState>(
initialState: initialState,
mocks: {
MyAction1 : ...
MyAction2 : ...
...
},
);
However, there are other ways:
As you said, use get_it or some other dependency injection code to inject the http client into the action reducer. This works well.
Use a DAO. For example:
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() {
await dao.deleteDatabase();
return null;
}
Then you can mock the DAO itself, or inject the DAO via get_it. You can also make the dao a getter to some BaseAction (that extends ReduxAction) and inject it there.

Vertx: Using AbstractVerticle Context to pass around objects

We have been using the Context object to in a long chain of async execution.
e.g.:
private void checkVehicle(final JsonObject cmd,
final RedisFleetStorage storage,
final Handler<AsyncResult<String>> handler) {
// omitted for brevity
// some async call to another verticle
storage.getVehicle(fleetId, vehicleId, result -> {
if (!result.succeeded()) {
LOG.error(String.format("Impossible to get vehicleStatus %s:%s", fleetId, vehicleId), result.cause());
handler.handle(Future.failedFuture("KO");
return;
}
// put vehicle in context for later use
final Vehicle vehicle = result.result();
LOG.info("vehicle details {}", vehicle);
context.put("vehicle", vehicle);
handler.handle(Future.succeededFuture());
});
}
As seen above, we put an object (vehicle) in the context and then access later in the execution.
But we suspect that the vehicle object it's altered by another execution. Is it possible? Can another event-loop change the object in the context?
A verticle instance handles all requests with the same event loop.
This why the Context object is not suited for storage of request specific data.

MEF and IObservables

I have a singleton IObservable that returns the results of a Linq query. I have another class that listens to the IObservable to structure a message. That class is Exported through MEF, and I can import it and get asynchronous results from the Linq query.
My problem is that after initial composition takes place, I don't get any renotification on changes when the data supplied to the Linq query changes. I implemented INotifyPropertyChanged on the singleton, thinking it word make the exported class requery for a new IObservable, but this doesn't happen.
Maybe I'm not understanding something about the lifetime of MEF containers, or about property notification. I'd appreciate any help.
Below are the singleton and the exported class. I've left out some pieces of code that can be inferred, like the PropertyChanged event handlers and such. Suffice to say, that does work when the underlying Session data changes. The singleton raises a change event for UsersInCurrentSystem, but there is never any request for a new IObservable from the UsersInCurrentSystem property.
public class SingletonObserver: INotifyPropertyChanged
{
private static readonly SingletonObserver _instance = new SingletonObserver();
static SingletonObserver() { }
private SingletonObserver()
{
Session.ObserveProperty(xx => xx.CurrentSystem, true)
.Subscribe(x =>
{
this.RaisePropertyChanged(() => this.UsersInCurrentSystem);
});
}
public static SingletonObserverInstance { get { return _instance; } }
public IObservable<User> UsersInCurrentSystem
{
get
{
var x = from user in Session.CurrentSystem.Users
select user;
return x.ToObservable();
}
}
}
[Export]
public class UserStatus : INotifyPropertyChanged
{
private string _data = string.Empty;
public UserStatus
{
SingletonObserver.Instance.UsersInCurrentSystem.Subscribe(sender =>
{
//set _data according to information in sender
//raise PropertyChanged for Data
}
}
public string Data
{
get { return _data; } }
}
}
My problem is that after initial composition takes place, I don't get any renotification on changes when the data supplied to the Linq query changes.
By default MEF will only compose parts once. When a part has been composed, the same instance will be supplied to all imports. The part will not be recreated unless you explicitly do so.
In your case, if the data of a part change, even if it implements INotifyPropertyChanged, MEF will not create a new one, and you don't need to anyway.
I implemented INotifyPropertyChanged on the singleton, thinking it word make the exported class requery for a new IObservable
No.
Maybe I'm not understanding something about the lifetime of MEF containers, or about property notification.
Property notification allows you to react to a change in the property and has no direct effect on MEF. As for the container's lifetime, it will remain active until it is disposed. While it is still active, the container will keep references to it's compose parts. It's actually a little more complex than that, as parts can have different CreationPolicy that affects how MEF holds the part, I refer you to the following page: Parts Lifetime for more information.
MEF does allow for something called Recomposition. You can set it likewise:
[Import(AllowRecomposition=true)]
What this does tough is allow MEF to recompose parts when new parts are available or existing parts aren't available anymore. From what I understand it isn't what you are referring to in your question.