Seyit Gokce
Seyit Gokce

Reputation: 363

Randomly Sized Containers Are Unexpectedly Resized when Keyboard is Opened in Flutter

I am currently creating 12 containers for testing purposes, whose dimensions are randomly determined in proportion to the screen size of the device during application opening. I show them on the screen as the bottom element of the column structure in a SingleChildScrollView that I wrapped in a container. As you can see from the image, when I click on the top search bar, the keyboard normally opens, but when the keyboard is opened, the containers are resized. When the keyboard is closed, these are resized again.

Unexpectedly Resizing Containers

What I want it to happen is that the containers are sized one time only at application startup. I want them not to be resized later.

Here is the function I wrote to create containers:

_listAllCategories(int amount){
    List<Widget> _even = [];
    List<Widget> _single = [];

    double width = MediaQuery.of(context).size.width*(5/11);
    double height = MediaQuery.of(context).size.height/5;

    for(int i = 0; i < amount; i++){
      double rndHeight = height*randomDoubleInRange(min: 0.7, max: 1.3);
      i.isEven ?
      _even.add(Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          width: width,
          height: rndHeight,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: Colors.black12
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Padding(
                padding: const EdgeInsets.all(4.0),
                child: Text(
                  '#etiket',
                  style: TextStyle(
                    fontFamily: 'JetBrainsMono-Regular',
                  ),
                ),
              ),
              Container(
                width: width,
                height: rndHeight - (height/5.5),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(12),
                    color: Colors.grey.shade900
                ),
              ),
            ],
          ),
        ),
      )):
      _single.add(Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          width: width,
          height: rndHeight,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: Colors.black12
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Padding(
                padding: const EdgeInsets.all(4.0),
                child: Text(
                  '#etiket',
                  style: TextStyle(
                    fontFamily: 'JetBrainsMono-Regular',
                  ),
                ),
              ),
              Container(
                width: MediaQuery.of(context).size.width*(5/11),
                height: rndHeight - (height/5.5),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(12),
                    color: Colors.grey.shade900
                ),
              ),
            ],
          ),
        ),
      ));
    }

    return Container(
      height: MediaQuery.of(context).size.height*(3.8/5),
      child: SingleChildScrollView(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: _single
            ),
            Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: _even
            ),
          ],
        ),
      ),
    );
  }

The function I use to generate a random double value in the range I want:

double randomDoubleInRange({double min = 0.0, double max = 1.0}) {
    return Random().nextDouble() * (max - min + 1) + min;
  }

My build function is here:

var _searchBarController;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _searchBarController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      drawer: myDrawer(),
      backgroundColor: Color.fromARGB(255, 16,16,16),
      body: Center(
            child: Container(
              child: Column(
                children: [
                  SizedBox(height: 48,),
                  InkWell(
                    child: Container(
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          Padding(
                            padding: const EdgeInsets.only(left: 18.0),
                            child: Icon(Icons.search),
                          ),
                          Container(
                            width: MediaQuery.of(context).size.width*(6.5/10),
                            height: 50,
                            child: Center(
                              child: TextFormField(
                                style: TextStyle(fontFamily: 'JetBrainsMono-Regular'),
                                controller: _searchBarController,
                                autofocus: false,
                                decoration: InputDecoration(
                                  border: InputBorder.none,
                                  focusedBorder: InputBorder.none,
                                  enabledBorder: InputBorder.none,
                                  errorBorder: InputBorder.none,
                                  disabledBorder: InputBorder.none,
                                  contentPadding:
                                  EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15),
                                  hintText: "Etiketleri arayın.",
                                  hintStyle: TextStyle(fontFamily: 'JetBrainsMono-Light')
                                ),
                              ),
                            ),
                          ),
                          PopupMenuButton(
                            child: Padding(
                              padding: const EdgeInsets.only(left: 18.0),
                              child: Icon(Icons.more_vert_outlined),
                            ),
                            itemBuilder: (context){
                              return List.generate(5, (index){
                                return PopupMenuItem(child: Text('Item $index', style: TextStyle(fontFamily: 'JetBrainsMono-Regular'),));
                              });
                            },
                          ),
                        ],
                      ),
                      width: MediaQuery.of(context).size.width*(9/10),
                      height: 50,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(12),
                        color: Color.fromARGB(255,30,30,30),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(24.0),
                    child: Text(
                      'Tüm Etiketler',
                       style: TextStyle(
                         fontFamily: 'JetBrainsMono-Regular',
                         fontSize: 20
                       ),
                    ),
                  ),
                  _listAllCategories(12)
                ],
              ),
            ),
          ),

    );
  }

