yoohoo
yoohoo

Reputation: 1217

break a form into multiple widget and interact with those widget in flutter

i have a form which i decided to break into multiple widget for code re- usability. the problem i am having i dont know how to interact with each components. for example, if the main form declare a variable, how do i access that variable in the custom textfield widget which is store in a different dart file.

below is the code i have

form dart file (main.dart)

import 'package:flutter/material.dart';
import 'package:finsec/widget/row_text_input.dart';
import 'package:finsec/widget/text_form_field.dart';
import 'package:finsec/widget/save_button.dart';
import 'package:finsec/utils/strings.dart';
import 'package:finsec/utils/dimens.dart';
import 'package:finsec/utils/colors.dart';
import 'package:finsec/widget/column_text_input.dart';

void main() {
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    title: 'Simple Interest Calculator App',
    home: ThirdFragment(),
    theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: Colors.indigo,
        accentColor: Colors.indigoAccent),
  ));
}

class ThirdFragment extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ThirdFragmentState();
  }
}

class _ThirdFragmentState extends State<ThirdFragment> {

  var _formKey = GlobalKey<FormState>();
  var _currentItemSelected = '';
  bool isError = false;
  bool isButtonPressed = false;

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



  TextEditingController amountController = TextEditingController();
  TextEditingController frequencyController = TextEditingController();


  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = Theme.of(context).textTheme.title;

    return Scaffold(
      appBar: AppBar(
        title: Text('Simple Interest Calculator'),
      ),
      body: Form(
        key: _formKey,      
        child: SingleChildScrollView(
          child: Column (children: [
            Padding(
              padding: EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
              child: CustomTextField(textInputType:TextInputType.number,
                textController: amountController,
                errorMessage:'Enter Income Amount',
                labelText:'Income Amount for testing'),
            ),
            RowTextInput(inputName: 'Frequency:',
              textInputType: TextInputType.number,
              textController: frequencyController,
              errorMessage: 'Choose Income Frequency',
              labelText: 'Income Amount for testing'
            ),
            RowTextInput(inputName: 'Date Paid:',
                textInputType: TextInputType.number,
                textController: datePaidController,
                errorMessage: 'Pick Income Payment Date',
                labelText: 'Income Amount for testing'
            ),

            SizedBox(height: 20),


          Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                MaterialButton(
                  height: margin_40dp,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
                  minWidth: (MediaQuery.of(context).size.width * .9) / 2,
                  color: Theme.of(context).primaryColor,
                  textColor: white,
                  child: new Text(save),
                  onPressed: () => {
                  setState(() {
                  if (_formKey.currentState.validate()) {
                    // amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
                  //this.displayResult = _calculateTotalReturns();
                  }
                  })
                  },
                  splashColor: blueGrey,
                ),
                MaterialButton(
                  height: margin_40dp,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
                  minWidth: (MediaQuery.of(context).size.width * .9) / 2,
                  color: Theme.of(context).primaryColor,
                  textColor: white,
                  child: new Text(save_and_continue),
                  onPressed: () => {},
                  splashColor: blueGrey,
                )
              ])
          ]
          ),
      ),
}

RowTextInput is a different dart file that contains this code. RowTextInput.dart

import 'package:flutter/material.dart';
import 'package:finsec/utils/hex_color.dart';

class CustomTextField extends StatelessWidget {
  CustomTextField({
    this.textInputType,
    this.textController ,
    this.errorMessage,
    this.labelText,
  });

  TextInputType textInputType;
  TextEditingController textController;
  String errorMessage, labelText;


  @override
  Widget build(BuildContext context) {
    bool isError = false;
    return  Container(
      child: TextFormField(
        keyboardType: textInputType,
        style: Theme
              .of(context)
              .textTheme
              .title,
        controller: textController,
        validator: (String value) {
            if (value.isEmpty) {
              return errorMessage;
            }
        },
        decoration: InputDecoration(
          labelStyle: TextStyle(
            color: Colors.grey,
            fontSize: 16.0
          ),
        contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),  //size of textfield
        errorStyle: TextStyle(
          color: Colors.red,
          fontSize: 15.0
        ),
        border: OutlineInputBorder(
          borderSide:  BorderSide(width:5.0),
          borderRadius: BorderRadius.circular(5.0)
        )
        )
      ),
    );
  }
}


i want to access isError and isButtonPressed variables located in main.dart from RowTextInput.dart and be able to assign values. main.dart should then be able to see those values assign in RowTextInput.dart file.

also,i want to move the MaterialButton button in its own widget file (button.dart) but then i dont know how this dart file will interact with the main.dart file when button is click or to check values of isError and IS button pressed. basically, i am breaking the form into different components (textfield and button) and store them in their own separate file. but i want all the files main.dart, rowintputtext, button.dart(new) to be able to see values of variables in main.dart and change the values. is this possible? is there an easier way?

thanks in advance

Upvotes: 2

Views: 4251

Answers (4)

Seferovic
Seferovic

Reputation: 46

CustomTextFields must extends parent(widget where is form) in this case it is ThirdFragment

