I would like to keep multiple TextField to hold values

Viewed 45

I am developing a fill-in-the-blanks quiz app. Each quiz contains 5 sentences and they are arranged by PageView. The sentences are retrieved from a List. Each sentence is a TextWithBlanks class and they have several BlankWord class (which are TextField).

class TextWithBlanks extends StatefulWidget {
  final String text;
  static final regex = RegExp("(?={)|(?<=})");
  List correctList = [];

  TextWithBlanks({Key? key, required this.text, required this.correctList})
      : super(key: key);

  @override
  State<TextWithBlanks> createState() => _TextWithBlanksState();
}

class _TextWithBlanksState extends State<TextWithBlanks> {
  @override
  Widget build(BuildContext context) {
    final split = widget.text.split(TextWithBlanks.regex);
    return Padding(
      padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
      child: Text.rich(
        TextSpan(
          style: const TextStyle(fontSize: 15, height: 3.0),
          children: <InlineSpan>[
            for (String text in split)
              text.startsWith('{')
                  ? WidgetSpan(
                      child: blankWord(text.substring(1, text.length - 1),
                          widget.correctList),
                    )
                  : TextSpan(text: text),
          ],
        ),
      ),
    );
  }
}
class blankWord extends StatefulWidget {
  final String answer;
  int answerLength = 0;
  double answerWidth = 0.0;
  String answerHint = "";
  List correctList;
  String value = "";
  bool answerBool = false;

  blankWord(this.answer, this.correctList, {Key? key}) : super(key: key) {
    answerLength = answer.length;
    answerWidth = answerLength * 15.0;
    answerHint = answer;
  }

  @override
  State<blankWord> createState() => _blankWordState();
}

class _blankWordState extends State<blankWord> {
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.answerWidth,
      child: TextFormField(
        maxLines: null,
        cursorColor: Colors.grey,
        style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        autofocus: false,
        maxLength: widget.answerLength + 5,
        onChanged: (enterWord) {
          widget.value = enterWord;
          if (enterWord == widget.answer) {
            if (widget.answerBool == false) {
              widget.answerBool = true;
              widget.correctList.add(widget.answer);
            }
          } else {
            if (widget.answerBool == true) {
              widget.answerBool = false;
              widget.correctList.remove(widget.answer);
            }
          }
        },
        decoration: InputDecoration(
          counterText: "",
          hintText: widget.answerHint,
          hintStyle: const TextStyle(color: Colors.grey, fontSize: 12),
        ),
      ),
    );
  }
}

If I enter text in one TextWithBlank BlankWord and then enter text in another TextWithBlank BlankWord, what I entered before disappears. I want to keep the value in the BlankWord (TextField) of each TextWithBlank. What is the best way to do this? Thank you.

TextWithBlank is included in the QuizText class.

class PlayGame extends StatefulWidget {
  final List document;
  List correctList = [];
  PlayGame({Key? key, required this.document}) : super(key: key);

  @override
  State<PlayGame> createState() => _PlayGameState();
}

class _PlayGameState extends State<PlayGame> {
  int quizNum = 0;
  int quizCount = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Center(
          child: Text(
            "$quizCount/5",
            style: const TextStyle(
                fontSize: 25,
                fontStyle: FontStyle.italic,
                fontWeight: FontWeight.bold),
          ),
        ),
        actions: [
          if (quizCount == 5)
            GestureDetector(
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => Result(),
                  ),
                );
                print(widget.correctList.length);
              },
              child: Row(
                children: const [
                  Text(
                    "採点する",
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  Icon(
                    Icons.arrow_forward,
                    size: 20,
                  ),
                  SizedBox(
                    width: 10,
                  )
                ],
              ),
            )
          else
            const SizedBox.shrink()
        ],
        automaticallyImplyLeading: false,
      ),
      body: PageView(
        onPageChanged: (counter) {
          setState(
            () {
              quizCount = counter + 1;
            },
          );
        },
        children: [
          QuizText(widget: widget, quizNum: 0),
          QuizText(widget: widget, quizNum: 1),
          QuizText(widget: widget, quizNum: 2),
          QuizText(widget: widget, quizNum: 3),
          QuizText(widget: widget, quizNum: 4)
        ],
      ),
    );
  }
}

class QuizText extends StatelessWidget {
  const QuizText({
    Key? key,
    required this.widget,
    required this.quizNum,
  }) : super(key: key);

  final PlayGame widget;
  final int quizNum;

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: const BoxConstraints.expand(),
      child: Padding(
        padding: const EdgeInsets.all(5.0),
        child: Card(
          child: SizedBox(
            height: double.infinity,
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.only(bottom: 30.0),
                child: TextWithBlanks(
                    text: widget.document[quizNum],
                    correctList: widget.correctList),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
1 Answers

Once the widget is not used, it disposes from widget tree and value lost. You can use state-management property. For now I am using AutomaticKeepAliveClientMixin to preserve the widget .

Changes will be here

class _TextWithBlanksState extends State<TextWithBlanks>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);

    final split = widget.text.split(TextWithBlanks.regex);
    return Padding(
      padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
      child: Text.rich(
        TextSpan(
          style: const TextStyle(fontSize: 15, height: 3.0),
          children: <InlineSpan>[
            for (String text in split)
              WidgetSpan(
                child: blankWord(
                  text.substring(1, text.length - 1),
                  widget.correctList,
                ),
              )
          ],
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}
Related