Upvotes: 2

Views: 221

Answers (2)

Zac
Zac

Reputation: 1103

Every time the keyboard shows up or goes back down, the build() function in called in Flutter, because the look of the screen is changing. That's why you can't have a TextField in a Stateless Widget (try it out, the keyboard will try popping up, but will immediately go back down).

To fix this, you can assign the value coming from listAllCategories to a widget variable during initState(), and then use that variable in your build function.

Since you need the context variable in your function, you can add a postframe callback like so:

So your updated code is:

In your class:

var _searchBarController;
Widget categoryListing;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _searchBarController = TextEditingController();
    WidgetsBinding.instance.addPostFrameCallback((_) => initializeCategoryWidget(context));
  }

    initializeCategoryWidget(BuildContext context) {
        setState(() {
            categoryListing = _listAllCategories(12);
        });
    }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      drawer: myDrawer(),
      backgroundColor: Color.fromARGB(255, 16,16,16),
      body: Center(
            child: Container(
              child: Column(
                children: [
                  SizedBox(height: 48,),
                  InkWell(
                    child: Container(
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: [
                          Padding(
                            padding: const EdgeInsets.only(left: 18.0),
                            child: Icon(Icons.search),
                          ),
                          Container(
                            width: MediaQuery.of(context).size.width*(6.5/10),
                            height: 50,
                            child: Center(
                              child: TextFormField(
                                style: TextStyle(fontFamily: 'JetBrainsMono-Regular'),
                                controller: _searchBarController,
                                autofocus: false,
                                decoration: InputDecoration(
                                  border: InputBorder.none,
                                  focusedBorder: InputBorder.none,
                                  enabledBorder: InputBorder.none,
                                  errorBorder: InputBorder.none,
                                  disabledBorder: InputBorder.none,
                                  contentPadding:
                                  EdgeInsets.only(left: 15, bottom: 11, top: 11, right: 15),
                                  hintText: "Etiketleri arayın.",
                                  hintStyle: TextStyle(fontFamily: 'JetBrainsMono-Light')
                                ),
                              ),
                            ),
                          ),
                          PopupMenuButton(
                            child: Padding(
                              padding: const EdgeInsets.only(left: 18.0),
                              child: Icon(Icons.more_vert_outlined),
                            ),
                            itemBuilder: (context){
                              return List.generate(5, (index){
                                return PopupMenuItem(child: Text('Item $index', style: TextStyle(fontFamily: 'JetBrainsMono-Regular'),));
                              });
                            },
                          ),
                        ],
                      ),
                      width: MediaQuery.of(context).size.width*(9/10),
                      height: 50,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(12),
                        color: Color.fromARGB(255,30,30,30),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(24.0),
                    child: Text(
                      'Tüm Etiketler',
                       style: TextStyle(
                         fontFamily: 'JetBrainsMono-Regular',
                         fontSize: 20
                       ),
                    ),
                  ),
                  (categoryListing == null)?Container():categoryListing,
                ],
              ),
            ),
          ),

    );
  }

Upvotes: 1

ahmetakil
ahmetakil

Reputation: 908

You are generating new random sizes for each container everytime build runs instead you can create another function that will be run once in init state, That function can generate all of the sizes and your _listAllCategories function can simply use the sizes that was generated.

Upvotes: 0

Related Questions