Flutter + Riverpod로 전역 상태 관리 마스터하기 💪

hur-kyuh-leez·2024년 8월 25일
0

안녕하세요, 여러분! 오늘은 제가 Flutter 프로젝트에서 겪은 상태 관리의 '아하' 모먼트를 공유하려고 해요. Riverpod를 처음 봤을 때 "이게 뭐야?" 싶었던 그 복잡한 코드, 함께 파헤쳐 봐요!

1. 상태? 그게 뭔데? 🤔

먼저 상태가 뭔지부터 알아볼까요? 간단히 말하면, 앱이 기억해야 할 데이터예요. 우리 example 앱에서는 이렇게 생겼어요:

class DataDisplayState {
  final List<PlutoRow> rows;
  final bool isLoading;
  final bool isDownloading;

  // 생성자랑 copyWith는 생략할게요~
}

이게 뭐냐고요? 그냥 우리 앱의 '현재 상황'이에요. "지금 데이터 몇 개 있고, 로딩 중이야? 다운로드는?" 이런 걸 담고 있죠.

사용법은 초간단!

var state = DataDisplayState(rows: []);
print(state.isLoading); // false 나오겠죠?

2. 상태 관리의 든든한 조력자, Notifier 👨‍🔧

이제 이 상태를 어떻게 바꾸냐고요? 여기서 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); // 자, 이제 로딩 시작!

3. 프로바이더: 전역 상태의 비밀 요원 🕵️

자, 이제 이 상태를 앱 전체에서 쓸 수 있게 만들어볼까요?

final dataDisplayProvider = StateNotifierProvider<DataDisplayNotifier, DataDisplayState>((ref) {
  return DataDisplayNotifier();
});

이 한 줄로 우리의 상태가 앱 전체에서 사용 가능한 '비밀 요원'이 됐어요! 어디서든 부를 수 있죠.

네, 당연하죠! "4. UI에서 사용하기" 부분의 코드에 상세한 주석을 추가해 드리겠습니다. 더 쉽게 이해할 수 있도록 설명을 보강해 볼게요. 😊


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);
    });
  }
}

여기서 주목할 점들이에요:

  1. ConsumerStatefulWidgetConsumerState를 사용해요. 이렇게 하면 ref 객체를 통해 Riverpod의 기능을 사용할 수 있어요.

  2. Consumer 위젯 안에서 ref.watch(dataDisplayProvider)를 사용해요. 이게 바로 실시간으로 상태 변화를 감지하는 마법의 주문이에요!

  3. _fetchData 메서드에서는 ref.read를 사용해요. 이건 상태를 변경할 때 쓰는 방법이에요.

  4. 상태 변경은 항상 notifier를 통해 해요. 예를 들어, ref.read(dataDisplayProvider.notifier).setLoading(true)처럼요.

  5. 데이터를 가져오는 동안 로딩 상태를 true로, 다 가져오면 false로 설정해요. 이렇게 하면 사용자에게 "지금 데이터 가져오는 중이에요~" 라고 알려줄 수 있죠.

이렇게 하면 상태 관리가 쉬워져요. 데이터가 변경되면 자동으로 UI가 업데이트되니까 우리가 직접 setState를 호출할 필요가 없어요. 편하죠? 😎


정리: 이게 바로 Riverpod의 힘! 💪

자, 정리해볼까요?
1. 상태를 정의하고 (DataDisplayState)
2. 그 상태를 관리할 Notifier를 만들고 (DataDisplayNotifier)
3. 프로바이더로 전역에서 사용 가능하게 만들고
4. UI에서 편하게 사용!

이렇게 하면 복잡한 상태 관리도 미션 클리어!

여러분도 한번 시도해 보세요. 처음엔 좀 복잡해 보여도, 익숙해지면 정말 편해요. 질문 있으면 언제든 댓글로 물어봐주세요! 다음에 또 재밌는 Flutter 팁으로 찾아올게요~ 안녕! 👋

profile
벨로그에 생각을 임시로 저장합니다. 틀린건 틀렸다고 해주세요 :) 그래야 논리 학습이 강화됩니다.

0개의 댓글