0
votes

So I have a Form Screen (With its own scaffold) like this :

class InputForm extends StatelessWidget {
  final Receipt initialReceipt;

  InputForm({this.initialReceipt}){
    print("InputForm() called");
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      bloc: InputFormBloc(),
      child: InputFromWidget(initialReceipt: initialReceipt),
    );
  }
}

class InputFromWidget extends StatelessWidget {
  final Receipt initialReceipt;

  InputFromWidget({this.initialReceipt}){
    print("InputFromWidget() called");
  }
 @override
 Widget build(BuildContext context) {
     return Scaffold(...........);
 }

I have my root Widget like this :

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      child: MaterialApp(
        title: 'Takefin',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.purple,
        ),
        //home: InputForm(),
        home: DashboardWidget(),
      ),
      bloc: TakeFinBloc(),
    );
  }
}

When did it work as planned?

when I set

home: InputForm(),

Even if I click anywhere and the UI changes (dropdown or click in input fields)

print("InputForm() called");        
print("InputFromWidget() called");

Are called exactly once.

But when I set

home: DashboardWidget(),

And then on clicking a button at DashboardWidget I navigated to by doing :

MaterialPageRoute(builder: (context) => InputForm()) 

And then when I click somewhere in the InputForm screen that changes widget (for example expanding dropdown, typing in a field)

print("InputForm() called");
print("InputFromWidget() called");

are called every time in such clicks which cause the layout to change.

Is this normal behavior?

If so what is the best way to store the state of InputFromWidget to avoid recreating it from start?

1

1 Answers

0
votes

Yes, this is normal behavior.you have to use StatefulWidget store the state of InputFromWidget to avoid recreating it from start. From below signup screen example you can get idea how you can manage state for InputFromWidget.

sign_up_screen.dart

class SignUpScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _SignUpScreenState();
  }
}

class _SignUpScreenState extends State<SignUpScreen> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("SignUp"),
      ),
      body: Container(
        padding: EdgeInsets.all(15.0),
        color: Colors.white,
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              SignUpFormWidget(),
              SocialLoginWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

sing_up_form_widget.dart

class SignUpFormWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SignUpFormWidgetState();
  }
}

class _SignUpFormWidgetState extends State<SignUpFormWidget> {
  final _formKey = GlobalKey<FormState>();

  var _userNameController = TextEditingController(text: "");
  var _userEmailController = TextEditingController(text: "");
  var _userPasswordController = TextEditingController(text: "");

