개요

Flutter에서 setState는 가장 기본적인 상태 관리 방법입니다. StatefulWidget의 상태를 변경하고 UI를 다시 그리도록 하는 메커니즘을 제공합니다. 이번 포스트에서는 setState의 개념과 사용법에 대해 자세히 알아보겠습니다.

 

 

setState의 기본 개념

 

StatefulWidget과 State

// StatefulWidget 예시
class Counter extends StatefulWidget {
  const Counter({Key? key}) : super(key: key);

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

class _CounterState extends State<Counter> {
  int _count = 0;  // 상태 변수

  @override
  Widget build(BuildContext context) {
    return Text('Count: $_count');
  }
}

 

 

setState 기본 사용법

class _CounterState extends State<Counter> {
  int _count = 0;

  void _incrementCounter() {
    setState(() {
      _count++;  // 상태 변경
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

 

 

setState의 동작 원리

 

State 생명주기

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    // 초기화 코드
  }

  @override
  void dispose() {
    // 정리 코드
    super.dispose();
  }

  @override
  void setState(VoidCallback fn) {
    super.setState(fn);
    // setState가 호출되면 build 메서드가 다시 실행됨
  }

  @override
  Widget build(BuildContext context) {
    // UI 구성
    return Container();
  }
}

 

 

setState 호출 시 일어나는 일

  1. setState 함수 호출
  2. 상태 업데이트
  3. build 메서드 재실행
  4. 위젯 트리 재구성
  5. UI 업데이트

 

 

실전 활용 예시

 

기본적인 카운터 앱

class CounterApp extends StatefulWidget {
  const CounterApp({Key? key}) : super(key: key);

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

class _CounterAppState extends State<CounterApp> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      if (_counter > 0) {
        _counter--;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current Count:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '$_counter',
              style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _decrementCounter,
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: _incrementCounter,
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

 

폼 데이터 관리

class UserForm extends StatefulWidget {
  @override
  _UserFormState createState() => _UserFormState();
}

class _UserFormState extends State<UserForm> {
  String _name = '';
  String _email = '';
  bool _subscribed = false;

  void _updateName(String value) {
    setState(() {
      _name = value;
    });
  }

  void _updateEmail(String value) {
    setState(() {
      _email = value;
    });
  }

  void _toggleSubscription(bool? value) {
    setState(() {
      _subscribed = value ?? false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(16.0),
      child: Column(
        children: [
          TextField(
            onChanged: _updateName,
            decoration: InputDecoration(
              labelText: 'Name',
            ),
          ),
          TextField(
            onChanged: _updateEmail,
            decoration: InputDecoration(
              labelText: 'Email',
            ),
          ),
          CheckboxListTile(
            title: Text('Subscribe to newsletter'),
            value: _subscribed,
            onChanged: _toggleSubscription,
          ),
          ElevatedButton(
            onPressed: () {
              // 폼 제출 로직
              print('Name: $_name');
              print('Email: $_email');
              print('Subscribed: $_subscribed');
            },
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}

 

 

리스트 관리

class TodoList extends StatefulWidget {
  @override
  _TodoListState createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  final List<String> _todos = [];
  final TextEditingController _controller = TextEditingController();

  void _addTodo() {
    if (_controller.text.isNotEmpty) {
      setState(() {
        _todos.add(_controller.text);
        _controller.clear();
      });
    }
  }

  void _removeTodo(int index) {
    setState(() {
      _todos.removeAt(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: EdgeInsets.all(8.0),
          child: Row(
            children: [
              Expanded(
                child: TextField(
                  controller: _controller,
                  decoration: InputDecoration(
                    hintText: 'Enter new todo',
                  ),
                ),
              ),
              IconButton(
                icon: Icon(Icons.add),
                onPressed: _addTodo,
              ),
            ],
          ),
        ),
        Expanded(
          child: ListView.builder(
            itemCount: _todos.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(_todos[index]),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () => _removeTodo(index),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

 

 

setState 사용 시 주의사항

 

비동기 작업에서의 setState

class _MyWidgetState extends State<MyWidget> {
  String _data = '';

  // 잘못된 사용
  void _fetchData() async {
    final response = await API.getData();
    if (!mounted) return;  // 위젯이 여전히 트리에 있는지 확인
    setState(() {
      _data = response;
    });
  }
}

 

성능 고려사항

// 비효율적인 방법
setState(() {
  for (var i = 0; i < 1000; i++) {
    // 상태 변경
  }
});

// 효율적인 방법
void _updateMultipleStates() {
  final updates = List.generate(1000, (index) => index);
  setState(() {
    // 한 번에 모든 상태 업데이트
    _items.addAll(updates);
  });
}

 

setState의 장단점

장점

  • 사용이 간단하고 직관적
  • 작은 규모의 앱에서 효과적
  • Flutter의 기본 제공 기능으로 추가 패키지 불필요

단점

  • 복잡한 상태 관리에는 적합하지 않음
  • 위젯 트리가 깊어지면 상태 전달이 어려움
  • 전역 상태 관리에는 부적합

 

 

마치며

setState는 Flutter에서 가장 기본적인 상태 관리 방식입니다. 작은 규모의 앱이나 로컬 상태 관리에는 매우 효과적이지만, 앱이 커지고 복잡해질수록 다른 상태 관리 솔루션을 고려해야 할 수 있습니다. 다음 포스트에서는 Provider나 Riverpod와 같은 더 강력한 상태 관리 솔루션에 대해 알아보도록 하겠습니다.

'Language > Flutter' 카테고리의 다른 글

[Flutter] 위젯 생명주기(Lifecycle)  (0) 2024.11.20
[Dart] fold() 메소드  (0) 2024.11.19
[Dart] Spread 연산자(...)  (0) 2024.11.17
[Dart] Null Safety  (0) 2024.11.16
[Dart] var, dynamic, final, late, const 키워드  (0) 2024.11.15

+ Recent posts