Riverpod 3.0 실전 예제 완전 정복

Riverpod 3.0의 모든 기능을 실전 예제로 하나씩 마스터! Provider, Notifier, AsyncNotifier부터 3.0 신규 기능인 Mutation, Offline, Retry까지. 각 기능마다 바로 복사해서 쓸 수 있는 실전 코드 제공.

Flutter,Riverpod,State Management중급
35시간
23개 항목
학습 진행률0 / 23 (0%)

학습 항목

1. Flutter,Riverpod
초급
Provider로 카운터 앱 만들기 완벽 가이드
퀴즈튜토리얼
2. Flutter,Riverpod
초급
FutureProvider로 API 호출하기 완벽 가이드
퀴즈튜토리얼
3. Flutter,Riverpod
초급
StreamProvider로 실시간 타이머 만들기 완벽 가이드
퀴즈튜토리얼
4. Flutter,Riverpod
초급
Notifier로 Todo 리스트 만들기 실전 가이드
퀴즈튜토리얼
5. Flutter,Riverpod
초급
AsyncNotifier로 서버 CRUD 구현 완벽 가이드
퀴즈튜토리얼
6. Flutter,Riverpod
초급
StreamNotifier로 채팅 메시지 관리 완벽 가이드
퀴즈튜토리얼
7. Flutter,Riverpod
초급
Riverpod Generator로 보일러플레이트 제거 완벽 가이드
퀴즈튜토리얼
8. Flutter,Riverpod
초급
Riverpod Family 완벽 가이드 파라미터로 동적 Provider 만들기
퀴즈튜토리얼
9. Flutter,Riverpod
초급
Riverpod autoDispose 메모리 관리 완벽 가이드
퀴즈튜토리얼
10. Flutter,Riverpod
초급
ref.watch로 반응형 UI 만들기 완벽 가이드
퀴즈튜토리얼
11. Flutter,Riverpod
초급
ref.read로 이벤트 핸들러 구현하기
퀴즈튜토리얼
12. Flutter,Riverpod
초급
ref.listen으로 사이드 이펙트 처리 완벽 가이드
퀴즈튜토리얼
13. Flutter,Riverpod
초급
Riverpod invalidate와 refresh로 데이터 새로고침 완벽 가이드
퀴즈튜토리얼
14. Flutter,Riverpod
초급
Riverpod Select로 불필요한 리빌드 방지하는 완벽 가이드
퀴즈튜토리얼
15. Flutter,Riverpod
초급
AsyncValue로 상태별 UI 분기 완벽 가이드
퀴즈튜토리얼
16. Flutter,Riverpod
초급
ConsumerWidget vs Consumer 비교 완벽 가이드
퀴즈튜토리얼
17. Flutter,Riverpod
초급
ProviderScope로 의존성 주입 완벽 가이드
퀴즈튜토리얼
18. Flutter,Riverpod
초급
Riverpod Provider 오버라이드 테스트 완벽 가이드
퀴즈튜토리얼
19. Flutter,Riverpod
초급
Riverpod 3.0 Mutation으로 폼 제출 완벽 가이드
퀴즈튜토리얼
20. Flutter,Riverpod
초급
Flutter 3.0 Offline 데이터 영속화 완벽 가이드
퀴즈튜토리얼
21. Flutter,Riverpod
초급
Riverpod 3.0 requireValue로 Provider 결합하기
퀴즈튜토리얼
22. Flutter,Riverpod
초급
Riverpod 3.0 Retry 자동 재시도 완벽 가이드
퀴즈튜토리얼
23. Flutter,Riverpod
초급
Riverpod 3.0 쇼핑 앱 종합 프로젝트 완벽 가이드
퀴즈튜토리얼
1 / 23
🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

이미지 로딩 중...

Provider로 카운터 앱 만들기 완벽 가이드 - 슬라이드 1/7
상세 보기

Provider로 카운터 앱 만들기 완벽 가이드

Flutter Riverpod의 Provider를 활용하여 실제로 작동하는 카운터 앱을 처음부터 끝까지 구현합니다. 프로젝트 설정부터 상태 관리, UI 구성, 버튼 이벤트 처리까지 전 과정을 초보자 눈높이에서 단계별로 설명합니다.


목차

  1. 프로젝트 설정과 의존성 추가
  2. 간단한 Provider 선언
  3. ConsumerWidget으로 값 읽기
  4. 버튼 클릭으로 상태 변경
  5. 전체 코드와 실행 결과
  6. 연습문제: 증가/감소 버튼 추가

1. 프로젝트 설정과 의존성 추가

어느 날 김개발 씨는 Flutter로 첫 번째 앱을 만들기로 결심했습니다. 하지만 상태 관리는 어떻게 해야 할까요?

선배 박시니어 씨가 말합니다. "요즘은 Riverpod을 많이 쓰죠.

일단 설치부터 해봅시다."