  var _emailFocusNode = FocusNode();
  var _passwordFocusNode = FocusNode();
  bool _isPasswordVisible = true;
  bool _autoValidate = false;

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      autovalidate: _autoValidate,
      child: Column(
        children: <Widget>[
          _buildUserNameField(context),
          _buildEmailField(context),
          _buildPasswordField(context),
          _buildTermsWidget(context),
          _buildSignUpButton(context),
        ],
      ),
    );
  }

  Widget _buildUserNameField(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 5),
      child: TextFormField(
        controller: _userNameController,
        keyboardType: TextInputType.text,
        textInputAction: TextInputAction.next,
        onFieldSubmitted: (_) {
          FocusScope.of(context).requestFocus(_emailFocusNode);
        },
        validator: (value) => _userNameValidation(value),
        decoration: CommonStyles.textFormFieldStyle("User Name", ""),
      ),
    );
  }

  String _userNameValidation(String value) {
    if (value.isEmpty) {
      return "Please enter valid user name";
    } else {
      return null;
    }
  }

  Widget _buildEmailField(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 5),
      child: TextFormField(
        controller: _userEmailController,
        keyboardType: TextInputType.emailAddress,
        textInputAction: TextInputAction.next,
        onFieldSubmitted: (_) {
          FocusScope.of(context).requestFocus(_passwordFocusNode);
        },
        validator: (value) => _emailValidation(value),
        decoration: CommonStyles.textFormFieldStyle("Email", ""),
      ),
    );
  }

  String _emailValidation(String value) {
    bool emailValid =
        RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value);
    if (!emailValid) {
      return "Enter valid email address";
    } else {
      return null;
    }
  }

  Widget _buildPasswordField(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 5),
      child: TextFormField(
        controller: _userPasswordController,
        keyboardType: TextInputType.text,
        textInputAction: TextInputAction.next,
        onFieldSubmitted: (_) {
          FocusScope.of(context).requestFocus(_emailFocusNode);
        },
        validator: (value) => _userNameValidation(value),
        obscureText: _isPasswordVisible,
        decoration: InputDecoration(
          labelText: "Password",
          hintText: "",
          labelStyle: TextStyle(color: Colors.black),
          alignLabelWithHint: true,
          contentPadding: EdgeInsets.symmetric(vertical: 5),
          suffixIcon: IconButton(
              icon: Icon(
                _isPasswordVisible ? Icons.visibility_off : Icons.visibility,
                color: Colors.black,
              ),
              onPressed: () {
                setState(() {
                  _isPasswordVisible = !_isPasswordVisible;
                });
              }),
          enabledBorder: UnderlineInputBorder(
            borderSide: BorderSide(
              color: Colors.black,
            ),
          ),
          focusedBorder: UnderlineInputBorder(
            borderSide: BorderSide(color: Colors.black, width: 2),
          ),
        ),
      ),
    );
  }

  Widget _buildTermsWidget(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
      child: GestureDetector(
        onTap: (){_openTermsInWeb();},
        child: RichText(
          text: new TextSpan(
            style: new TextStyle(
              fontSize: 14.0,
              color: Colors.black,
            ),
            children: <TextSpan>[
              TextSpan(
                text: 'Terms of use',
                style:
                    TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
              ),
              TextSpan(
                text: ' and ',
                style: TextStyle(
                    color: Colors.black54, fontWeight: FontWeight.w500),
              ),
              TextSpan(
                text: 'Privacy policy',
                style:
                    TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildSignUpButton(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 15.0),
      width: double.infinity,
      child: RaisedButton(
        color: Colors.black,
        onPressed: () {
          _signUpProcess(context);
        },
        child: Text(
          "Sign Up",
          style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800),
        ),
      ),
    );
  }

  void _signUpProcess(BuildContext context) {
    var validate = _formKey.currentState.validate();

    if (validate) {
      showDialog(
          context: context,
          builder: (BuildContext ctx) {
            return AlertDialog(
              title: Text("Sign Up Success."),
              actions: <Widget>[
                FlatButton(
                    onPressed: () {
                      _clearAllFields();
                      Navigator.of(context).pop();
                    },
                    child: Text("Close"))
              ],
            );
          });
    } else {
      setState(() {
        _autoValidate = true;
      });
    }
  }

  void _clearAllFields() {
    setState(() {
      _userNameController = TextEditingController(text: "");
      _userEmailController = TextEditingController(text: "");
      _userPasswordController = TextEditingController(text: "");
    });
  }

  _openTermsInWeb( ) async {
    const url = 'https://www.solutionanalysts.com/terms-use/';
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }
}

social_login_widget.dart

class SocialLoginWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      children: <Widget>[
        _buildSocialLoginTextWidget(context),
        Row(
          children: <Widget>[
            __buildFacebookButtonWidget(context),
            __buildTwitterButtonWidget(context)
          ],
        )
      ],
    );
  }

  Widget _buildSocialLoginTextWidget(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
      child: RichText(
        text: new TextSpan(
          style: new TextStyle(
            fontSize: 14.0,
            color: Colors.black,
          ),
          children: <TextSpan>[
            TextSpan(
              text: 'Or sign up with social account',
              style:
                  TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
            ),
          ],
        ),
      ),
    );
  }

  Widget __buildTwitterButtonWidget(BuildContext context) {
    return Expanded(
      flex: 1,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: RaisedButton(
            color: Color.fromRGBO(29, 161, 242, 1.0),
            child: Image.asset(
              "assets/images/ic_twitter.png",
              width: 25,
              height: 25,
            ),
            onPressed: () {},
            shape: RoundedRectangleBorder(
                borderRadius: new BorderRadius.circular(30.0))),
      ),
    );
  }

  Widget __buildFacebookButtonWidget(BuildContext context) {
    return Expanded(
      flex: 1,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: RaisedButton(
            color: Color.fromRGBO(42, 82, 151, 1.0),
            child: Image.asset(
              "assets/images/ic_fb.png",
              width: 35,
              height: 35,
            ),
            onPressed: () {},
            shape: RoundedRectangleBorder(
                borderRadius: new BorderRadius.circular(30.0))),
      ),
    );
  }
}