kranfix / riverbloc

`flutter_bloc` implemented with `riverpod` instead of `provider`.
83 stars 16 forks source link

useBloc not using the correct state #64

Closed yun-cheng closed 2 years ago

yun-cheng commented 2 years ago

I noticed useBloc is not using the correct state when onEmitted return true sometimes. Maybe because rapidly changing of state. Here's simplified demonstration of my usage.

final state = useBloc<ABloc, AState>(
  onEmitted: (_, p, c) {
    if (p.loadState != c.loadState && c.loadState == 'success') {
      return true;
    }
    return false;
  },
).state;

print(state.loadState); 
// not always 'success', but should be 'success' except first time.
// In my case, it sometimes print 'initial' (first time) and then 'inProgress'

Not sure what went wrong. Is it possible that it's using the next state or previous state?

Btw, BlocBuilder works fine with the same expression writing in buildWhen.

kranfix commented 2 years ago

Thanks for reports. I'll review this ASAP.

kranfix commented 2 years ago

@yun-cheng, Is there any value that is rebuilding your widget?

yun-cheng commented 2 years ago

@kranfix Yes, I also use useState to rebuild this widget when keyboard visibility changed. But I don't think it's relevant.

I just wrote my own version of useBloc and seems like solving this problem. Not fully tested yet.

The buildWhen here is slightly different from the original buildWhen from BlocBuilder. It compares the current state to last saved state (with ValueNotifier), not comparing the sequential state.

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

S useBloc<B extends BlocBase<S>, S>({
  bool Function(S previous, S current)? buildWhen,
}) {
  final bloc = useContext().read<B>();
  final state = useState(bloc.state);

  useEffect(() {
    final stream = bloc.stream.listen((currentState) {
      if (buildWhen != null ? buildWhen(state.value, currentState) : true) {
        state.value = currentState;
      }
    });
    return () => stream.cancel();
  }, []);

  return state.value;
}

Edit: I think the problem is gone.

kranfix commented 2 years ago

Yes, that is beacuse you are keeping the state, while the useBloc(..).state reads the current state. Despite the onEmitted is not rebuilding the widget, another state does.

Then, I will make a breaking change:

// before

final state = useBloc<MyBloc, MyState>().state;  
 

// after

final state = useBloc<MyBloc, MyState>();  

Thus, the state will keep the latest emitted state instead of the current state.

If someone wants to read the bloc, now it is possible to use final myBloc = context.read<MyBloc>().

kranfix commented 2 years ago

This version fix the problem.
https://pub.dev/packages/flutter_hooks_bloc/versions/0.16.0