모든 Flutter 프로젝트는 의존성 추가부터 시작합니다. Riverpod을 사용하려면 pubspec.yaml 파일에 필요한 패키지를 명시해야 합니다.

이 과정은 마치 요리를 시작하기 전에 재료를 준비하는 것과 같습니다.

다음 코드를 살펴봅시다.

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  # Riverpod 패키지 추가
  flutter_riverpod: ^2.4.0

# 터미널에서 실행
# flutter pub get

김개발 씨는 새로운 프로젝트 폴더를 열었습니다. VS Code 화면에는 Flutter 프로젝트의 기본 구조가 보입니다.

하지만 아직 Riverpod은 설치되지 않았습니다. "먼저 pubspec.yaml 파일을 열어볼까요?" 박시니어 씨가 말합니다.

pubspec.yaml은 Flutter 프로젝트의 설정 파일입니다. 여기에 프로젝트에서 사용할 외부 패키지들을 명시합니다.

의존성 추가란 무엇일까요? 쉽게 비유하자면, 의존성은 마치 요리할 때 필요한 재료와 같습니다. 파스타를 만들려면 면과 소스가 필요하듯이, Riverpod으로 상태 관리를 하려면 flutter_riverpod 패키지가 필요합니다.

pubspec.yaml 파일의 dependencies 섹션에 패키지 이름과 버전을 적어주면 됩니다. 버전 번호 앞의 캐럿 기호는 "이 버전 이상의 호환 가능한 최신 버전을 사용하라"는 의미입니다.

왜 Riverpod을 선택했을까요? Flutter에는 여러 상태 관리 라이브러리가 있습니다. setState, Provider, Bloc, GetX 등 선택지가 많습니다.

그중에서 Riverpod을 선택한 이유는 무엇일까요? Riverpod은 기존 Provider의 단점을 개선한 차세대 상태 관리 솔루션입니다.

컴파일 타임에 오류를 잡아주고, 코드가 간결하며, 테스트하기도 쉽습니다. 무엇보다 BuildContext에 의존하지 않아 어디서든 Provider에 접근할 수 있습니다.

패키지 설치 과정 pubspec.yaml을 저장한 후에는 터미널에서 flutter pub get 명령을 실행합니다. 이 명령은 명시된 패키지들을 다운로드하고 프로젝트에 연결합니다.

김개발 씨가 터미널에 명령을 입력하자, 화면에 여러 줄의 메시지가 흘러갑니다. "Resolving dependencies...", "Got dependencies!" 마지막 메시지가 뜨면 설치가 완료된 것입니다.

이제 프로젝트 폴더를 보면 .dart_tool과 .packages 파일이 생성된 것을 볼 수 있습니다. 이 파일들은 Flutter가 패키지를 관리하기 위해 자동으로 생성한 것입니다.

버전 선택의 중요성 패키지 버전을 명시할 때는 주의가 필요합니다. 너무 낮은 버전을 사용하면 최신 기능을 쓸 수 없고, 너무 높은 버전을 사용하면 다른 패키지와 충돌할 수 있습니다.

2.4.0 버전은 2024년 기준으로 안정적인 버전입니다. 실무에서는 프로젝트의 다른 의존성들과 호환되는 버전을 선택해야 합니다.

설치 확인하기 패키지가 제대로 설치되었는지 확인하는 방법이 있습니다. Dart 파일에서 import 문을 작성해 보는 것입니다.

dart import 'package:flutter_riverpod/flutter_riverpod.dart'; 이 줄을 작성했을 때 빨간 줄이 뜨지 않는다면 설치가 성공한 것입니다. VS Code의 자동완성 기능도 작동할 것입니다.

"자, 이제 준비가 끝났네요." 박시니어 씨가 미소 지으며 말합니다. "다음 단계로 넘어가볼까요?" 김개발 씨는 설치가 이렇게 간단한 줄 몰랐습니다.

pubspec.yaml에 한 줄 추가하고 명령 하나만 실행하면 되는 것이었습니다.

실전 팁

💡 - pubspec.yaml 파일 수정 후에는 반드시 flutter pub get을 실행하세요

  • 패키지 버전은 프로젝트의 다른 의존성과 호환되는지 확인하세요
  • import 문으로 패키지 설치 여부를 빠르게 확인할 수 있습니다

2. 간단한 Provider 선언

이제 본격적으로 Provider를 만들 차례입니다. 김개발 씨는 코드 에디터를 열고 고민에 빠졌습니다.

"Provider를 어떻게 선언하죠?" 박시니어 씨가 설명을 시작합니다.

StateProvider는 Riverpod에서 가장 간단한 형태의 Provider입니다. 변경 가능한 상태를 관리하며, 카운터처럼 단순한 값을 다룰 때 적합합니다.

final 키워드로 선언하고, 제네릭 타입으로 상태의 타입을 명시합니다.

