Flutter에서 상태는 변할 수 있는 데이터를 의미합니다.
예를 들어, 버튼을 눌렀을 때 숫자가 증가하는 앱에서는 숫자가 상태(state) 입니다.
UI를 동적으로 변경할 수 있으며, 상태가 변할 때마다 UI를 다시 빌드합니다.
Flutter에서 상태를 관리하는 방식은 크게 두 가지입니다.
| 상태 관리 방식 | 설명 |
|---|---|
| 로컬 상태 관리 | 개별 위젯 내부에서만 상태를 관리 (예: StatefulWidget) |
| 전역 상태 관리 | 여러 위젯에서 상태를 공유 (예: Provider, Riverpod, Bloc 등) |
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를 다시 그리기 위한 메서드 |
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),
),
);
}
}
CounterProvider는 InheritedWidget을 상속받아 상태를 공유합니다.context.dependOnInheritedWidgetOfExactType<T>()을 사용하여 데이터를 가져옵니다.Provider를 이용한 상태 관리 (전역 관리)Provider는 Flutter에서 가장 많이 사용하는 상태 관리 패턴입니다. 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가 자동으로 업데이트됩니다.Riverpod을 이용한 상태 관리 (전역 관리)Riverpod은 Provider를 기반으로 한 더 안전하고 강력한 상태 관리 라이브러리입니다.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가 갱신됩니다.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),
),
);
}
}
CounterBloc은 Bloc 클래스를 상속받고 CounterEvent를 처리하여 CounterState를 업데이트합니다.BlocProvider를 사용하여 Bloc을 앱에 제공하고, BlocBuilder로 상태를 UI에 반영합니다.IncrementEvent가 발생하면 mapEventToState 함수에서 상태를 업데이트하고 UI가 자동으로 갱신됩니다.이 외에도 각 상태 관리 방식의 특징과 사용 예시를 설명할 때, StatefulWidget, InheritedWidget, Provider, Riverpod, Bloc 각각의 장단점을 비교하는 표가 유용하게 추가될 수 있습니다.
| 상태 관리 방식 | 사용 용도 | 특징 |
|---|---|---|
StatefulWidget | 로컬 상태 | 간단한 UI 상태 관리, 상태가 적을 때 유용 |
InheritedWidget | 상태 공유 | 상태를 자식 위젯으로 전달, 직접 구현 어려움 |
Provider | 전역 상태 | 사용이 쉬움, 가장 많이 사용됨 |
Riverpod | 전역 상태 | 더 강력하고 안전함 |
Bloc | 전역 상태 | 복잡한 상태 관리 및 비즈니스 로직 분리 |
StatefulWidget으로 관리Provider 사용Riverpod 사용BLoC 패턴 사용