How do I save data from a stateful widget in Flutter?

I have implemented a TextField that allows user input in my flutter app. Now, I would like to save the user input data to a variable on pressing a button in order to use it later on. As I see it, in order to achieve this, I have to create a specific instance of the stateful widget and of its state so that when I call the widget to extract the variable, it does not create a new version of the widget with an empty TextField. The textfields in question are ProjectNameField and ProjectDescriptionField. Here is my implementation:

ProjectDescriptionField savedDescription = ProjectDescriptionField();
ProjectNameField savedName = ProjectNameField();
AddPage savedPage = AddPage();
_AddPageState savedPageState = _AddPageState();
List<String> image_list_encoded = [];

class AddPage extends StatefulWidget {
  final _AddPageState _addPageState = _AddPageState();
  AddPage({Key? key}) : super(key: key);

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

class _AddPageState extends State<AddPage> {
  List<Asset> images = <Asset>[];
  String _error = NOERROR;
  @override
  Widget build(BuildContext context) {
    if (images.isEmpty)
      return AppLayout(logicalHeight * 0.15);
    else
      return AppLayout(logicalHeight * 0.01);
  }

  List<Asset> getImages() {
    return images;
  }

  Widget AppLayout(double distanceFromImages) {
    return Scaffold(
      appBar: AppBar(
        leading: BackButton(color: Colors.white),
        title: Text(CREATEPROJECT),
        actions: <Widget>[
          IconButton(
            icon: Icon(
              Icons.check,
              color: Colors.white,
            ),
            onPressed: () {
              SavedData _savedData = SavedData(
                  savedName._nameFieldState.myController.value.text,
                  savedDescription
                      ._descriptionFieldState.myController.value.text,
                  savedPage._addPageState.getImages());
              print(_savedData.saved_Project_Description);
              print(_savedData.saved_Project_Name);
              print(_savedData.saved_images.toString());
              saveNewProject(_savedData);
            },
          ),
        ],
      ),
      body: Column(children: <Widget>[
        Padding(
          padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
          child: Center(
              child: Container(
                  height: logicalHeight * 0.05,
                  width: logicalWidth * 0.9,
                  child: savedName)),
        ),
        Padding(
            padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
            child: ElevatedButton(
              child: Text(PICKIMAGES),
              onPressed: loadAssets,
            )),
        Center(
            child: Padding(
                padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
                child: getWidget())),
        Padding(
          padding: EdgeInsets.fromLTRB(
              logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
          child: Center(child: Container(child: savedDescription)),
        ),
      ]),
    );
  }

  Widget getWidget() {
    if (images.length > 0) {
      return Container(
          height: logicalHeight * 0.4,
          width: logicalWidth * 0.8,
          child: ImagePages());
    } else {
      return Padding(
          padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.15, 0, 0),
          child: Container(child: Text(NOPICTURESSELECTED)));
    }
  }

  PageView ImagePages() {
    final PageController controller = PageController(initialPage: 0);
    List<Widget> children = [];
    images.forEach((element) {
      children.add(Padding(
          padding: EdgeInsets.fromLTRB(
              logicalWidth * 0.01, 0, logicalWidth * 0.01, 0),
          child: AssetThumb(
            asset: element,
            width: 1000,
            height: 1000,
          )));
    });
    return PageView(
      scrollDirection: Axis.horizontal,
      controller: controller,
      children: children,
    );
  }
}

class ProjectNameField extends StatefulWidget {
  ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
  @override
  ProjectNameFieldState createState() {
    return _nameFieldState;
  }
}

class ProjectNameFieldState extends State<ProjectNameField> {
  final myController = TextEditingController();
  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return TextField(
      controller: myController,
      decoration: InputDecoration(
        border: UnderlineInputBorder(),
        hintText: PROJECTNAME,
      ),
      maxLength: 30,
    );
  }
}

class ProjectDescriptionField extends StatefulWidget {
  ProjectDescriptionFieldState _descriptionFieldState =
      ProjectDescriptionFieldState();
  @override
  ProjectDescriptionFieldState createState() {
    return _descriptionFieldState;
  }
}

class ProjectDescriptionFieldState extends State<ProjectDescriptionField> {
  final myController = TextEditingController();

  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return TextField(
      controller: myController,
      decoration: InputDecoration(
        border: UnderlineInputBorder(),
        hintText: PROJECTDESCRIPTION,
      ),
      minLines: 1,
      maxLines: 5,
      maxLength: 5000,
    );
  }
}

class SavedData {
  String saved_Project_Name = "";
  String saved_Project_Description = "";
  List<Asset> saved_images = [];

  SavedData(String saved_Project_Name, String saved_Project_Description,
      List<Asset> saved_images) {
    this.saved_Project_Name = saved_Project_Name;
    this.saved_Project_Description = saved_Project_Description;
    this.saved_images = saved_images;
  }
}

I believe the problem lies here (ProjectNameField and ProjectDescriptionField are essentially the same, with only minor differences):

class ProjectNameField extends StatefulWidget {
  ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
  @override
  ProjectNameFieldState createState() {
    return _nameFieldState;
  }
}

class ProjectNameFieldState extends State<ProjectNameField> {
  final myController = TextEditingController();
  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    myController.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return TextField(
      controller: myController,
      decoration: InputDecoration(
        border: UnderlineInputBorder(),
        hintText: PROJECTNAME,
      ),
      maxLength: 30,
    );
  }
}