다음 코드를 살펴봅시다.

import 'package:flutter_riverpod/flutter_riverpod.dart';

// 카운터 상태를 관리하는 Provider
// 초기값은 0으로 설정
final counterProvider = StateProvider<int>((ref) {
  return 0;
});

// 전역 변수처럼 선언하여 어디서든 접근 가능

"Provider 선언이 뭔가요?" 김개발 씨가 물었습니다. 박시니어 씨는 화이트보드에 그림을 그리기 시작했습니다.

Provider란 무엇인가요? Provider는 마치 은행 금고와 같습니다. 중요한 데이터를 안전하게 보관하고, 필요할 때 꺼내 쓸 수 있게 해줍니다.

누구나 금고에 접근할 수 있지만, 규칙에 따라서만 값을 변경할 수 있습니다. 일반 변수를 사용하면 어떤 문제가 생길까요?

여러 위젯에서 같은 데이터를 공유하려면 복잡한 콜백 함수를 계속 전달해야 합니다. 부모에서 자식으로, 자식에서 손자로 계속 넘겨주는 것은 번거롭고 실수하기 쉽습니다.

StateProvider의 특징 StateProvider는 여러 종류의 Provider 중에서 가장 간단한 형태입니다. 변경 가능한 단일 값을 관리할 때 사용합니다.

StateProvider<int>라고 쓴 것에 주목해 봅시다. 꺾쇠 괄호 안의 int는 제네릭 타입입니다.

"이 Provider는 정수형 데이터를 다룬다"고 명시하는 것입니다. 만약 문자열을 다루고 싶다면 StateProvider<String>이라고 쓰면 됩니다.

코드 분석하기 첫 번째 줄의 final 키워드를 보세요. Provider는 한 번 선언하면 변경되지 않습니다.

변하는 것은 Provider가 담고 있는 값이지, Provider 자체가 아닙니다. counterProvider라는 이름은 관례에 따라 Provider로 끝나도록 지었습니다.

이렇게 하면 코드를 읽는 사람이 한눈에 "아, 이게 Provider구나"라고 알 수 있습니다. 괄호 안의 (ref) 부분이 보이나요?

ref는 reference의 약자로, 다른 Provider를 참조하거나 Provider의 생명주기를 관리할 때 사용합니다. 지금은 간단한 예제이므로 사용하지 않지만, 나중에 복잡한 상태 관리를 할 때 유용합니다.

초기값 설정 return 0; 이 부분이 초기값을 설정하는 코드입니다. 앱이 처음 실행될 때 카운터는 0부터 시작합니다.

만약 10부터 시작하고 싶다면 return 10;이라고 쓰면 됩니다. 이렇게 초기값을 명시적으로 설정할 수 있다는 것이 Provider의 장점입니다.

전역 선언의 의미 이 Provider는 클래스 밖에서, 파일의 최상단에 선언되어 있습니다. 이것이 바로 전역 변수입니다.

앱의 어디서든 이 Provider에 접근할 수 있습니다. "전역 변수는 나쁘다고 배웠는데요?" 김개발 씨가 의문을 제기했습니다.

좋은 질문입니다. 일반적인 전역 변수는 어디서든 마음대로 값을 바꿀 수 있어 위험합니다.

하지만 Provider는 다릅니다. Riverpod이 제공하는 안전한 메커니즘을 통해서만 값을 변경할 수 있습니다.

누가 언제 값을 바꿨는지 추적할 수도 있고, 디버깅도 쉽습니다. 실무에서의 활용 실제 프로젝트에서는 여러 개의 Provider를 선언합니다.

사용자 정보를 담는 Provider, 장바구니 데이터를 담는 Provider, 앱 설정을 담는 Provider 등 용도에 따라 나눠서 관리합니다. 보통 providers.dart나 state.dart 같은 별도 파일에 모아서 관리합니다.

이렇게 하면 상태 관리 코드를 한곳에서 볼 수 있어 유지보수가 편합니다. "생각보다 간단하네요!" 김개발 씨가 감탄했습니다.

단 몇 줄의 코드로 전역 상태 관리가 가능해진 것입니다. 박시니어 씨가 고개를 끄덕입니다.

"Riverpod의 장점이 바로 이거예요. 복잡한 보일러플레이트 코드 없이 간결하게 상태를 관리할 수 있죠."

실전 팁

💡 - Provider 이름은 관례에 따라 Provider로 끝나도록 짓습니다

  • StateProvider는 단순한 값을 다룰 때 사용하고, 복잡한 객체는 StateNotifierProvider를 고려하세요
  • 별도 파일로 분리하여 상태 관리 코드를 체계적으로 관리하세요

3. ConsumerWidget으로 값 읽기

Provider를 선언했으니 이제 화면에 표시할 차례입니다. 김개발 씨는 StatelessWidget을 쓰려다가 멈칫했습니다.