class CustomTextField extends ThirdFragment{
CustomTextField({
this.textInputType,
this.textController,
this.errorMessage,
this.labelText,
});

Upvotes: 0

Adrien Arcuri
Adrien Arcuri

Reputation: 2400

I was also looking for breaking a form into multiple classes. This is that I did :


Form

Pass the onSaved function at the form level.

final _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
  return Form(
    key: _formKey,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
          _CustomFormField(
           onSaved: (value) => _myModelForm.field1 = value),
          ),
          _CustomFormField2(
           onSaved: (value) => _myModelForm.field2 = value),
          )
        ),
          RaisedButton(
            onPressed: () {
              // Validate will return true if the form is valid, or false if
              // the form is invalid.
              if (_formKey.currentState.validate()) {
                // Process data.
                _formKey.currentState.save();
                // Observe if your model form is updated
                print(myModelForm.field1);
                print(myModelForm.field2)
              }
            },
            child: Text('Submit'),
        ),
      ],
    ),
  );
}

_CustomFormField1

The onSaved function will be passed as argument. This class can be either in the same file than the form or in another dedicated file.

class _CustomFormField1 extends StatelessWidget {
  final FormFieldSetter<String> onSaved;
  //maybe other properties...


  _CustomFormField1({
    @required this.onSaved,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 10.0),
      child: TextFormField(
        // You can keep your validator here
        validator: (value) {
          if (value.isEmpty) {
            return 'Please enter some text';
          }
          return null;
        },
        onSaved: onSaved,
      ),
    );
  }
}

Like onSaved, you can do the same way for focusNode, onFieldSubmitted, validator if needed in

I hope it will help you and others

Upvotes: 3

Willie Nandi
Willie Nandi

Reputation: 637

There's probably a more elegant way to do it but I am currently experimenting with Singletons. See the code below:

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'dart:async';

class AppModel {


 TextEditingController nameController;
 TextEditingController surnameController;
 StreamController<String> fullnameStreamController;


 AppModel() {
   nameController = TextEditingController();
   surnameController = TextEditingController(); 
   fullnameStreamController = StreamController.broadcast();
 }

  update() {
  String fullname;
   if (nameController.text != null && surnameController.text != null) {
     fullname = nameController.text + ' ' + surnameController.text;
   } else {
     fullname = 'Please enter both names';
   }
   fullnameStreamController.add(fullname);
  }
}

GetIt getIt = new GetIt();
final appModel = getIt.get<AppModel>();


void main() {
  getIt.registerSingleton<AppModel>(AppModel());
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'Singleton Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String text;

  update() {
    setState(() {   
    });
  }

  @override 
  void initState() {  
    text = 'waiting for input';
    appModel.fullnameStreamController.stream.listen((data) {
      text = data;
      update();
    });
    super.initState();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        decoration: BoxDecoration(color: Colors.amberAccent),
        child: Column(
            children: <Widget> [
              Card(
                color: Colors.white,
                child: Text('Name'),
              ),
              Card(
                color: Colors.yellow,
                child: NameTextField()
              ),
              Divider(),
              Card(
                color: Colors.white,
                child: Text('Surname'),
              ),
              Card(
                color: Colors.yellow,
                child: SurnameTextField()
                ),
              OkButton(),
              Card(
                color: Colors.white,
                child: Text('Full name'),
              ),
              Card(
                color: Colors.orange,
                child: FullnameText(text),
                ),
              ],
          ),
        ),
    );
  }
}

class NameTextField extends StatefulWidget {
  NameTextField({Key key}) : super(key: key);

  _NameTextFieldState createState() => _NameTextFieldState();
}

class _NameTextFieldState extends State<NameTextField> {
  @override
  Widget build(BuildContext context) {
    return Container(
       child: TextField(
         controller: appModel.nameController,
       ),
    );
  }
}


class SurnameTextField extends StatefulWidget {
  SurnameTextField({Key key}) : super(key: key);

  _SurnameTextFieldState createState() => _SurnameTextFieldState();
}

class _SurnameTextFieldState extends State<SurnameTextField> {

  @override
  Widget build(BuildContext context) {
    return Container(
       child: TextField(
         controller: appModel.surnameController,
       ),
    );
  }
}

class FullnameText extends StatefulWidget {
  FullnameText(this.text,{Key key}) : super(key: key);
  final String text;


  _FullnameTextState createState() => _FullnameTextState();
}

class _FullnameTextState extends State<FullnameText> {

  @override
  Widget build(BuildContext context) {
    return Container(
       child: Text(widget.text),
    );
  }
}



class OkButton extends StatefulWidget {
  OkButton({Key key}) : super(key: key);

  _OkButtonState createState() => _OkButtonState();
}

class _OkButtonState extends State<OkButton> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white10,
       child: RaisedButton(
         color: Colors.white,
         child: Icon(Icons.check),
         onPressed: () {appModel.update();},
      ),
    );
  }
}

Check how I use the three controllers in the update function of the AppModel class.

Upvotes: 0

LgFranco
LgFranco

Reputation: 1044

If you think about it. In Flutter the Button and RawMaterialButton are already in other files. And the manage to do exactly what you want.

You should create a File mycustomButtons.dart.
In the file you should create a class that will build your Buttons...
But it must has two parameters in it's constructor actionSave actionSaveAndContinue.

You will then create two functions in your main something like:

      void _save() {
          setState(() {
              if (_formKey.currentState.validate()) {
                // amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
              //this.displayResult = _calculateTotalReturns();
              }
          })
      }

Then you should pass your created functions as parameters:

    MyCustomButtons(actionSave: _save, actionSaveAndContinue: _saveAndContinue)

So the button will have all needed information to update your main.dart variables.

The textField is pretty much the same. But you will need pass a validation function and a TextEditingController.

You can see the font of RawnMaterialButton, TextFormField to see how they receive (and pass) data from one class to an other.

Upvotes: 3

Related Questions