Flutter의 복잡한 앱 구조에서 가장 중요한 부분 중 하나는 상태관리이다.
상태를 어떻게 효과적으로 관리하느냐에 따라 앱의 유지보수성과 성능이 크게
달라질 수 있다.
이번 글에서는 다양한 상태관리 기법을 살펴보고, 각각의 장단점과 적합한 사용 사례를 소개하고자 한다.
Flutter에서 setState는 StatefulWidget에서 상태를 관리하는 핵심 메서드이다.
주로 간단한 상태의 변화가 생길 때 사용되며, setState는 해당 위젯의 상태가 변경되었음을 프레임워크에 알리고, UI 갱신을 위해 위젯의 build메서드를 다시 호출하게 된다.
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
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:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
위 코드처럼 setState는 간단한 상태 관리에 매우 유용하지만, 앱의 규모가 커지면 복잡한 상태 관리 기법을 사용해야 한다. 이제 Flutter의 여러 상태관리 라이브러리에 대해 알아보자.
GetX는 Flutter의 상태관리 뿐만 아니라, 의존성 및 라우트 관리도 가능하다.
적은 코드로 많은 기능들을 구현할 수 있으며 반응형/비반응형 상태 관리를 모두 지원한다.
GetX에 대한 의존성이 높아질 수 있다.class CounterController extends GetxController {
var count = 0.obs;
increment() => count++;
}
class HomePage extends StatelessWidget {
final CounterController controller = Get.put(CounterController());
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GetX Counter Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pushed the button this many times:'),
Obx(() => Text(
'${controller.count}',
style: TextStyle(fontSize: 40),
)),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment,
),
);
}
}
HomePage 생성자에서 CounterController()가 호출된다.CounterController의 인스턴스가 생성되고 GetX의 의존성 관리 시스템에 등록된다.CounterController가 초기화된다.count 변수가 0.obs로 초기화되어 반응형 변수가 된다.GetConsumer가 변경을 감지한다.BloC(Business Logic Component)는 플러터 전용으로 구축된 라이브러리로 이벤트 기반의 상태 관리를 제공하며, 애플리케이션의 비즈니스 로직을 UI로부터
분리한다. 테스트가 용이하고, 특히 복잡한 비즈니스 로직을 가진 대규모 앱에서 강점을 발휘한다.
// 이벤트 정의
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
// 상태 정의
class CounterState {
final int count;
CounterState(this.count);
}
// BLoC 정의
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<IncrementEvent>((event, emit) {
emit(CounterState(state.count + 1));
});
}
}
// UI에서 사용 예시
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(child: Text('Count: ${state.count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
child: Icon(Icons.add),
),
);
},
),
);
}
}
BlocBuilder, BlocListener, 또는 BlocConsumer위젯이 UI를 구성한다.<onEvent>핸들러가 이벤트를 받아 처리한다.BlocBuilder나 BlocConsumer가 이 변화를 감지하고 UI를 다시 빌드한다.간단하고 직관적인 API
Provider는 상태관리를 매우 간단하고 직관적으로 구현할 수 있도록 돕는다.ChangeNotifier, Stream, Future등을 이용해 데이터들을 관리하고, UI와의 연결을 쉽게 만들 수 있다.Consumer나 Selector 위젯을 통해 상태를 구독하고, 상태가 변경될 때 UI를 갱신하는 방식으로 작동한다.의존성 주입 (Dependency Injection)
Provider는 의존성 주입을 지원하여, 특정 상태를 위젯 트리의 하위 위젯들에 전달하는 역할을 한다. 이 덕분에 다른 위젯들이 전역적으로 상태를 관리할 필요 없이 필요한 곳에서만 상태를 참조 할 수 있다.단방향 데이터 흐름
Provider는 단방향 데이터 흐름을 따른다. 상태는 위젯 트리의 하위에서 상위로 전달되지 않고, 상위 위젯에서 하위 위젯으로 상태를 주입하여 관리된다. 이는 코드의 예측 가능성을 높이고 디버깅을 쉽게 한다.효율적인 상태 변경 감지
Provider는 상태 변경이 일어날 때만 UI를 갱신하므로, 필요하지 않은 위젯까지 불필요하게 다시 빌드되지 않습니다. Consumer위젯을 사용하여 상태의 변화를 선택적으로 구독할 수 있어 성능 최적화가 가능합니다.상태와 비즈니스 로직의 분리
Provider는 비즈니스 로직과 UI를 명확히 분리할 수 있도록 설계되었습니다. ChangeNotifier 클래스를 통해 상태 변화 및 로직을 별도로 관리하고, UI는 상태가 변경될 때 자동으로 갱신됩니다.