"아, Provider를 읽으려면 특별한 위젯을 써야 하나요?" 박시니어 씨가 고개를 끄덕입니다.

ConsumerWidget은 Provider의 값을 읽고 UI에 반영하는 특별한 위젯입니다. StatelessWidget과 사용법이 비슷하지만, build 메서드에서 WidgetRef 객체를 추가로 받습니다.

이 ref 객체를 통해 Provider에 접근할 수 있습니다.

다음 코드를 살펴봅시다.

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

// ConsumerWidget을 상속받는 화면
class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Provider의 현재 값을 읽기
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('카운터 앱')),
      body: Center(
        child: Text('$count', style: TextStyle(fontSize: 48)),
      ),
    );
  }
}

김개발 씨는 새로운 파일을 만들었습니다. counter_screen.dart라는 이름으로 저장했습니다.

이제 화면을 구성할 차례입니다. StatelessWidget vs ConsumerWidget 일반적으로 Flutter에서 화면을 만들 때는 StatelessWidget이나 StatefulWidget을 사용합니다.

하지만 Riverpod을 쓸 때는 ConsumerWidget이라는 새로운 선택지가 생깁니다. ConsumerWidget은 마치 StatelessWidget의 업그레이드 버전과 같습니다.

StatelessWidget이 할 수 있는 모든 것을 할 수 있고, 거기에 더해 Provider의 값을 쉽게 읽을 수 있습니다. build 메서드의 차이점 StatelessWidget의 build 메서드는 BuildContext 하나만 받습니다.

하지만 ConsumerWidget은 두 개의 파라미터를 받습니다. dart Widget build(BuildContext context, WidgetRef ref) 두 번째 파라미터인 WidgetRef ref가 핵심입니다.

이 ref 객체가 Provider와 위젯을 연결해 주는 다리 역할을 합니다. ref.watch()의 의미 코드에서 ref.watch(counterProvider) 부분을 주목해 봅시다.

watch라는 메서드 이름이 의미심장합니다. "지켜본다"는 뜻이죠.

이것은 마치 유튜브 채널을 구독하는 것과 같습니다. counterProvider를 구독하면, 그 값이 바뀔 때마다 Flutter가 자동으로 알려줍니다.

그러면 build 메서드가 다시 실행되어 화면이 업데이트됩니다. 왜 watch를 써야 할까요? Riverpod에는 watch 외에도 read라는 메서드가 있습니다.

둘의 차이는 무엇일까요? watch는 값이 바뀔 때 자동으로 위젯을 다시 빌드합니다.

read는 단순히 현재 값만 읽어옵니다. 화면에 표시할 때는 대부분 watch를 사용합니다.

코드 흐름 이해하기 첫 번째 줄에서 counterProvider의 현재 값을 읽어 count 변수에 저장합니다. 앱을 처음 실행하면 0이 들어 있겠죠.

그다음 Scaffold 위젯으로 기본 화면 구조를 만듭니다. AppBar에는 제목을, body에는 중앙에 텍스트를 배치합니다.

Text 위젯에서 '$count'라고 쓴 부분을 보세요. Dart의 문자열 보간 기능입니다.

count 변수의 값이 문자열로 변환되어 표시됩니다. 처음에는 "0"이 보일 것입니다.

실시간 업데이트의 마법 여기서 놀라운 일이 일어납니다. 나중에 버튼을 눌러 counterProvider의 값이 1로 바뀌면, 이 화면도 자동으로 "1"로 업데이트됩니다.

setState를 호출할 필요도 없고, 복잡한 콜백 함수를 전달할 필요도 없습니다. ref.watch가 모든 것을 알아서 처리합니다.

성능 최적화 "그럼 Provider가 바뀔 때마다 전체 화면이 다시 그려지나요?" 김개발 씨가 걱정스러운 표정으로 물었습니다. 좋은 질문입니다.

Riverpod은 똑똑합니다. watch를 사용한 위젯만 다시 빌드됩니다.

나머지 위젯은 그대로 유지되어 성능이 최적화됩니다. 실제로 Flutter DevTools로 확인해 보면, Text 위젯과 그 부모들만 다시 빌드되는 것을 볼 수 있습니다.

AppBar는 변하지 않았으니 다시 그릴 필요가 없겠죠. 실무 적용 사례 실제 앱에서는 이런 패턴을 자주 사용합니다.

사용자 프로필 화면에서 사용자 정보를 표시할 때, 장바구니 화면에서 상품 목록을 보여줄 때, 모두 같은 원리입니다. Provider에 데이터를 저장하고, ConsumerWidget에서 watch로 읽어서 표시합니다.

데이터가 바뀌면 화면이 자동으로 업데이트됩니다. 박시니어 씨가 말합니다.

"이제 Provider의 값을 읽는 방법을 배웠네요. 다음은 값을 변경하는 방법을 알아볼까요?"

