GIfatahTH / states_rebuilder

a simple yet powerful state management technique for Flutter
494 stars 56 forks source link

How to RM.inject wait onInitialized finished #248

Closed qhu91it closed 2 years ago

qhu91it commented 2 years ago
class LocalPref {
    late SharedPreferences _prefs;
    Future<void> initialized() async {
        _prefs = await SharedPreferences.getInstance();
    }
    ...
}

final localPrefRM = RM.inject<LocalPref>(
  () => LocalPref(),
  onInitialized: (_) => _?.initialized(),
  autoDisposeWhenNotUsed: false,
);

class HomeViewModel {
    Future<void> initialized() async {
        // use LocalPref here and see error from here, SharedPreferences not init yet
    }
}

final homeVM = RM.inject<HomeViewModel>(
  () => HomeViewModel(
    localPref: localPrefRM.state,
    ...
  ),
  dependsOn: DependsOn({
    localPrefRM,
    ...
  }),
);

OnBuilder.all(
      listenTo: homeVM,
      sideEffects: SideEffects(
        initState: () => homeVM.state.initialized(),
        dispose: () => homeVM.state.dispose(),
      ),
    ...
)

Follow code sample, it call when app start, because onInitialized func not finished yet so when func initialized call from homeVM got the error the _prefs is not init... I just want onInitialized of RM.inject can wait until finish before go to next or any suggest for this case?

GIfatahTH commented 2 years ago

@qhu91it

In these cases, try to use InjectedFuture;

class LocalPref {
    late SharedPreferences _prefs;
    Future<LocalPref> initialized() async {
        _prefs = await SharedPreferences.getInstance();
        // add this line
         return this;
    }
    ...
}

final localPrefRM = RM.injectFuture<LocalPref>(
  () => LocalPref().initialized(),
  autoDisposeWhenNotUsed: false,
);
qhu91it commented 2 years ago

@GIfatahTH thank for your help, I try and got another error when homeVM dependsOn localPrefRM so localPrefRM not init finished => homeVM still waiting, then OnBuilder call initState (homeVM.state.initialized(args)) in SideEffects will throw error invalid arguments. Because in initialized func I pass some arguments so I can not declare at RM.inject

════════ Exception caught by widgets library ═══════════════════════════════════
The following ArgumentError was thrown building OnBuilder<Object?>:
Invalid argument(s) (
[HomeViewModel] is NON-NULLABLE STATE!
The non-nullable state [HomeViewModel] has null value which is not accepted
To fix:
1- Define an initial value to injected state.
2- Handle onWaiting or onError state.
3- Make the state nullable. (HomeViewModel?).
): Must not be null
GIfatahTH commented 2 years ago

@qhu91it Try not using dependsOn and instead use RM.injectedFuture and wait there for the localPrefRM to initialize:

final homeVM = RM.injectFuture<HomeViewModel>(
  () async => HomeViewModel(
    localPref: await localPrefRM.stateAsync,
    ...
  ),

);
qhu91it commented 2 years ago

Thanks, but it not solve my problem. I move to onSetState and it can work now

if (snap.data is HomeViewModel && snap.isIdle) {
    final vm = (snap.data) as HomeViewModel;
    vm.initialized(this);
    return;
}

I got another problem, not sure it issue or not, I set autoDisposeWhenNotUsed is false but sometime later I see it Initializing again, Screen Shot 2021-12-17 at 18 07 29

The situation is each view model use devicesRM, and have set devicesRM to dependsOn, when I push and pop page continuous, the view model dispose and somehow cause devicesRM dispose too. I remove devicesRM from dependsOn and not get this problem anymore.


Another question about injectFuture, if I get class I use like this await localPrefRM.stateAsync,, but how about get ReactiveModel?

final homeVM = RM.injectFuture<HomeViewModel>(
  () async => HomeViewModel(
    localPref: localPrefRM, //<-- how to wait localPrefRM init finished here?
    ...
  ),

);
GIfatahTH commented 2 years ago

I got another problem, not sure it issue or not, I set autoDisposeWhenNotUsed is false but sometime later I see it Initializing again,

If autoDisposeWhenNotUsed is false the model should not dispose of. Could you give me a minimistic example reproducing this case.

Another question about injectFuture, if I get class I use like this await localPrefRM.stateAsync,, but how about get ReactiveModel? Just await for the stateAsync.

final homeVM = RM.injectFuture<HomeViewModel>(
  () async {
await localPrefRM.stateAsync;
HomeViewModel(
    localPref:  localPrefRM, //<-- how to wait localPrefRM init finished here?
    ...
  )
},

);
qhu91it commented 2 years ago

@GIfatahTH I create a demo to reproducing this case https://drive.google.com/file/d/1SjY48dFFW50XQpY6yKTNtnVOm_iw2KR1/view?usp=sharing

I describe this case:

Now swipe left to open side bar and tap to push page (button 'Go to List' or '+' or '-'), then pop page, you can see countersRM dispose called.


Another issue is when you push to counter page, and push a button to change counter in this page, and got error index not init... I see because counterVM depend on countersRM, so when countersRM reload and counterVM reload too but I think counterVM should not return default object.

GIfatahTH commented 2 years ago

Hi @qhu91it

Thanks for the example. I got your case.

The situation is each view model use devicesRM, and have set devicesRM to dependsOn, when I push and pop page continuous, the view model dispose and somehow cause devicesRM dispose too. I remove devicesRM from dependsOn and not get this problem anymore.

Yes, this is a bug. It will be fixed in the next release. Thanks again for reporting it.

Another issue is when you push to counter page, and push a button to change counter in this page, and got error index not init... I see because counterVM depend on countersRM, so when countersRM reload and counterVM reload too but I think counterVM should not return default object.

This behavior is expected. Because using dependsOn and when countersRM emits a notification the counterDecrementVM will recall its creation function, thus creating a new instance of CounterDecrementViewModel which will have a new index not initialized yet.

dependsOn is meant to be used if the state is recalculated for other states.

You are using dependsOn only to notify child views when countersRM emits a notification. Just remove all dependsOn and your example will work.

qhu91it commented 2 years ago

Hi @GIfatahTH, thank for explain about dependsOn, I thought use dependsOn just for wait all depend init completed :D