Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform action automatically on a flutter page based on appState changes. #45

Closed
bowojori7 opened this issue May 8, 2018 · 6 comments
Closed

Comments

@bowojori7
Copy link

bowojori7 commented May 8, 2018

I'm trying to implement this idea where I dispatch a log-in action and from the log-in middleware, launch another action that changes a variable in the appState if there's an error in the log in process. That's done.
The issue is, I tried to use the storeConnector, since it updates the application based on appState changes, to check for the change and open a SnackBar when the variable changes as shown in the code for the container below, but I'm obviously not doing this the right way:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

import 'package:takeout/models/app_state.dart';
import 'package:takeout/presentation/login_form.dart';
import 'package:takeout/actions/auth_actions.dart';


class AuthLogin extends StatelessWidget {

  AuthLogin({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
//    String isLoginFailed = StoreProvider.of<AppState>(context).state.isLoginFailed;

    return new StoreConnector<AppState, _ViewModel>(
        distinct: true,
        converter: _ViewModel.fromStore,
        builder: (BuildContext context, _ViewModel vm){


//          if (!StoreProvider.of<AppState>(context).reducer == "") {
//            Scaffold.of(context).showSnackBar(new SnackBar(
//                backgroundColor: Colors.red,
//                content: new Text("Error Logging In")));
//            //put reset state.isLoginFailed action here
//            isLoginFailed = StoreProvider.of<AppState>(context).state.isLoginFailed;
//          }


          return new LoginForm(
            email: vm.email,
            password: vm.password,
            saveEmail: vm.saveEmail,
            savePassword: vm.savePassword,
            attemptLogin: vm.attemptLogin,
          );
        }
    );
  }
}

class _ViewModel {
  // It should take in whatever it is you want to 'watch'
  final Function saveEmail;
  final Function savePassword;
  final Function attemptLogin;
  final String email;
  final String password;

  _ViewModel({
    this.email,
    this.password,
    this.saveEmail,
    this.savePassword,
    this.attemptLogin
  });

  // This is simply a constructor method.
  // This creates a new instance of this _viewModel
  // with the proper data from the Store.

  static _ViewModel fromStore(Store<AppState> store) {
    return new _ViewModel(
      email: store.state.typedEmail,
      password: store.state.typedPassword,
      ///...
      saveEmail: (val){
        store.dispatch(new SaveEmail(val));
      },
      savePassword: (val){
        store.dispatch(new SavePassword(val));
      },
      attemptLogin: (){
        store.dispatch(
          new LogInAction(store.state.typedEmail, store.state.typedPassword)
        );
      }
    );
  }
}

I'm sorry if this is such a tyro question but I'm just getting started with Redux and I still dont know how everything works out yet. The commented part is the part I tried to implement the "listen" for the appState change.

@brianegan
Copy link
Owner

Hey hey :) Thanks for writing in! One option that might work well for this case is to pass an onWillChange callback to the StoreConnector. This will let you run a function after the Store has changed, and is meant to handle situations like this.

Please feel free to try that, and if it doesn't quite work please write back and we can think of more options :)

@bowojori7
Copy link
Author

Thanks! @brianegan I tried the onWillChanged function and it works, I just have a problem with checking the exact change on the Store. So the body of the function won't run on every state change. Trying to check for the change in the state.isLoginFailed variable.

@brianegan
Copy link
Owner

Cool, glad that worked for ya.

To solve the second challenge, one solution is to use the distinct property on the StoreConnector. If the ViewModel does not change (based on it's == method), it won't re-run the onWillChange function!

@bowojori7
Copy link
Author

bowojori7 commented May 19, 2018

Hi @brianegan Noticed that you closed the issue. I'm sorry for the late responses but, I don't understand how setting the distinct property to true will help me in this case. Here's my storeConnector:

_ViewModel _viewModel = new _ViewModel(context: context);
//  String isLoginFailed;
    return new StoreConnector<AppState, _ViewModel>(
        onInit: (Store<AppState> store){
//          isLoginFailed = store.state.isLoginFailed;
        },
        onWillChange: (_viewModel){
          Scaffold.of(context).showSnackBar(new SnackBar(
              content: new Text("Error Occurred")));
        },
        distinct: true,  
        converter: _viewModel.fromStore,
        builder: (BuildContext context, _viewModel){
          return new LoginForm(
            email: _viewModel.email,
            password: _viewModel.password,
            saveEmail: _viewModel.saveEmail,
            savePassword: _viewModel.savePassword,
            attemptLogin: _viewModel.attemptLogin,
          );
        }
    );

Where am I supposed to perform the check if the state.isLoginFailed variable has changed?
Everything inside onWillChanged works but the snackBar shows everytime my state changes

@DizWARE
Copy link

DizWARE commented Aug 25, 2018

It looks like your view model is missing the required equality functions to be able to utilize the distinct property.

From the flutter_redux.dart file:
/// As a performance optimization, the Widget can be rebuilt only when the
/// [ViewModel] changes. In order for this to work correctly, you must
/// implement [==] and [hashCode] for the [ViewModel], and set the [distinct]
/// option to true when creating your StoreConnector.

@ScholliYT
Copy link

I had somewhat the same problem. I decided to make a "ErrorSnackbar" widget which listens for changes on the state.error variable, displays them in a snackbar and then clears up the variable.

I ended up with this code: https://gist.github.com/ScholliYT/d26dc1b947ff48eb0c96bc7c6d4e086e

I think it's bad practice to just return an empty Text widget but I could not figure out how to do it better and this seems to work.

Just add the Widget to your Scaffold like this:

body: Stack(
        children: <Widget>[
          // put other stuff here...
          ErrorSnackbar(),
        ],
      ),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants