Flutter 개발

Flutter 상태 관리 어렵다면? riverpod으로 쉽게 시작하자!

withmilk 2025. 5. 25. 18:45

Flutter로 앱을 만들다 보면 꼭 마주하게 되는 문제가 있어요.
바로 "상태 관리(State Management)"예요.
버튼을 눌렀을 때 숫자가 바뀌거나, 로그인한 사용자 정보가 바뀌었을 때
그걸 앱 화면에 반영하려면 상태를 잘 관리해야 하거든요.

이걸 제대로 못하면…
버튼 눌렀는데 값이 안 바뀌고, 다른 화면에서도 이상한 데이터가 보이고…
앱이 고장난 것처럼 보이겠죠? 😰

그래서 오늘은 Flutter에서 상태를 아주 깔끔하게 관리할 수 있게 도와주는
강력한 친구, 바로 riverpod을 소개해드릴게요!


✅ 상태 관리는 왜 필요한 걸까?

  • 앱 안에는 버튼 클릭 횟수, 로그인 여부, 장바구니 같은 "상태"들이 있어요.
  • 이런 값들은 사용자 행동에 따라 계속 바뀌어요.
  • 바뀐 값을 화면에 보여주려면 상태를 추적하고, 변화에 반응하는 구조가 필요해요.
  • 앱이 커질수록 상태가 많아지니까 잘 정리된 관리 방식이 꼭 필요하죠!

예를 들어, 쇼핑몰 앱을 만든다고 생각해보세요.
사용자가 상품을 장바구니에 담고, 로그인을 하고, 결제하는 과정마다
앱은 각 상태를 저장하고, 그 상태에 따라 다른 화면을 보여줘야 해요.
이걸 그냥 setState() 하나로 관리하면 나중엔 코드가 엉켜서 손도 못 댈 지경이 되죠.
그래서! 상태를 체계적으로 관리하는 도구가 필요한 거예요.


🧠 riverpod이란?

  • Flutter 앱에서 상태를 쉽고 깔끔하게 관리할 수 있게 도와주는 패키지예요.
  • 기존 provider 패키지의 단점을 개선해서 나온 최신 도구예요.
  • 전역으로 상태를 관리하면서도 타입 안정성, 자동으로 UI 업데이트, 테스트 편리성을 갖추고 있어요.

다른 언어와 비교하자면…

  • React에서는 useState, Recoil, Redux
  • Vue에서는 Vuex, Pinia
  • Android에서는 LiveData, ViewModel
  • SwiftUI에서는 @State, @ObservedObject

이런 친구들과 비슷한 역할이에요.
그중에서도 riverpod은 깔끔하고 안전한 사용법으로 Flutter 개발자들 사이에서 인기 폭발 중이에요! 💥


🛠️ riverpod 설치 방법

먼저 패키지를 설치해야겠죠?

항상 설치하는 방법대로, pubspec.yaml에 추가해줄게요.

dependencies:
  flutter_riverpod: ^2.6.1

dependencies에 추가했다면, flutter pub get 명령어를 통해서 설치를 완료해주세요.

 

가장 먼저 작업해야하는 것은 main.dart 파일이에요.

main.dart 파일에서 꼭! ProviderScope로 앱 전체를 감싸줘야 해요.

이걸 잊으면 riverpod이 작동하지 않아요 😅

MyApp을 ProviderScope로 감싸준 모습


💡 기본 사용 예제: 카운터 앱

🔧 상태 선언하기

아래처럼 어떤 형의 데이터를 쓸지, 초기값은 뭔지 선언해주면 돼요.

final counterProvider = StateProvider<int>((ref) => 0);
final nameProvider = StateProvider<string>((ref) => 'withMilk');
  • StateProvider<int>는 숫자 상태를 다루겠다는 뜻이에요.
  • (ref) => 0는 상태의 초기값을 0으로 설정한 거예요.

🖥️ 상태 읽고 변경하기 (전체 코드)

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

// 1. 상태 선언 (0부터 시작하는 카운터)
final counterProvider = StateProvider<int>((ref) => 0);

void main() {
  runApp(
    // 2. 앱을 ProviderScope로 감싸야 riverpod이 작동함!
    ProviderScope(
      child: MyApp(),
    ),
  );
}

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

// 3. 상태를 사용하려면 ConsumerWidget 또는 ConsumerStatefulWidget이 필요함!
class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 4. 상태 읽기 - 현재 카운터 값을 가져옴
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Riverpod 카운터')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('버튼을 누른 횟수:', style: TextStyle(fontSize: 20)),
            SizedBox(height: 10),
            Text('$count', style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 5. 상태 변경 - 기존 값에 1 더하기
                ref.read(counterProvider.notifier).state++;
              },
              child: Text('숫자 증가'),
            ),
          ],
        ),
      ),
    );
  }
}