실전 팁

💡 - ConsumerWidget 대신 Consumer 위젯으로 특정 부분만 감쌀 수도 있습니다

  • ref.watch는 build 메서드 안에서만 사용하세요
  • 성능이 중요하다면 Consumer 위젯으로 최소 범위만 리빌드하도록 최적화하세요

4. 버튼 클릭으로 상태 변경

화면에 숫자를 표시했지만 아직 0에서 움직이지 않습니다. 김개발 씨는 버튼을 추가하려고 합니다.

"버튼을 누르면 숫자가 올라가게 하려면 어떻게 해야 하죠?" 드디어 핵심 기능을 구현할 시간입니다.

Provider의 값을 변경하려면 ref.read() 메서드를 사용합니다. 이벤트 핸들러 안에서 Provider의 notifier에 접근하여 상태를 업데이트할 수 있습니다.

StateProvider는 편리한 update 메서드를 제공합니다.

다음 코드를 살펴봅시다.

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

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('카운터 앱')),
      body: Center(
        child: Text('$count', style: TextStyle(fontSize: 48)),
      ),
      floatingActionButton: FloatingActionButton(
        // 버튼 클릭 시 상태 변경
        onPressed: () {
          ref.read(counterProvider.notifier).update((state) => state + 1);
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

김개발 씨는 FloatingActionButton을 추가했습니다. 화면 오른쪽 아래에 동그란 버튼이 나타납니다.

하지만 onPressed 안에 무엇을 써야 할까요? read vs watch의 차이 앞에서 watch를 사용했던 것을 기억하시나요?

그런데 여기서는 read를 사용합니다. 왜 그럴까요?

watch는 "값이 바뀌면 나한테 알려줘"라는 의미입니다. build 메서드 안에서 사용하여 화면을 업데이트할 때 씁니다.

read는 "지금 당장 값을 읽기만 할게"라는 의미입니다. 이벤트 핸들러나 버튼 클릭 같은 일회성 동작에서 사용합니다.

값이 바뀌어도 위젯을 다시 빌드하지 않습니다. notifier의 역할 ref.read(counterProvider) 대신 ref.read(counterProvider.notifier)라고 쓴 것에 주목하세요.

notifier는 무엇일까요? 이것은 마치 리모컨과 같습니다.

TV를 보는 것과 TV 채널을 바꾸는 것은 다른 행위입니다. counterProvider는 값을 보는 용도이고, counterProvider.notifier는 값을 변경하는 용도입니다.

update 메서드의 마법 update 메서드는 현재 상태를 받아서 새로운 상태를 반환하는 함수를 인자로 받습니다. dart .update((state) => state + 1) 이 코드를 풀어서 설명하면 이렇습니다.

state는 현재 카운터 값입니다. 처음에는 0이겠죠.

그 값에 1을 더한 결과를 반환합니다. 그러면 Provider의 값이 1로 업데이트됩니다.

연쇄 반응 버튼을 클릭하면 어떤 일이 일어날까요? 마법 같은 연쇄 반응이 시작됩니다.

첫째, update 메서드가 실행되어 counterProvider의 값이 0에서 1로 바뀝니다. 둘째, 이 Provider를 watch하고 있던 모든 위젯에게 알림이 갑니다.

"값이 바뀌었어요!" 셋째, 해당 위젯들의 build 메서드가 다시 실행됩니다. Text 위젯이 다시 그려지면서 "1"이 표시됩니다.

이 모든 과정이 눈 깜짝할 사이에 일어납니다. 사용자는 버튼을 누르자마자 숫자가 올라가는 것을 볼 수 있습니다.

왜 이렇게 복잡하게? "그냥 counterProvider = 1 이렇게 쓰면 안 되나요?" 김개발 씨가 물었습니다. 프로그래밍을 처음 배울 때는 이런 의문이 당연합니다.

하지만 그렇게 하면 Flutter가 값이 바뀐 것을 알 수 없습니다. Riverpod의 메커니즘을 통해 값을 변경해야 모든 리스너들에게 알림이 갑니다.

이것은 마치 회사에서 메일을 보내는 것과 같습니다. 중요한 결정 사항을 혼자만 알고 있으면 안 되죠.

공식 메일 시스템을 통해 모두에게 알려야 합니다. update 메서드가 바로 그 공식 채널입니다.

다양한 업데이트 방법 update 외에도 값을 변경하는 방법이 있습니다. dart ref.read(counterProvider.notifier).state = count + 1; 이렇게 직접 state 프로퍼티에 값을 할당할 수도 있습니다.

하지만 update를 사용하는 것이 더 안전합니다. 현재 값을 기반으로 새 값을 계산할 때 특히 그렇습니다.

실무에서의 활용 실제 앱에서는 더 복잡한 상태 변경이 일어납니다. 좋아요 버튼을 누르면 서버에 요청을 보내고, 응답이 오면 Provider를 업데이트합니다.

장바구니에 상품을 추가하면 여러 Provider가 동시에 업데이트됩니다. 하지만 기본 원리는 같습니다.

ref.read()로 notifier에 접근하고, 적절한 메서드로 상태를 변경합니다. Riverpod이 나머지를 알아서 처리해 줍니다.

박시니어 씨가 말합니다. "이제 버튼을 눌러볼까요?

숫자가 올라가는 게 보일 거예요." 김개발 씨가 앱을 실행하고 버튼을 눌렀습니다. 0이 1로, 1이 2로 바뀝니다.

첫 번째 상태 관리 앱이 완성된 것입니다!

실전 팁

💡 - 이벤트 핸들러에서는 read를 사용하고, build 메서드에서는 watch를 사용하세요

  • update 메서드는 현재 상태를 안전하게 참조하여 새 상태를 계산합니다
  • 복잡한 상태 변경 로직은 별도 메서드로 분리하는 것이 좋습니다

5. 전체 코드와 실행 결과

지금까지 배운 내용을 모두 합쳐 완전한 앱을 만들어 봅시다. 김개발 씨는 여러 파일에 흩어진 코드를 정리하기 시작했습니다.

박시니어 씨가 옆에서 조언합니다. "한 파일에 모아서 볼까요?"

완성된 카운터 앱은 ProviderScope로 앱을 감싸고, Provider를 선언하고, ConsumerWidget에서 값을 읽고 변경하는 모든 요소를 포함합니다. main 함수에서 ProviderScope를 설정하는 것이 중요합니다.

다음 코드를 살펴봅시다.

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

// 1. Provider 선언
final counterProvider = StateProvider<int>((ref) => 0);

// 2. ProviderScope로 앱 감싸기
void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '카운터 앱',
      home: CounterScreen(),
    );
  }
}