When I let createState() return _nameFieldState instead of ProjectNameFieldState(), the following error occurs:

The createState function for ProjectDescriptionField returned an old or invalid state instance: ProjectDescriptionField, which is not null, violating the contract for createState.
'package:flutter/src/widgets/framework.dart':
Failed assertion: line 4681 pos 7: 'state._widget == null'

However, when I return ProjectNameFieldState(), then this code

onPressed: () {
              SavedData _savedData = SavedData(
                  savedName._nameFieldState.myController.value.text,
                  savedDescription
                      ._descriptionFieldState.myController.value.text,
                  savedPage._addPageState.getImages());
              print(_savedData.saved_Project_Description);
              print(_savedData.saved_Project_Name);
              print(_savedData.saved_images.toString());
              saveNewProject(_savedData);
            }

does not save the project name. How can I get rid of this error and save the project name and project description? Thank you!

Upvotes: 0

Views: 1597

Answers (1)

Mustafa Bhatkar
Mustafa Bhatkar

Reputation: 355

You can use the Form and TextFormFiled Widgets to easily save the value in your variable and also validate the form fields.

Here is the code snippet:

Declare a GlobalKey for the Form Widget

final _formKey = GlobalKey<FormState>();

In your Scaffold body where you will putting your TextFormFiled add the Form Widget as the parent for all the fields

Form(
    key: _formKey,
    child: Column(children: <Widget>[
      Padding(
        padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
        child: Center(
            child: Container(
                height: logicalHeight * 0.05,
                width: logicalWidth * 0.9,
                child: TextFormField(
                controller: projectNameController,
                decoration: InputDecoration(
                    hintText: PROJECTNAME,
                     border: UnderlineInputBorder()),
                     maxLength: 30
                validator: (value) {
                  if (value.isEmpty) {
                    return 'This is a required field';
                  }
                  return null;
                },
                onSaved: (value) => savedProjectName = value),
            )),
      ),
      Padding(
          padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
          child: ElevatedButton(
            child: Text(PICKIMAGES),
            onPressed: loadAssets,
          )),
      Center(
          child: Padding(
              padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
              child: getWidget())),
      Padding(
        padding: EdgeInsets.fromLTRB(
            logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
        child: Center(child: Container(child: TextFormField(
                controller:projectDescriptionController ,
                decoration: InputDecoration(
                    hintText: PROJECTDESCRIPTION,
                     border: UnderlineInputBorder()),
                      minLines: 1,
                      maxLines: 5,
                      maxLength: 5000,
                validator: (value) {
                  if (value.isEmpty) {
                    return 'This is a required field';
                  }
                  return null;
                },
                onSaved: (value) => savedProjectDescription = value))),
      ),
    ]),
  ),

Now in your IconButton's onPressed validate the form and use the save function to save the TextFormField values in your variable.

final form = _formKey.currentState;
          if (form.validate()) {
            form.save();
          }

Now here in the above code form.validate() will invoke the validator and form.save() will invoke onSaved property of the TextFormField

Here is the complete code:

import 'package:flutter/material.dart';


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

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

class _AddPageState extends State<AddPage> {
  final _formKey = GlobalKey<FormState>();
  String savedProjectName;
  String savedProjectDescription;

  final projectNameController = TextEditingController();
  final projectDescriptionController = TextEditingController();
  double logicalHeight; //your logical height
  double logicalWidth; //your logical widgth


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: BackButton(color: Colors.white),
        title: Text(CREATEPROJECT),
        actions: <Widget>[
          IconButton(
            icon: Icon(
              Icons.check,
              color: Colors.white,
            ),
            onPressed: () {
              final form = _formKey.currentState;
              if (form.validate()) {
                form.save();
              } 
            },
          ),
        ],
      ),
      body:Form(
        key: _formKey,
        child: Column(children: <Widget>[
          Padding(
            padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
            child: Center(
                child: Container(
                    height: logicalHeight * 0.05,
                    width: logicalWidth * 0.9,
                    child: TextFormField(
                    controller: projectNameController,
                    decoration: InputDecoration(
                        hintText: PROJECTNAME,
                         border: UnderlineInputBorder()),
                         maxLength: 30,
                    validator: (value) {
                      if (value.isEmpty) {
                        return 'This is a required field';
                      }
                      return null;
                    },
                    onSaved: (value) => savedProjectName = value),
                )),
          ),
          Padding(
              padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
              child: ElevatedButton(
                child: Text(PICKIMAGES),
                onPressed: loadAssets,
              )),
          Center(
              child: Padding(
                  padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
                  child: getWidget())),
          Padding(
            padding: EdgeInsets.fromLTRB(
                logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
            child: Center(child: Container(child: TextFormField(
                    controller:projectDescriptionController ,
                    decoration: InputDecoration(
                        hintText: PROJECTDESCRIPTION,
                         border: UnderlineInputBorder()),
                          minLines: 1,
                          maxLines: 5,
                          maxLength: 5000,
                    validator: (value) {
                      if (value.isEmpty) {
                        return 'This is a required field';
                      }
                      return null;
                    },
                    onSaved: (value) => savedProjectDescription = value),)),
          ),
        ]),
      ),
    );
  }

}

Upvotes: 1

Related Questions