flutterdart

Why does my widget rebuild when I use keyboard


I have this issue of rebuilding widget when The keyboards shows up. I tried to use the sizer package but never could figure out how to get it to work when I go back from this screen everything in the previous screen will rebuild, Please note: If I don't click on the typeaheadwidget such that the keyboard doesn't show up the state is preserved in the previous screen but as soon as the keyboard pops up the widgets get rebuilt Could you please check ?

class SearchScreen extends StatefulWidget {
  @override
  _SearchScreenState createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
  TextEditingController pickUpTextEditingController = TextEditingController();
  TextEditingController dropOffTextEditingController = TextEditingController();

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

  @override
  @mustCallSuper
  Widget build(BuildContext context) {
    String placeAddress =
        Provider.of<AppData>(context).pickUpLocation.placeName ?? "";
    pickUpTextEditingController.text = placeAddress;

    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Stack(
        children: [
          Container(
            height: 250.0,
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.black,
                  blurRadius: 6.0,
                  spreadRadius: 0.5,
                  offset: Offset(0.7, 0.7),
                )
              ],
            ),
            child: Padding(
              padding: EdgeInsets.only(
                  left: 25.0, top: 30.0, right: 25.0, bottom: 20.0),
              child: Column(
                children: [
                  SizedBox(height: 5.0),
                  Stack(
                    children: [
                      GestureDetector(
                          onTap: () {
                            Navigator.pop(
                                //send back data
                                context,
                                dropOffTextEditingController.text);
                          },
                          child: Icon(Icons.arrow_back)),
                      Center(
                        child: Text(
                          "Set Drop Off",
                          style: TextStyle(
                              fontSize: 18.0, fontFamily: "Brand-Bold"),
                        ),
                      )
                    ],
                  ),
                  SizedBox(height: 16.0),
                  Row(
                    children: [
                      Image.asset("images/images/pickicon.png",
                          height: 16.0, width: 16.0),
                      SizedBox(width: 18.0),
                      Expanded(
                          child: Container(
                        decoration: BoxDecoration(
                          color: Colors.grey[400],
                          borderRadius: BorderRadius.circular(5.0),
                        ),
                        child: Padding(
                          padding: EdgeInsets.all(3.0),
                          child: TextField(
                            controller: pickUpTextEditingController,
                            decoration: InputDecoration(
                              hintText: "PickUp Location",
                              fillColor: Colors.grey[400],
                              filled: true,
                              border: InputBorder.none,
                              isDense: true,
                              contentPadding: EdgeInsets.only(
                                  left: 11.0, top: 8.0, bottom: 8.0),
                            ),
                          ),
                        ),
                      ))
                    ],
                  ),
                  SizedBox(height: 10.0),
                  Row(
                    children: [
                      Image.asset("images/images/desticon.png",
                          height: 16.0, width: 16.0),
                      SizedBox(width: 18.0),
                      Expanded(
                        child: Container(
                          decoration: BoxDecoration(
                            color: Colors.grey[400],
                            borderRadius: BorderRadius.circular(5.0),
                          ),
                          child: Padding(
                            padding: EdgeInsets.all(3.0),
                            child: TypeAheadField(
                              itemBuilder: null,
                              onSuggestionSelected: null,
                              suggestionsCallback: null,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Solution

  • You should not try to control when the build method is called. Flutter will call build when it decides it needs to (e.g. keyboard appears, device rotated, parent rebuilds, etc).

    Instead, you should make sure that your build method is a "pure" function. In Flutter specifically, this means that you should not perform any action with "side-effects" (basically anything which modifies the state of the app).

    For example:

    Widget build(BuildContext context) {
      final x = 2 + 3;  // fine, nothing else is modified
      final state = context.watch<MyModel>();  // also fine, only reading data
      controller.text = "hello";  // BAD, modifies the state of the app
    
      return ...;
    }
    

    Instead, you should move your logic with side effects into other lifecycle methods (e.g. initState(), didChangeDepencencies(), etc).

    For example, if you want to set your text field to a particular string when it first appears, you can use initState:

    class _SearchScreenState extends State<SearchScreen> {
      @override
      void initState() {
        super.initState();
        final data = context.read<AppData>();
        controller.text = data.pickUpLocation.placeName ?? "";
      }
    
      Widget build(BuildContext context) {
        // ...
      }
    }
    

    Now build() can be called whenever it has to be, without resetting the state of your text field.

    Note that, even if there was some way to prevent your widget from being rebuilt, this is also likely not what you want, since the UI would not update to accommodate the keyboard.