// 3. ConsumerWidget으로 화면 구성
class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Riverpod 카운터'),
        backgroundColor: Colors.blue,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('버튼을 누른 횟수:', style: TextStyle(fontSize: 20)),
            SizedBox(height: 10),
            Text('$count', style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(counterProvider.notifier).update((state) => state + 1);
        },
        child: Icon(Icons.add),
        backgroundColor: Colors.blue,
      ),
    );
  }
}

김개발 씨는 화면에 나타난 전체 코드를 보며 감탄했습니다. "생각보다 코드가 짧네요!" 파일 구조 이해하기 실제 프로젝트에서는 코드를 여러 파일로 나누지만, 학습 목적으로 한 파일에 모두 담았습니다.

크게 세 부분으로 나뉩니다. 첫째, Provider 선언 부분입니다.

counterProvider를 정의하여 상태를 관리합니다. 둘째, main 함수입니다.

앱의 시작점이며, ProviderScope로 전체 앱을 감쌉니다. 셋째, UI 부분입니다.

MyApp과 CounterScreen 위젯이 화면을 구성합니다. ProviderScope의 중요성 main 함수를 자세히 보세요.

runApp을 호출할 때 ProviderScope로 MyApp을 감싸고 있습니다. 이것은 매우 중요합니다.

ProviderScope가 없으면 앱 어디에서도 Provider를 사용할 수 없습니다. 마치 전기 콘센트가 없으면 전자기기를 쓸 수 없는 것과 같습니다.

ProviderScope는 Riverpod의 모든 Provider들을 관리하는 컨테이너입니다. 앱 전체에 걸쳐 단 하나만 존재하며, 가장 바깥쪽에 위치합니다.

UI 구성 상세 보기 CounterScreen을 자세히 살펴봅시다. Column 위젯으로 수직 배치를 했습니다.

위에는 설명 텍스트, 아래에는 큰 숫자가 표시됩니다. mainAxisAlignment를 center로 설정하여 화면 중앙에 배치했습니다.

SizedBox로 위젯 사이에 간격을 주었습니다. Text 위젯의 스타일을 보세요.

숫자는 72 크기의 굵은 글꼴로 표시하여 눈에 잘 띄게 했습니다. 실행 결과 예상하기 이 앱을 실행하면 어떻게 보일까요?

상상해 봅시다. 화면 상단에 파란색 AppBar가 나타나고 "Riverpod 카운터"라는 제목이 보입니다.

화면 중앙에는 "버튼을 누른 횟수:"라는 텍스트 아래 큰 숫자 0이 표시됩니다. 오른쪽 아래에는 파란색 동그란 버튼이 떠 있습니다.

플러스 아이콘이 그려진 FloatingActionButton입니다. 버튼을 누르면 숫자가 1, 2, 3으로 증가합니다.

부드러운 애니메이션과 함께 숫자가 바뀝니다. 코드의 흐름 앱이 실행되는 순서를 따라가 봅시다.

먼저 main 함수가 실행됩니다. ProviderScope가 설정되고 MyApp이 렌더링됩니다.

