ReactiveX / rxdart

The Reactive Extensions for Dart
http://reactivex.io
Apache License 2.0
3.37k stars 270 forks source link

Rx.combineLatest doesn't call combiner when keyboard is up #574

Open donpaul120 opened 3 years ago

donpaul120 commented 3 years ago

This be the most weird thing to debug... as i'm currently new to flutter

  Widget _buildMain(BuildContext context) {
    final viewModel = Provider.of<OnBoardingViewModel>(context, listen: false);
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      mainAxisSize: MainAxisSize.max,
      children: [
        Text(
          'Create Your Profile',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            color: Colors.darkBlue,
            fontSize: 21,
          ),
          textAlign: TextAlign.start,
        ),
        SizedBox(height: 30),
        StreamBuilder(
            stream: viewModel.profileForm.usernameStream,
            builder: (context, snapshot) {
              return Styles.appEditText(
                  hint: 'Enter Username',
                  animateHint: true,
                  onChanged: viewModel.profileForm.onUsernameChanged,
                  errorText:
                      snapshot.hasError ? snapshot.error.toString() : null,
                  startIcon:
                      Icon(CustomFont.username_icon, color: Colors.colorFaded),
                  drawablePadding: 8);
            }),
        SizedBox(height: 16),
        StreamBuilder(
            stream: viewModel.profileForm.passwordStream,
            builder: (context, snapshot) {
              return Styles.appEditText(
                  hint: 'Password',
                  onChanged: viewModel.profileForm.onPasswordChanged,
                  errorText: snapshot.hasError ? snapshot.error.toString() : null,
                  animateHint: true,
                  drawablePadding: 4,
                  startIcon: Icon(CustomFont.password, color: Colors.colorFaded),
                  isPassword: true
              );
            }),
        SizedBox(height: 32),
        // Spacer(),
        StreamBuilder(
          stream: viewModel.profileForm.isValid,
          initialData: false,
          builder: (context, AsyncSnapshot<bool> snapshot) {
            print(snapshot.connectionState);
            print(snapshot);
            return Stack(
              children: [
                SizedBox(
                  width: double.infinity,
                  child: Styles.appButton(
                      onClick: snapshot.hasData && snapshot.data == true ? () => {} : null,
                      text: 'Continue'
                  ),
                ),
                Positioned(right: 16, top: 16, bottom: 16, child: SizedBox())
              ],
            );
          },
        ),
      ],
    );
  }
class ProfileForm with ChangeNotifier {

  final _usernameController = StreamController<String>.broadcast();
  Stream<String> get usernameStream => _usernameController.stream;

  final _passwordController = StreamController<String>.broadcast();
  Stream<String> get passwordStream => _passwordController.stream;

  void onUsernameChanged(String? text) {
    if(text == null || text.isEmpty) {
      _usernameController.sink.add("");
      _usernameController.sink.addError("Enter username");
    } else {
      _usernameController.sink.add(text);
    }
  }

  void onPasswordChanged(String? text) {
    if(text == null || text.isEmpty) {
      _passwordController.sink.add("");
      _passwordController.sink.addError("Enter password");
    } else {
      _passwordController.sink.add(text);
    }
  }

  // bool _isPasswordValid() {
  //   //check that the password is not empty
  //   //check that the password conforms to what we require
  // }

  Stream<bool> get isValid => Rx.combineLatest([usernameStream, passwordStream], (values) {
    print(values);
    return (values.elementAt(0) as String).isNotEmpty;
  });

}

The input fields works fine, that's username and password reacts well, however when the keyboard is up, the callback in combineLatest isn't trigger. i use cmd + K to open up the keyboard in ios simulator.

The idea is to validate both the username and password and that should determine if the button should be enabled or disabled.

I tried StreamGroup.merge([]) this works fine with keyboard visible or hidden, but the issue with stream group is i cannot access the typed value emitted.

I noticed that when the keyboard is up, the connectionState is always waiting, i'm quite getting my hands around flutter coming from android native (observables,livedata,flow etc) so i really don't know much here.

I'm using: async: ^2.5.0 rxdart: ^0.26.0

donpaul120 commented 3 years ago

I'm sorry guys!!! After sharing my problem i found out the embarrassing issue!!!!

  Stream<bool> get isValid => Rx.combineLatest([usernameStream, passwordStream], (values) {
    print(values);
    return (values.elementAt(0) as String).isNotEmpty;
  });

get returns a new combined stream each time it's called! don't know why it works when the keyboard is hidden though..

hoc081098 commented 3 years ago

You should store Stream in a field and can use shareValueSeed(...) to provide a value for StreamBuilder

donpaul120 commented 3 years ago

You should store Stream in a field and can use shareValueSeed(...) to provide a value for StreamBuilder

Thank you!... Please can you point me to the documentation of shareValueSeed, i googled it and can't seem to find anything.

hoc081098 commented 3 years ago

You should store Stream in a field and can use shareValueSeed(...) to provide a value for StreamBuilder

Thank you!... Please can you point me to the documentation of shareValueSeed, i googled it and can't seem to find anything.

Just a typo 😃 I mean, shareValueSeeded(...): https://pub.dev/documentation/rxdart/latest/rx/ConnectableStreamExtensions/shareValueSeeded.html

hoc081098 commented 3 years ago
class ProfileForm {
  final stream = Rx.combineLatest(...).shareValueSeeded(false);
  final subscription = stream.listen(null); // keep stream alive

  void dispose() => subscription.cancel();
}

StreamBuilder<bool>(
  stream: form.stream,
  initialData: form.stream.requireValue, // or stream.value,
  builder: ...
);
donpaul120 commented 3 years ago

@hoc081098 Cool, Thanks... I will try this out.