[Flutter 입문] 기본 레이아웃 구성(2): 상태 관리

김동연·2025년 3월 27일

2. 상태 관리 (State Management)

  • Flutter에서 상태(state)는 UI의 데이터를 관리하는 핵심 요소입니다.
  • 상태를 효율적으로 관리하면 앱의 성능을 높이고, 유지보수를 쉽게 할 수 있습니다.
  • 상태 관리는 크게 로컬 상태 관리와 전역 상태 관리로 나뉩니다.

📌 2-1. 상태(State)의 개념

  • Flutter에서 상태는 변할 수 있는 데이터를 의미합니다.

  • 예를 들어, 버튼을 눌렀을 때 숫자가 증가하는 앱에서는 숫자가 상태(state) 입니다.

  • UI를 동적으로 변경할 수 있으며, 상태가 변할 때마다 UI를 다시 빌드합니다.

  • Flutter에서 상태를 관리하는 방식은 크게 두 가지입니다.

상태 관리 방식설명
로컬 상태 관리개별 위젯 내부에서만 상태를 관리 (예: StatefulWidget)
전역 상태 관리여러 위젯에서 상태를 공유 (예: Provider, Riverpod, Bloc 등)

📌 2-2. StatefulWidget을 이용한 로컬 상태 관리

  • 작은 규모의 상태는 StatefulWidget에서 직접 관리할 수 있습니다.
  • 예를 들어, 버튼을 눌러 숫자를 증가시키는 카운터 앱을 만들어 봅시다.

StatefulWidget을 사용한 카운터 예제

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterScreen(),
    );
  }
}

class CounterScreen extends StatefulWidget {
  
  _CounterScreenState createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _counter = 0; // 상태 변수

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("카운터 앱")),
      body: Center(
        child: Text("카운트: $_counter", style: TextStyle(fontSize: 30)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

🔹 설명

  • StatefulWidget을 사용하여 상태를 관리합니다.
  • _counter 변수는 setState()를 호출해야 UI가 갱신됩니다.

🔹 주요 속성

속성설명
StatefulWidget상태를 가질 수 있는 위젯
setState()상태 변경을 알리고 UI를 다시 그리기 위한 메서드

📌 2-3. InheritedWidget을 이용한 상태 공유

  • InheritedWidget부모에서 자식 위젯으로 상태를 전달하는 기본적인 방법입니다.

InheritedWidget 예제

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class CounterProvider extends InheritedWidget {
  final int counter;
  final Function() increment;

  CounterProvider({required this.counter, required this.increment, required Widget child}) : super(child: child);

  static CounterProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterProvider>();
  }

  
  bool updateShouldNotify(CounterProvider oldWidget) {
    return oldWidget.counter != counter;
  }
}

class MyApp extends StatefulWidget {
  
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _counter = 0;

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

  
  Widget build(BuildContext context) {
    return CounterProvider(
      counter: _counter,
      increment: _incrementCounter,
      child: MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

class CounterScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final provider = CounterProvider.of(context);

    return Scaffold(
      appBar: AppBar(title: Text("InheritedWidget 예제")),
      body: Center(
        child: Text("카운트: ${provider?.counter}", style: TextStyle(fontSize: 30)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: provider?.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

🔹 설명

  • CounterProviderInheritedWidget을 상속받아 상태를 공유합니다.
  • context.dependOnInheritedWidgetOfExactType<T>()을 사용하여 데이터를 가져옵니다.

📌 2-4. Provider를 이용한 상태 관리 (전역 관리)

  • ProviderFlutter에서 가장 많이 사용하는 상태 관리 패턴입니다.
  • InheritedWidget을 더 쉽게 사용할 수 있도록 만든 라이브러리입니다.

Provider 설치

dependencies:
  provider: ^6.0.5

Provider 예제

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterProvider(),
      child: MyApp(),
    ),
  );
}

class CounterProvider extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners(); // UI 갱신
  }
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final counterProvider = Provider.of<CounterProvider>(context);

    return Scaffold(
      appBar: AppBar(title: Text("Provider 예제")),
      body: Center(
        child: Text("카운트: ${counterProvider.counter}", style: TextStyle(fontSize: 30)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counterProvider.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

🔹 설명

  • ChangeNotifierProvider를 사용하여 CounterProvider를 앱 전체에서 공유합니다.
  • notifyListeners()를 호출하면 UI가 자동으로 업데이트됩니다.

📌 2-5. Riverpod을 이용한 상태 관리 (전역 관리)

  • RiverpodProvider를 기반으로 한 더 안전하고 강력한 상태 관리 라이브러리입니다.

Riverpod 설치

dependencies:
  flutter_riverpod: ^2.0.2

Riverpod 예제

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateProvider<int>((ref) => 0);

void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterScreen(),
    );
  }
}

class CounterScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text("Riverpod 예제")),
      body: Center(
        child: Text("카운트: $counter", style: TextStyle(fontSize: 30)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).state++,
        child: Icon(Icons.add),
      ),
    );
  }
}