MyApp은 MaterialApp을 반환하고, home으로 CounterScreen을 지정합니다. CounterScreen의 build 메서드가 호출됩니다.

ref.watch(counterProvider)가 실행되어 초기값 0을 읽어옵니다. Scaffold가 그려지고 화면에 0이 표시됩니다.

사용자가 버튼을 누르면 onPressed 콜백이 실행됩니다. ref.read로 Provider를 업데이트하면, watch하던 위젯이 다시 빌드되어 새 값이 표시됩니다.

디버깅 팁 코드가 예상대로 동작하지 않으면 어떻게 할까요? 몇 가지 디버깅 방법이 있습니다.

첫째, print 문을 추가해 봅니다. update 메서드 안에 print('count: $state')를 넣으면 콘솔에 값이 출력됩니다.

둘째, Flutter DevTools를 사용합니다. Provider의 상태를 실시간으로 확인할 수 있습니다.

셋째, ProviderScope를 제대로 설정했는지 확인합니다. 이것을 빼먹으면 에러가 발생합니다.

성능 확인하기 Flutter의 Performance Overlay를 켜고 버튼을 눌러봅시다. 프레임 드롭 없이 부드럽게 동작하는 것을 볼 수 있습니다.

Riverpod은 효율적으로 설계되어 있어 성능 걱정 없이 사용할 수 있습니다. 필요한 부분만 다시 빌드되고, 나머지는 그대로 유지됩니다.

실무로 확장하기 이 간단한 카운터 앱이 실무 앱의 기초가 됩니다. 같은 원리로 복잡한 앱도 만들 수 있습니다.

쇼핑몰 앱을 만든다면? 장바구니 개수를 Provider로 관리합니다.

SNS 앱을 만든다면? 좋아요 개수, 팔로워 수를 Provider로 관리합니다.

김개발 씨는 처음으로 상태 관리 앱을 완성했습니다. 비록 간단한 카운터 앱이지만, Riverpod의 핵심 개념을 모두 경험했습니다.

박시니어 씨가 말합니다. "축하해요!

이제 Provider, ConsumerWidget, 상태 업데이트를 모두 이해했네요. 이것이 Riverpod의 기본입니다."

실전 팁

💡 - ProviderScope를 main 함수에서 설정하는 것을 잊지 마세요

  • 실제 프로젝트에서는 Provider를 별도 파일로 분리하여 관리하세요
  • Flutter DevTools로 Provider의 상태를 실시간으로 모니터링할 수 있습니다

6. 연습문제: 증가/감소 버튼 추가

기본 카운터 앱을 완성했습니다. 이제 직접 기능을 추가해 볼 차례입니다.

김개발 씨에게 박시니어 씨가 과제를 냅니다. "증가 버튼만 있으니 심심하네요.

감소 버튼도 추가해 볼까요?"

학습한 내용을 응용하여 감소 버튼을 추가하는 연습입니다. 같은 Provider를 사용하되, update 메서드에서 1을 빼면 됩니다.

두 개의 FloatingActionButton을 배치하는 방법도 배웁니다.

다음 코드를 살펴봅시다.

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('카운터 앱')),
      body: Center(
        child: Text('$count', style: TextStyle(fontSize: 48)),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          // 증가 버튼
          FloatingActionButton(
            onPressed: () {
              ref.read(counterProvider.notifier).update((state) => state + 1);
            },
            child: Icon(Icons.add),
            heroTag: 'increment',
          ),
          SizedBox(height: 10),
          // 감소 버튼
          FloatingActionButton(
            onPressed: () {
              ref.read(counterProvider.notifier).update((state) => state - 1);
            },
            child: Icon(Icons.remove),
            heroTag: 'decrement',
          ),
        ],
      ),
    );
  }
}

김개발 씨는 도전 정신이 생겼습니다. "감소 버튼을 추가해야겠네요.

어떻게 하면 될까요?" 문제 분석하기 먼저 무엇을 해야 하는지 정리해 봅시다. 현재는 FloatingActionButton이 하나만 있고, 누르면 1씩 증가합니다.

여기에 감소 버튼을 추가하여 1씩 빼는 기능이 필요합니다. 그렇다면 두 가지 문제를 해결해야 합니다.

첫째, 감소하는 로직을 어떻게 구현할까요? 둘째, 버튼 두 개를 어떻게 배치할까요?

감소 로직 구현하기 감소 로직은 놀라울 정도로 간단합니다. 증가할 때 state + 1이라고 썼던 것을 state - 1로 바꾸기만 하면 됩니다.

dart ref.read(counterProvider.notifier).update((state) => state - 1); 같은 Provider를 사용하고, 같은 update 메서드를 사용합니다. 단지 더하기를 빼기로 바꿨을 뿐입니다.

