1

I have a LoginButtonProvider class and that class has two purposes.

First: it should provide an onPressed method to my LoginButton widget once the user has typed in both text form fields. The class calls notifyListeners() method once it assigns a onPressed function to the button.

Second: it should create a spinning circle inside that button once the user has pressed that button and called the onPressed method. It should also call notifyListeners() once the user has pressed the button in order for boolean flag _isLoading to be set to true.

The problem is that I don't know how to implement the onPressed method in order for the Consumer widget to rebuild itself once the onPressed method is triggered by the user. I think that the notifyListeners is never called once the user presses the button and therefore the spinning circle never appears on the button, but it should! I have also tried sending the reference to the notifyListeners method. The widget doesn't rebuild itself when the notifyListeners() is called inside the onPressed function. Why is that? Thank you very much for your help!

Here is the LoginButtonProvider class.

class LoginButtonProvider with ChangeNotifier {

  TokenViewModel _tokenViewModel = new TokenViewModel();
  Function _onPressed;
  bool _isLoading = false;

  get onPressed => _onPressed;
  get isLoading => _isLoading;

  void updateButton(BuildContext context, FocusNode focusNode, Validator validator) {
    if (!validator.areFieldsEmpty()) {
      _onPressed = () {
        if (validator.validateFields()) {
          _isLoading = true;
          notifyListeners();
          _tokenViewModel.getToken(validator.email, validator.password, context, focusNode);
          _isLoading = false;
          notifyListeners();
        } else {
          SimpleShowDialog.createSimpleShowDialog(
            title: "Invalid Input",
            buttonText: "OK",
            context: context,
            message: validator.errorMessage,
            focusNode: focusNode,
          );
          validator.reset();
        }
      };
      notifyListeners();
    } else {
      _onPressed = null;
      notifyListeners();
    }
  }
}
Aleksa Vujisic
  • 45
  • 1
  • 10
  • What I've found out so far is that my LoginButton widget is never rebuilt. My conclusion is that notifyListeners() method is never called inside from the LoginButton once it is passed. Can you please explain how I can achieve this? Maybe I should pass a handler function? But how to do that in this situation? – Aleksa Vujisic Oct 09 '20 at 23:50

1 Answers1

0

I believe the problem is that this method is never called because the onPressed handler is inside the ChangeNotifier. And this is all the wrong way to do it. do not pass the instance of Widget logic like FocusNode, context, validator inside your ChangeNotifier instead do all of the validation in the onPressed handler of the button and then call the method in Notifier using Provider.

...
  Widget build(BuildContext context){
   final login = Provider.of<LoginButtonProvider>(context);
   
    return login.isLoading? 
    Center(child: CircularProgressIndicator()):
    Flatbutton(
    child: Text('Login')
    onPressed : 
         validator.areFieldsEmpty()) ?  /// Now the button should not be clickbale, till the field is filled.
         null:
         () {
      // do all your validation here
        if (!validator.areFieldsEmpty()) {

          login.updateButton(validator.email, validator.password);      

          } else {

          SimpleShowDialog.createSimpleShowDialog(
            title: "Invalid Input",
            buttonText: "OK",
            context: context,
            message: validator.errorMessage,
            focusNode: focusNode,
          );
          validator.reset();
        }
    );

  } 




and the LoginButtonProvider,

class LoginButtonProvider with ChangeNotifier {

  TokenViewModel _tokenViewModel = new TokenViewModel();

  bool _isLoading = false;


  get isLoading => _isLoading;

  void updateButton(String email, String password)async {
          _isLoading = true; 
           notifyListeners();

           await _tokenViewModel.getToken(email ,password); /// not sure what this is, but if its an API call would not recommend passing in context and node again,

          _isLoading = false;
          notifyListeners();
    }

Make sure you have the ChangeNotifierProvider<LoginButtonProvider> above your MaterialApp to access through Provider.

sameer kashyap
  • 1,121
  • 1
  • 9
  • 14
  • Thank you very much for taking the time to check out my problem. I see what you are saying. I was reading about MVVM code architecture. They point out that all the business logic should be outside of the widget UI. I have updateButton method with those arguments because I wanted to assign the onPressed method outside of the widget UI. If I put everything inside of the widget the code just looks bloated. I managed to get my code to work by calling notifyListeners() inside the getToken() method and I just registered a new Listener in the form field. – Aleksa Vujisic Oct 10 '20 at 09:27
  • I Follow MVVM too, by business logic it might be API calls and state management, but I think validation should happen in the UI Layer itself. You'll even find validator in forms. hence, only values must be passed down to the next layer, not instances of a widget or class. Especially in case of flutter, you are passing too many unnecessary stuff into `ChnageNotifier`, like context and Focus node whose lifecycle should stay within the widget layer. So I'd say my solution is the better way to do it since I have been following it for quite some while now. It works really well @AleksaVujisic – sameer kashyap Oct 10 '20 at 17:10
  • I sure agree with you! After reading more about MVVM I understand it better now. The problem with your answer is that the button always has the onPressed function. And I want the button to not be clickable until both of my text fields in my form have some input text from the user. – Aleksa Vujisic Oct 11 '20 at 21:12
  • @AleksaVujisic, that isn't an issue, you can add a ternary operator to make the button nonclickbale. Let me edit my answer. Checkout [this](https://www.raywenderlich.com/6373413-state-management-with-provider) blog post for a great dive into MVVM using `provider` and `ChangeNotifier` – sameer kashyap Oct 12 '20 at 02:54
  • Thank you very much! I changed the way I use MVVM and I did it this way, it's much more organized and better overall! I will accept your answer! – Aleksa Vujisic Oct 14 '20 at 19:46