🔹 설명

  • StateProvider를 사용하여 상태를 관리합니다.
  • ref.watch()를 사용하면 상태가 변경될 때 자동으로 UI가 갱신됩니다.

📌 2-6. Bloc 패턴을 이용한 상태 관리 (전역 관리)

  • BLoC(Business Logic Component) 패턴은 Flutter에서 복잡한 상태 관리를 분리하여 효율적으로 관리할 수 있는 방법입니다.
  • BLoC 패턴은 상태와 이벤트를 스트림을 통해 분리하여 관리하는 방식으로, 앱의 비즈니스 로직을 UI와 분리하는 데 유리합니다.
  • BLoC 패턴은 flutter_bloc 라이브러리를 사용하여 구현합니다.

Bloc 설치

dependencies:
  flutter_bloc: ^8.0.0

Bloc 예제

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(MyApp());
}

// 이벤트 정의
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

// 상태 정의
class CounterState {
  final int counter;
  CounterState(this.counter);
}

// BLoC 정의
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0));

  
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.counter + 1);
    }
  }
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

class CounterScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("BLoC 예제")),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text("카운트: ${state.counter}", style: TextStyle(fontSize: 30));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
        child: Icon(Icons.add),
      ),
    );
  }
}

🔹 설명

  • CounterBlocBloc 클래스를 상속받고 CounterEvent를 처리하여 CounterState를 업데이트합니다.
  • BlocProvider를 사용하여 Bloc을 앱에 제공하고, BlocBuilder로 상태를 UI에 반영합니다.
  • IncrementEvent가 발생하면 mapEventToState 함수에서 상태를 업데이트하고 UI가 자동으로 갱신됩니다.

이 외에도 각 상태 관리 방식의 특징과 사용 예시를 설명할 때, StatefulWidget, InheritedWidget, Provider, Riverpod, Bloc 각각의 장단점을 비교하는 표가 유용하게 추가될 수 있습니다.


📌 2-7. 상태 관리 비교

상태 관리 방식사용 용도특징
StatefulWidget로컬 상태간단한 UI 상태 관리, 상태가 적을 때 유용
InheritedWidget상태 공유상태를 자식 위젯으로 전달, 직접 구현 어려움
Provider전역 상태사용이 쉬움, 가장 많이 사용됨
Riverpod전역 상태더 강력하고 안전함
Bloc전역 상태복잡한 상태 관리 및 비즈니스 로직 분리

📌 정리

  • 작은 상태StatefulWidget으로 관리
  • 여러 위젯이 공유하는 상태Provider 사용
  • 더 강력한 전역 상태 관리가 필요하면 Riverpod 사용
  • 비즈니스 로직 분리와 복잡한 상태 관리가 필요하면 BLoC 패턴 사용

0개의 댓글