안녕하세요, 여러분! 오늘은 제가 Flutter 프로젝트에서 겪은 상태 관리의 '아하' 모먼트를 공유하려고 해요. Riverpod를 처음 봤을 때 "이게 뭐야?" 싶었던 그 복잡한 코드, 함께 파헤쳐 봐요!
먼저 상태가 뭔지부터 알아볼까요? 간단히 말하면, 앱이 기억해야 할 데이터예요. 우리 example 앱에서는 이렇게 생겼어요:
class DataDisplayState {
final List<PlutoRow> rows;
final bool isLoading;
final bool isDownloading;
// 생성자랑 copyWith는 생략할게요~
}
이게 뭐냐고요? 그냥 우리 앱의 '현재 상황'이에요. "지금 데이터 몇 개 있고, 로딩 중이야? 다운로드는?" 이런 걸 담고 있죠.
사용법은 초간단!
var state = DataDisplayState(rows: []);
print(state.isLoading); // false 나오겠죠?
이제 이 상태를 어떻게 바꾸냐고요? 여기서 Notifier가 등장합니다!
class DataDisplayNotifier extends StateNotifier<DataDisplayState> {
DataDisplayNotifier() : super(DataDisplayState(rows: []));
void setRows(List<PlutoRow> rows) {
state = state.copyWith(rows: rows);
}
void setLoading(bool isLoading) {
state = state.copyWith(isLoading: isLoading);
}
}
이 친구가 상태를 바꾸는 '리모컨' 역할을 해요. 상태를 바꾸고 싶을 때마다 이 리모컨의 버튼을 누르는 거죠!
var notifier = DataDisplayNotifier();
notifier.setLoading(true); // 자, 이제 로딩 시작!
자, 이제 이 상태를 앱 전체에서 쓸 수 있게 만들어볼까요?
final dataDisplayProvider = StateNotifierProvider<DataDisplayNotifier, DataDisplayState>((ref) {
return DataDisplayNotifier();
});
이 한 줄로 우리의 상태가 앱 전체에서 사용 가능한 '비밀 요원'이 됐어요! 어디서든 부를 수 있죠.
네, 당연하죠! "4. UI에서 사용하기" 부분의 코드에 상세한 주석을 추가해 드리겠습니다. 더 쉽게 이해할 수 있도록 설명을 보강해 볼게요. 😊
자, 이제 실제로 UI에서 어떻게 쓰는지 detail하게 살펴볼까요?
// ConsumerStatefulWidget을 사용해요. 이게 있어야 Riverpod의 마법을 부릴 수 있죠!
class DataDisplayPage extends ConsumerStatefulWidget {
_DataDisplayPageState createState() => _DataDisplayPageState();
}
// ConsumerState를 상속받아요. 이러면 ref를 사용할 수 있어요.
class _DataDisplayPageState extends ConsumerState<DataDisplayPage> {
Widget build(BuildContext context) {
// Consumer 위젯으로 감싸면 상태 변화를 실시간으로 감지할 수 있어요.
return Consumer(
builder: (context, ref, _) {
// ref.watch로 프로바이더의 상태를 지켜봐요.
// 상태가 변하면 이 부분이 자동으로 다시 실행돼요!
final state = ref.watch(dataDisplayProvider);
// 로딩 중이면 로딩 표시, 아니면 데이터 그리드를 보여줘요.
return state.isLoading
? CircularProgressIndicator()
: PlutoGrid(rows: state.rows);
},
);
}
// 데이터를 가져오는 메서드예요.
void _fetchData() {
// 먼저 로딩 상태를 true로 설정해요.
// ref.read로 notifier에 접근하고, setLoading 메서드를 호출해요.
ref.read(dataDisplayProvider.notifier).setLoading(true);
// 여기서 실제로 데이터를 가져오는 비동기 작업을 수행해요.
// 예를 들면 API 호출 같은 거죠.
// fetchDataFromSomewhere().then((newRows) {
// // 데이터를 가져왔으면, 상태를 업데이트해요.
// ref.read(dataDisplayProvider.notifier).setRows(newRows);
// // 로딩이 끝났으니 로딩 상태를 false로 설정해요.
// ref.read(dataDisplayProvider.notifier).setLoading(false);
// });
// 위의 주석 처리된 코드 대신, 지금은 간단히 이렇게 처리할게요.
Future.delayed(Duration(seconds: 2), () {
// 가짜 데이터를 만들어서 상태를 업데이트해요.
final newRows = [PlutoRow(cells: {})]; // 실제로는 여기에 데이터를 채워넣어야 해요!
ref.read(dataDisplayProvider.notifier).setRows(newRows);
// 로딩 끝!
ref.read(dataDisplayProvider.notifier).setLoading(false);
});
}
}
여기서 주목할 점들이에요:
ConsumerStatefulWidget
과 ConsumerState
를 사용해요. 이렇게 하면 ref
객체를 통해 Riverpod의 기능을 사용할 수 있어요.
Consumer
위젯 안에서 ref.watch(dataDisplayProvider)
를 사용해요. 이게 바로 실시간으로 상태 변화를 감지하는 마법의 주문이에요!
_fetchData
메서드에서는 ref.read
를 사용해요. 이건 상태를 변경할 때 쓰는 방법이에요.
상태 변경은 항상 notifier
를 통해 해요. 예를 들어, ref.read(dataDisplayProvider.notifier).setLoading(true)
처럼요.
데이터를 가져오는 동안 로딩 상태를 true로, 다 가져오면 false로 설정해요. 이렇게 하면 사용자에게 "지금 데이터 가져오는 중이에요~" 라고 알려줄 수 있죠.
이렇게 하면 상태 관리가 쉬워져요. 데이터가 변경되면 자동으로 UI가 업데이트되니까 우리가 직접 setState
를 호출할 필요가 없어요. 편하죠? 😎
자, 정리해볼까요?
1. 상태를 정의하고 (DataDisplayState)
2. 그 상태를 관리할 Notifier를 만들고 (DataDisplayNotifier)
3. 프로바이더로 전역에서 사용 가능하게 만들고
4. UI에서 편하게 사용!
이렇게 하면 복잡한 상태 관리도 미션 클리어!
여러분도 한번 시도해 보세요. 처음엔 좀 복잡해 보여도, 익숙해지면 정말 편해요. 질문 있으면 언제든 댓글로 물어봐주세요! 다음에 또 재밌는 Flutter 팁으로 찾아올게요~ 안녕! 👋