Programming/Mobile

[flutter] setState 없이 상태 값 변경 - ValueNotifier

BitSense 2021. 3. 12. 00:47

하나의 StatefulWidget에서 상태 값을 관리하는 방법은 setState()를 사용하는 것입니다. setState() 없이 상태 값을 관리할 수도 있는데, ValueNotifier를 사용하는 것입니다. ValueNotifier가 좋은 것은 다른 위젯에서 변경한 상태값을 적용할 수 있습니다.

setState() 없이 상태값을 변경하는 예제

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<int> _counter = ValueNotifier<int>(0); // ValueNotifier 변수 선언
  final Widget goodJob = const Text('Good job!');
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            ValueListenableBuilder(
              valueListenable: _counter, // 사용할 변수를 지정. _counter가 변경 되면 자동 호출
              builder: (BuildContext context, int value, Widget? child) {
                // value = _counter 로 적용
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    Text('$value'),
                    child!, // child는 아래 지정된 위젯으로 치환됨
                  ],
                );
              },
              child: goodJob,
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.plus_one),
        onPressed: () => _counter.value += 1, // _counter.value 값이 수정되면 자동 호출
      ),
    );
  }
}

ValueNotifier로 수정한 값을 다른 위젯에서도 사용하고 싶을때

ValueNotifier가 영향을 미치는 범위는 ValueListenableBuilder() 내 입니다. 전체 위젯에 값을 적용하기 위해서는 setState()를 사용해야 하나 그럴 경우 오류가 발생합니다. build가 완료되기 전에 setState()를 호출하게 되면 빨간 화면을 마주하게 됩니다.

이 오류를 피하면서 setState()를 사용하는 방법은 WidgetsBinding.instance.addPostFrameCallback() 입니다.

addPostFrameCallback()는 페이지 빌드 후에 비동기로 콜백함수를 호출하기 때문에 빨간 화면을 피할 수 없습니다.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<int> _counter = ValueNotifier<int>(0); // ValueNotifier 변수 선언
  final Widget goodJob = const Text('Good job!');
  
  int _count = 0;
  
  void setCount(int counter) {
    setState(() {
      _count = counter + 1;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            ValueListenableBuilder(
              valueListenable: _counter, // 사용할 변수를 지정. _counter가 변경 되면 자동 호출
              builder: (BuildContext context, int value, Widget? child) {
                // setState() 가 있는 함수를 호출
                WidgetsBinding.instance.addPostFrameCallback((_) {
                  setCount(value);
                });
                
                // value = _counter 로 적용
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    Text('$value'),
                    child!, // child는 아래 지정된 위젯으로 치환됨
                  ],
                );
              },
              child: goodJob,
            ),
            Container(
              child: Text('$_count')
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.plus_one),
        onPressed: () => _counter.value += 1, // _counter.value 값이 수정되면 자동 호출
      ),
    );
  }
}
반응형