0

I am trying to implement an UI as below.

App UI figure

User types his name in the TextField widget and press the button. His name will show as a text widget below the button. I know the StatefulWidget should be used to rebuild the text widget for showing the user input name. However, I am not sure what widgets should be included in the StatefulWidget for the best practice. I tried two different implementations. The first one puts the button and the text widget into the StatefulWidget. The code is

import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var appTitle = const Text('Flutter App');

    var appBody = const AppBody2();

    var appBar = AppBar(
      title: appTitle,
    );

    var app = MaterialApp(
      home: Scaffold(
        appBar: appBar,
        body: appBody,
      ),
    );

    return app;
  }
}

final nameController = TextEditingController();

class AppBody2 extends StatelessWidget {
  const AppBody2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final nameField = TextField(
      controller: nameController,
      style: const TextStyle(fontSize: 20),
      decoration: const InputDecoration(
        labelText: 'Your name',
        labelStyle: TextStyle(fontSize: 20),
      ),
    );

    final nameWidget = _NameButtonAndText();

    final widget = Center(
      child: Column(
        children: <Widget>[
          Container(child: nameField, width: 200, margin: const EdgeInsets.symmetric(vertical: 10),),
          Container(child: nameWidget, margin: const EdgeInsets.symmetric(vertical: 10),),
        ],
      ),
    );

    return widget;
  }
}

class _NameButtonAndText extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _NameButtonAndTextState();
}

class _NameButtonAndTextState extends State<_NameButtonAndText> {
  @override
  Widget build(BuildContext context) {

    final btn = ElevatedButton(
      child: const Text('OK'),
      onPressed: () => setState(() {}),
    );

    final widget = Center(
      child: Column(
        children: <Widget>[
          Container(child: btn, margin: const EdgeInsets.symmetric(vertical: 10),),
          Container(child: Text(nameController.text, style: const TextStyle(fontSize: 20)),),
        ],
      ),
    );

    return widget;
  }
}

I think there are two bad things in this implementation. Firstly, the TextEditingController is a global object and, secondly, the button and the text widget are recreated when the user presses the button. The recreation of the button should be unnecessary because it doesn't change. So I tried the 2nd implementation.

import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var appTitle = const Text('Flutter App');

    var appBody = const AppBody();

    var appBar = AppBar(
      title: appTitle,
    );

    var app = MaterialApp(
      home: Scaffold(
        appBar: appBar,
        body: appBody,
      ),
    );

    return app;
  }
}

class AppBody extends StatelessWidget {
  const AppBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final nameController = TextEditingController();
    final nameField = TextField(
      controller: nameController,
      style: const TextStyle(fontSize: 20),
      decoration: const InputDecoration(
        labelText: 'Your name',
        labelStyle: TextStyle(fontSize: 20),
      ),
    );

    final nameWidget = _NameWidget(GlobalKey<_NameWidgetState>());

    final btn = ElevatedButton(
      child: Text('OK'),
      onPressed: () => nameWidget.setName(nameController.text),
    );

    final widget = Center(
      child: Column(
        children: <Widget>[
          Container(child: nameField, width: 200, margin: const EdgeInsets.symmetric(vertical: 10),),
          Container(child: btn, margin: const EdgeInsets.symmetric(vertical: 10),),
          Container(child: nameWidget, margin: const EdgeInsets.symmetric(vertical: 10),),
        ],
      ),
    );

    return widget;
  }
}

class _NameWidget extends StatefulWidget {
  final GlobalKey<_NameWidgetState> _key;

  _NameWidget(this._key): super (key: _key);

  @override
  State<StatefulWidget> createState() => _NameWidgetState();

  setName(String name) {
    _key.currentState?.setName(name);
  }
}

class _NameWidgetState extends State<_NameWidget> {
  String _name = '';

  @override
  Widget build(BuildContext context) {
    final widget = Text(_name,
        style: const TextStyle(fontSize: 20));
    return widget;
  }

  setName(String name) {
    setState(() {
      _name = name;
    });
  }
}

I wrapped only the text widget into the StatefulWidget. When user inputs his name and presses the button. Only the text widget is rebuilded to show the input name. However, the 2nd implementation used a GlobalKey, which seems not good for efficiency considerations. I wonder which one is better between these two implementations or there is some other best practice for this case. Thanks for your reading and comments.

Henry
  • 41
  • 3
  • What was your problem – Ravindra S. Patil Oct 12 '21 at 17:30
  • I got two implementations for the same work but was not sure which one was better, or maybe there exists another best practice for this case. Thanks for you comments. – Henry Oct 13 '21 at 02:45
  • Have you tried looking up `ValueListenableBuilder` or State management package like Provider? They allowed you to only rebuild specific Widgets without much trouble. Simple Widgets like yours only requires `ValueListenableBuilder` Widget at most. – IcyHerrscher Oct 13 '21 at 03:31
  • @IcyHerrscher ValueListenableBuilder works fine, and it needs less code than the two posted methods. Thanks for your comments. – Henry Oct 14 '21 at 06:45

0 Answers0