👀 예제 흐름 정리

  • counterProvider는 카운터 숫자 상태를 담고 있어요.
  • ref.watch(counterProvider)로 현재 숫자를 읽고,
  • 버튼을 누르면 ref.read(counterProvider.notifier).state++로 상태를 1 증가시켜요.
  • 상태가 바뀌면 riverpod이 자동으로 build() 함수를 다시 실행시켜서, 화면을 최신 값으로 업데이트해줘요!

구현한 카운터 앱을 실행한 모습

 

💡 여기서 꼭 알아야 할 핵심 개념 정리

1. ref란?

  • ref는 Provider를 가져오거나, 상태를 조작할 수 있게 해주는 도우미 객체예요.
  • ref.watch(), ref.read(), ref.listen() 같은 함수들을 통해 상태에 접근하거나 변경할 수 있어요.
  • 마치 "Provider 관리 도구"라고 보면 돼요.

2. 어떤 위젯에서 ref를 쓸 수 있을까?

위젯 종류 설명
ConsumerWidget StatelessWidget처럼 생긴 위젯, build 함수에 ref가 들어옴
ConsumerStatefulWidget StatefulWidget에 ref를 사용할 수 있게 도와줌
Consumer 위젯 안의 특정 부분만 상태에 반응하게 만들고 싶을 때 사용
CounterScreen을 ConsumerWidget으로 구현한 결과와, ConsumerStatefulWidget으로 구현한 결과.

👉 예를 들어, 전체 화면이 아니라 특정 버튼이나 텍스트만 상태에 반응하도록 만들고 싶다면 Consumer를 쓰는 게 좋아요!

3. ref로 상태를 가져오는 함수들

함수 용도 주의사항
ref.watch() Provider의 값을 읽고, 값이 변경되면 자동으로 UI를 다시 그림 보통 build() 함수에서 사용
ref.read() Provider의 값을 읽지만, 값이 변경돼도 UI를 다시 그리지 않음 이벤트 처리용으로 사용 (onPressed 등)
ref.listen() 값이 변경될 때 특정 동작(예: 알림, 에러 처리)을 하고 싶을 때 사용 UI 렌더링보다는 로직 처리에 사용

💬 추가 꿀팁

  • watch()는 UI가 변화에 반응해야 할 때 꼭 사용해요. setState를 사용하는 것처럼 값이 바뀔 때 UI가 다시 그려져요!
  • read()는 버튼 클릭, 페이지 이동 같은 한 번만 쓰는 이벤트 처리에 사용해요.
  • listen()은 알림 메시지, 에러 로그 출력 등 사이드 이펙트(side effect) 발생 시 사용해요.

🌱 다양한 Provider 종류

riverpod에는 다양한 상태 관리 방식이 있어요:

Provider 종류 설명 예시
StateProvider 숫자, 문자열 등 간단한 상태 관리 카운터
FutureProvider 비동기 데이터 (API 요청 등) 뉴스 목록 불러오기
StreamProvider 실시간 데이터 스트림 관리 채팅 메시지
NotifierProvider 더 복잡한 상태/로직 처리 로그인 상태, 장바구니 등

마지막 정리!

  • 앱은 사용자와의 상호작용에 따라 상태가 계속 바뀌는 구조예요.
  • 이런 상태를 체계적으로 관리하지 않으면, 앱은 금방 복잡해지고 오류가 생기기 쉬워요.
  • riverpod은 Flutter에서 상태를 안정적이고 깔끔하게 관리할 수 있게 도와주는 아주 강력한 도구예요.
  • StateProvider는 간단한 숫자나 문자열 같은 상태를 관리할 때 유용하고,
    상황에 따라 FutureProvider, StreamProvider, NotifierProvider 등 더 다양한 Provider도 쓸 수 있어요!

마무리 하며...

이번 글에서는 StateProvider를 중심으로 riverpod의 기본 개념과 사용법을 다뤄봤어요.
다음 글에서는 아래와 같은 다양한 Provider의 사용법과 실제 활용 예제를 자세히 소개할 예정이에요:

  • FutureProvider로 API 요청하기
  • StreamProvider로 실시간 데이터 처리하기
  • NotifierProvider로 복잡한 상태와 로직 함께 다루기

Flutter 앱을 한 단계 더 발전시키고 싶은 분들은 꼭 다음 글도 기대해 주세요! 😄