marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
230 stars 41 forks source link

Custom partial BaseAction #80

Closed leonardo2204 closed 3 years ago

leonardo2204 commented 4 years ago

Hi @marcglasberg,

I'm having a problem using sub-state (again), I've created an error screen which has a retry button. The problem is that my ViewModel is not being fired, thus the retry screen stays forever. My code:

@immutable
class AppState {
  final EventSummaryState eventSummaryState;
  final CityState cityState;
  final Wait wait;
  final bool failed;

  AppState({ this.eventSummaryState, this.cityState, this.wait, this.failed });

  AppState copy({ EventSummaryState eventSummaryState, CityState cityState,
    Wait wait, bool failed }) {
    return AppState(
      eventSummaryState: eventSummaryState ?? this.eventSummaryState,
      cityState: cityState ?? this.cityState,
      wait: wait ?? this.wait,
      failed: failed ?? this.failed,
    );
  }

  static AppState initialState() => AppState(
    eventSummaryState: EventSummaryState.initialState(),
    cityState: CityState.initialState(),
    wait: Wait(),
    failed: false,
  );

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
          other is AppState &&
              runtimeType == other.runtimeType &&
              eventSummaryState == other.eventSummaryState &&
              cityState == other.cityState &&
              wait == other.wait &&
              failed == other.failed;

  @override
  int get hashCode =>
      eventSummaryState.hashCode ^
      cityState.hashCode ^
      wait.hashCode ^
      failed.hashCode;
}
abstract class BaseAction extends ReduxAction<AppState> {
  EventSummaryState get eventSummaryState => state.eventSummaryState;
  CityState get cityState => state.cityState;
}
abstract class EventSummaryAction extends BaseAction {

  FutureOr<EventSummaryState> reduceEventSummaryState();

  @override
  FutureOr<AppState> reduce() async {
    FutureOr<EventSummaryState> eventSummaryState = reduceEventSummaryState();
    if (eventSummaryState is Future) {
      final finalState = await eventSummaryState;
      return state.copy(eventSummaryState: finalState);
    } else {
      return (eventSummaryState == null) ? null : state.copy(eventSummaryState: eventSummaryState);
    }
  }
}
class FetchAllEventSummaries extends EventSummaryAction {

//  @override
//  void before() => dispatch(WaitAction.add(this));
//
//  @override
//  void after() => dispatch(WaitAction.remove(this));

  @override
  Future<EventSummaryState> reduceEventSummaryState() async {
    final events = await EventRepository(http.Client()).getEventsSummary();
    return state.eventSummaryState.copy(events: events, failed: false);
  }
}

And finally:

class HomePageConnector extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, _ViewModel>(
        model: _ViewModel(),
        builder: (BuildContext context, _ViewModel vm) =>
            HomePage(
              events: vm.events,
              fetchEvents: vm.fetchEvents,
              loading: vm.loading,
              failed: vm.failed,
              selectedCity: vm.selectedCity,
              selectCity: vm.selectCity,
            )
    );
  }
}

class _ViewModel extends BaseModel<AppState> {
  _ViewModel();

  List<EventSummary> events;
  VoidCallback fetchEvents;
  bool loading;
  bool failed;
  City selectedCity;
  Function(City) selectCity;

  _ViewModel.build({
    @required this.events,
    @required this.fetchEvents,
    @required this.loading,
    @required this.failed,
    @required this.selectedCity,
    @required this.selectCity,
  }) : super(equals: [events, loading, failed, selectedCity]);

  @override
  _ViewModel fromStore() => _ViewModel.build(
    events: state.eventSummaryState.events,
    fetchEvents: () => dispatch(FetchAllEventSummaries()),
    loading: state.wait.isWaiting,
    failed: state.eventSummaryState.failed,
    selectedCity: state.cityState.selectedCity,
    selectCity: (city) => dispatch(SelectCity(city))
  );
}

Is this the right approach? BTW it would be nice to have an enhanced example, using multiple screens and sub-states!

Thanks

marcglasberg commented 4 years ago

Normalmente quando o StoreConnector não detecta alteração no ViewModel é porque ele acha que o ViewModel não mudou. Você pode criar um toString() detalhado do ViewModel, e daí dar um print do ViewModel dentro do fromStore() pra entender porque isso está acontecendo.

Parece correto assim a primeira vista, mas é muito difícil achar erros assim. Se vc quiser minha ajuda, vc precisa me passar um código mínimo, reprodutível, que demonstre o seu problema, tudo em um único arquivo e sem dependências, para que eu possa rodar facilmente aqui na minha máquina.

abijr commented 3 years ago

Normally when the StoreConnector does not detect a change in the ViewModel it thinks that the ViewModel has not changed. You can create a detailed ViewModel toString(), and then give a printout of the ViewModel inside fromStore() to understand why this is happening.

It looks correct at first glance, but it is very difficult to find errors like this. If you want my help, you need to send me some minimal, reproducible code that demonstrates your problem, all in a single file and without dependencies, so I can easily run it here on my machine.

(deepl translation)