이것이 Riverpod의 우아함입니다. 두 개의 버튼 배치하기 FloatingActionButton은 기본적으로 화면 오른쪽 아래에 하나만 표시됩니다.

두 개를 표시하려면 어떻게 해야 할까요? 해결책은 Column 위젯을 사용하는 것입니다.

floatingActionButton 속성에 단일 버튼 대신 Column을 넣으면 여러 버튼을 수직으로 배치할 수 있습니다. mainAxisAlignment를 end로 설정하여 화면 아래쪽에 버튼들을 모았습니다.

SizedBox로 버튼 사이에 10픽셀 간격을 주어 겹치지 않게 했습니다. heroTag의 필요성 코드에서 heroTag라는 생소한 속성이 보입니다.

이게 무엇일까요? Flutter는 화면 전환 시 Hero 애니메이션을 제공합니다.

같은 화면에 FloatingActionButton이 여러 개 있으면 Flutter가 혼란스러워합니다. "어느 버튼을 애니메이션할까?" heroTag를 명시하면 각 버튼을 구별할 수 있습니다.

increment와 decrement처럼 고유한 값을 주면 됩니다. 아이콘 선택하기 증가 버튼에는 Icons.add를, 감소 버튼에는 Icons.remove를 사용했습니다.

Flutter는 Material Icons를 기본 제공하므로 다양한 아이콘을 쉽게 사용할 수 있습니다. 사용자는 플러스와 마이너스 아이콘을 보고 직관적으로 기능을 이해할 수 있습니다.

UI 디자인에서 아이콘 선택은 매우 중요합니다. 음수 처리 고민하기 버튼을 계속 누르면 0, -1, -2로 내려갑니다.

음수를 허용할 것인지 결정해야 합니다. 만약 0 미만으로 내려가지 않게 하려면 어떻게 할까요?

update 메서드 안에 조건을 추가하면 됩니다. ```dart ref.read(counterProvider.notifier).update((state) => state > 0 ?

state - 1 : 0); ``` 이렇게 하면 0일 때 감소 버튼을 눌러도 그대로 0으로 유지됩니다. 삼항 연산자를 사용한 간결한 코드입니다.

추가 개선 아이디어 여기서 더 나아갈 수 있습니다. 초기화 버튼을 추가해 볼까요?

0으로 리셋하는 버튼입니다. dart FloatingActionButton( onPressed: () { ref.read(counterProvider.notifier).state = 0; }, child: Icon(Icons.refresh), heroTag: 'reset', ), state 프로퍼티에 직접 0을 할당하여 초기화합니다.

이것도 유효한 방법입니다. 실무 확장 예시 이 패턴은 실무에서 자주 씁니다.

음량 조절 버튼, 수량 선택 버튼, 페이지 네비게이션 등 증가와 감소가 필요한 모든 곳에 적용할 수 있습니다. 예를 들어 쇼핑몰 앱의 장바구니에서 상품 수량을 조절할 때 똑같은 로직을 사용합니다.

Provider만 cartQuantityProvider로 바꾸면 됩니다. 테스트해 보기 김개발 씨가 앱을 실행했습니다.

증가 버튼을 누르니 0, 1, 2로 올라갑니다. 감소 버튼을 누르니 1, 0, -1로 내려갑니다.

완벽하게 작동합니다! "제가 직접 기능을 추가했어요!" 김개발 씨가 뿌듯해합니다.

배운 내용을 응용하여 새로운 기능을 구현한 것입니다. 학습 포인트 정리 이 연습을 통해 무엇을 배웠을까요?

첫째, 같은 Provider에 대해 여러 가지 작업을 할 수 있다는 것을 알았습니다. 증가, 감소, 초기화 모두 가능합니다.

둘째, UI 레이아웃을 조정하는 방법을 배웠습니다. Column으로 여러 버튼을 배치하고, 간격을 조절했습니다.

셋째, 조건 로직을 추가하는 방법을 고민했습니다. 음수를 막거나 최댓값을 설정하는 등 실무에 필요한 기능입니다.

박시니어 씨가 칭찬합니다. "잘했어요!

이제 Riverpod의 기본을 완전히 마스터했네요. 다음 단계는 StateNotifier로 더 복잡한 상태를 관리하는 거예요." 김개발 씨는 자신감이 생겼습니다.

간단한 카운터 앱에서 시작했지만, 이제 어떤 앱이든 만들 수 있을 것 같습니다. Riverpod의 기초를 탄탄히 다진 것입니다.

실전 팁

💡 - 여러 FloatingActionButton을 사용할 때는 heroTag를 반드시 지정하세요

  • 음수나 최댓값 제한이 필요하면 update 메서드 안에 조건을 추가하세요
  • 직접 기능을 추가해 보며 학습 내용을 응용하는 것이 가장 좋은 학습법입니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Flutter#Riverpod#Provider#StateProvider#ConsumerWidget#Flutter,Riverpod