0

So I am making my first Flutter app, and on the login screen I used a custom button widget with animation (I made it a class, so that I can use it multiple times in my application). My custom button widget is based on this Tutorial. My question is, I am trying to invoke text validations by using the setState() method in the method I am about to pass to the widget class. Since it is on a completely different class I am unable to invoke the setState() method.

I have searched SO as much as I can, but I cant seem to find any useful question that apply to my use case. What I want done is, once the user presses the custom widget button, the Custom Widget Button's onPressed() method should invoke the validateTextField() method and use setState() to set the TexFormField's errorText parameter accordingly (the text field is in the Login Widget Class).

PS: This is my first SO question, so please bear with me if I made any mistakes, if I need to edit any part of this question just comment on it I will do so. And if you require any more code just ask, thank you.

Here is my code:

My Custom Widget Class:

  class LoginProgressButton extends StatefulWidget {
  final Color successButtonColor;
  final Color buttonColor;
  final Color splashColor;
  @required
  final bool onPressed;
  @required
  final VoidCallback navigator;

  const LoginProgressButton({
    Key key,
    this.onPressed,
    this.successButtonColor,
    this.buttonColor,
    this.splashColor,
    this.navigator,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _LoginProgressButton();
}

class _LoginProgressButton extends State<LoginProgressButton>
    with TickerProviderStateMixin {
  int _state = 0;
  double _width = 240.0;
  Animation _animation;
  AnimationController _controller;
  GlobalKey _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Container(
      key: _globalKey,
      alignment: Alignment.center,
      height: 60.0,
      width: _width,
      child: ButtonTheme(
        height: 60.0,
        minWidth: _width,
        shape: OutlineInputBorder(
          borderRadius: BorderRadius.circular(30.0),
        ),
        child: RaisedButton(
          onPressed: () => onPressed(),
          color: _state == 2 ? widget.successButtonColor : widget.buttonColor,
          child: loginState(),
          splashColor: widget.splashColor,
          onHighlightChanged: null,
        ),
      ),
    );
  }

  Widget loginState() {
    if (_state == 2) {
      return Icon(
        Icons.check,
        color: Colors.white,
      );
    } else if (_state == 1) {
      return SizedBox(
        height: 30.0,
        width: 30.0,
        child: CircularProgressIndicator(
          value: null,
          backgroundColor: Colors.white,
          strokeWidth: 3.0,
          valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
        ),
      );
    } else {
      return Text('Connect',
          style: TextStyle(
            color: Colors.white,
            fontSize: 24.0,
          ));
    }
  }

  void animateButton() {
    print(_state);
    double initialWidth = _globalKey.currentContext.size.width;
    _controller =
        AnimationController(duration: Duration(milliseconds: 300), vsync: this);

    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
      ..addListener(() {
        setState(() {
          _width = initialWidth - ((initialWidth - 60.0) * _animation.value);
          //this reduces the width by 48.0 for each frame, thus showing a smooth transition
        });
      });

    _controller.forward(); //the forward function starts the animation

    //starting with the default state
    setState(() => _state = 1);

    Timer(Duration(milliseconds: 3600), () => setState(() => _state = 2));

    Timer(Duration(milliseconds: 4200), () => widget.navigator());

    Timer(Duration(milliseconds: 4400), () => reset());
  }

  void reset() {
    _width = 240.0;
    _state = 0;
  }

  @override
  void dispose() {
    super.dispose();
  }

  void onPressed() {
    if (widget.onPressed) {
      setState(() {
        if (_state == 0) {
          animateButton();
        } else if (_state == 2) {
          reset();
        }
      });
    } else {}
  }
}

The Custom Widget being invoked by the Login Widget Class:

LoginProgressButton(
     buttonColor: Theme.of(context).buttonColor,
                  splashColor: Theme.of(context).splashColor,
                  onPressed: _validateTextField(_pageHostLink),
                  navigator: _navigation,
                  successButtonColor: Colors.greenAccent,
                ),

On Pressed method passed to Custom Widget:

void _validateField() {
    String value = _pageHostLink;
    setState(() {
      _fieldValidated = value == null ? false : value.isNotEmpty;
    });
    print("value is $value of datatype ${value.runtimeType}");
    setState(() => _errorText =
        _fieldValidated ? null : 'This field cannot be left empty');
  }

  bool _validateTextField(String value) {
    print("value is $value of datatype ${value.runtimeType}");
    _validateField();
    return value == null ? false : value.isNotEmpty;
  }

The Navigator function that pushes the next route if validation is successful

void _navigation() {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (BuildContext context) => LoginPage(
              pageHostLink: _pageHostLink,
            ),
      ),
    );
  }
Gaana My
  • 1
  • 1
  • 3
  • Possible duplicate of [Trigger a function from a widget to a State object](https://stackoverflow.com/questions/50733840/trigger-a-function-from-a-widget-to-a-state-object) – Rémi Rousselet Jul 26 '18 at 09:55
  • Thanks I will look into it and see if my problem can be solved – Gaana My Jul 26 '18 at 10:44

1 Answers1

2

There are a few ways to change state of another widget. The simplest is to pass a callback function. Other option is to use InheritedWidhets, so you can get AppState from anywhere in your app. More about different architecture examples you can find here.

For your case, I'd change bool onPressed to ValidationFunction onPressed in LoginProgressButton class, where ValidationFunction is

typedef bool ValidationFunction();

And you create you class as

LoginProgressButton(
    buttonColor: Theme.of(context).buttonColor,
     splashColor: Theme.of(context).splashColor,
     onPressed: () => _validateTextField(_pageHostLink),
     navigator: _navigation,
     successButtonColor: Colors.greenAccent,
),
Marica
  • 646
  • 7
  • 11
  • Two questions, where should typedef bool ValidationFunction(); be declared and the function _validateTextField(_pageHostLink), should it be of type ValidationFunction()? – Gaana My Jul 27 '18 at 09:51
  • `typedef bool ValidationFunction();` should be outside of class, next to it. It is just new type for functions - accept 0 parameters and return bool. `_validateTextField(_pageHostLink)` - no, it should not be of this type. `() =>` - this anonymous function of this type – Marica Jul 27 '18 at 12:03
  • What should the `ValidationFunction` return? – Gaana My Jul 27 '18 at 13:57
  • `bool`. As I understand your code, it will be anonymous function with returning validation result. – Marica Jul 27 '18 at 19:53