alibaba / fish-redux

An assembled flutter application framework.
https://github.com/alibaba/fish-redux
Apache License 2.0
7.34k stars 843 forks source link

Global Store only updates view when runs twice. #734

Open henry-hz opened 3 years ago

henry-hz commented 3 years ago

Describe the bug In a chat application, I am using a global variable to update the badge of the pending notifications in the main bar, and also in a report view, that is in fact a component. In the main bar, it keeps in sync, but inside the component, even after using ConnOp and connected successfully with the host page, the only way to have it updated is calling the global store twice. Believe me, I tested with Future.delayed(Duration...)), also added await in all correct places, but the only solution is calling the dispatch twice. I am Show the code you wrote as completely as possible.

Effect<VisitState> buildEffect() {
  return combineEffects(<Object, Effect<VisitState>>{
    VisitAction.action: _onAction,
    VisitAction.refresh: _onRefresh,
    Lifecycle.initState: _onInit,
    Lifecycle.dispose: _onDispose
  });
}

void _onAction(Action action, Context<VisitState> ctx) {}

/// When HTTP is done, we use a [FutureBuilder] to render the UI
/// to help reading we will use a short name [con]
void _onInit(Action action, Context<VisitState> ctx) async {
  ctx.state.scrollController = ScrollController();
  // see https://stackoverflow.com/questions/49807687/how-to-load-all-dart-dateformat-locale-in-flutter
  await initializeDateFormatting();

  // pooling
  //Timer.periodic(Duration(seconds: 20), (_) => _loadAppointments(action, ctx));
  await _onRefresh(action, ctx);
  //ctx.state.appointments = fakeAppointments();
}

/// Clean resources
void _onDispose(Action action, Context<VisitState> ctx) =>
    ctx.state.scrollController?.dispose();

/// Retrieve [Appointment] list for this user using the JWT as an user
/// identification, so we don't need to send the appUser.id
/// final appUser = GlobalStore.store.getState().user;
void _onRefresh(Action action, Context<VisitState> ctx) async {
  ctx.dispatch(VisitActionCreator.loading(true));
  var  notifCount = await _getPending();
  //GlobalStore.store.dispatch(GlobalActionCreator.setNotificationCount(notifCount));
  //ctx.state.notificationCount = notifCount;
  await Client.rest.getAppointments().then((res) async {
    ctx.state.appointments = res;
    await _setSummary(res, ctx, notifCount);
  }).catchError((Object obj) {
    NetError er = RequestException.handleException(obj);
    ToastMessage.show(er.msg);
  });

  ctx.dispatch(VisitActionCreator.loading(false));
  // TODO: BUG HERE, leave it here twice... ugly, but works
  GlobalStore.store.dispatch(GlobalActionCreator.setNotificationCount(notifCount));
  GlobalStore.store.dispatch(GlobalActionCreator.setNotificationCount(notifCount));
  // var  notifCount2 = await _getPending();
}

/// set summary if we have [Appointment]
Future<void> _setSummary(List<Appointment> appointments, Context<VisitState> ctx, int notifCount) async {
  if (appointments.length == 0) return;
  String doctor = 'Dr. ' + appointments[0].healthcareProfessional.firstName + ' ' +
                           appointments[0].healthcareProfessional.lastName;
  DateTime date = appointments[0].date.add(Duration(days: 1));   // add 1 day [to be correct]
  Duration difference = date.difference(DateTime.now());         // calculates the diff
  // setup summary
  Summary summary = Summary(
      doctor: doctor,
      notificationCount: notifCount,
      remainingDays: difference.inDays.toString()
  );
  ctx.dispatch(SummaryActionCreator.onSetSummary(summary));
  print('sdfsdf'+notifCount.toString());
}

/// get pending converstations. It's ugly to call it again, maybe we could have it
/// in a global variable, or redesign the API
Future<int> _getPending() async {
  int pending;
  await Client.rest.getOnboarding().then((res) {
    pending =  res.pendingConversations;
  }).catchError((Object obj) {
    NetError er = RequestException.handleException(obj);
    ToastMessage.show(er.msg);
  });
  return pending ?? 0;
}

class SummaryComponent extends Component<SummaryState> {
  SummaryComponent()
      : super(
            effect: buildEffect(),
            reducer: buildReducer(),
            shouldUpdate: (oldItem, newItem) {
              return newItem.notificationCount != oldItem.notificationCount ||
                     newItem.user != oldItem.user ||
                     newItem.locale != oldItem.locale;
            },
            view: buildView,
            dependencies: Dependencies<SummaryState>(
                adapter: null,
                slots: <String, Dependent<SummaryState>>{
                })
            );

}

class SummaryState implements Cloneable<SummaryState> {

  // days for the next appointment
  String remainingDays;
  String doctor;

  Locale locale;

  User user;

  SummaryState clone() {
    return SummaryState()
      ..remainingDays = remainingDays
      ..locale = locale
      ..notificationCount = notificationCount
      ..doctor = doctor;
  }

  int notificationCount;
}

class SummaryConnector extends ConnOp<VisitState, SummaryState> {
  @override
  SummaryState get(VisitState state) {
    SummaryState subState = state.summaryState.clone();
    subState.notificationCount = state.notificationCount;
    subState.locale = state.locale;
    // Log.debug('=======================---------->>>>>>>>>>>>>>>');
    // Log.debug(state.locale.toString());
    Log.debug(state.notificationCount.toString());
    return subState;
  }

  @override
  void set(VisitState state, SummaryState subState) {
     // subState.notificationCount = state.notificationCount;
     // subState.locale = state.locale;
     state.summaryState = subState;
  }
}

To Reproduce have to land in the main view and see that the notificationCount conversation in summary view is null Expected behavior notificationCount should appear

Additional context

  1. The version of fish-redux which you are using. ^0.3.4

  2. The information from flutter doctor.

Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 1.22.4, on Linux, locale en_US.UTF-8) [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2) [!] Android Studio (not installed) [✓] VS Code (version 1.50.0) [✓] Connected device (1 available)

henry-hz commented 3 years ago

I solved, moving the Global dispatch to the

Lifecycle.build

instead of using 'onInit'