Search code examples
flutterdartdart-async

Flutter: setState() does not trigger a build


I have a very simple (stateful) widget that contains a Text widget that displays the length of a list which is a member variable of the widget's state.

Inside the initState() method, I override the list variable (formerly being null) with a list that has four elements using setState(). However, the Text widget still shows "0".

The prints I added imply that a rebuild of the widget has not been triggered although my perception was that this is the sole purpose of the setState() method.

Here ist the code:

import 'package:flutter/material.dart';

class Scan extends StatefulWidget {
  @override
  _ScanState createState() => _ScanState();
}

class _ScanState extends State<Scan> {
  List<int> numbers;

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

  @override
  Widget build(BuildContext context) {
    print('Build was scheduled');
    return Center(
      child: Text(
        numbers == null ? '0' : numbers.length.toString()
      )
    );
  }

  Future<List<int>> _getAsyncNumberList() {
    return Future.delayed(Duration(seconds: 5), () => [1, 2, 3, 4]);
  }

  _initializeController() async {
    List<int> newNumbersList = await _getAsyncNumberList();

    print("Number list was updated to list of length ${newNumbersList.length}");

    setState(() {
      numbers = newNumbersList;
    });
  }
}

My question: why does the widget only build once? I would have expected to have at least two builds, the second one being triggered by the execution of setState().


Solution

  • I have the feeling, the answers don't address my question. My question was why the widget only builds once and why setState() does not trigger a second build.

    Answers like "use a FutureBuilder" are not helpful since they completely bypass the question about setState(). So no matter how late the async function finishes, triggering a rebuild should update the UI with the new list when setState() is executed.

    Also, the async function does not finish too early (before build has finished). I made sure it does not by trying WidgetsBinding.instance.addPostFrameCallback which changed: nothing.

    I figured out that the problem was somewhere else. In my main() function the first two lines were:

      SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
      SystemChrome.setPreferredOrientations(
        [DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
      );
    

    which somehow affected the build order. But only on my Huawei P20 Lite, on no other of my test devices, not in the emulator and not on Dartpad.

    So conclusion: Code is fine. My understanding of setState() is also fine. I haven't provided enough context for you to reproduce the error. And my solution was to make the first two lines in the main() function async:

    void main() async {
      await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
      await SystemChrome.setPreferredOrientations(
        [DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
      );
      ...
    }