[Flutter] StateProvider-①

겨레·2024년 7월 22일
post-thumbnail


가장 기본이되는 provider인 state를 사용해보자.



① 새 프로젝트를 만든다.
그리고 사진과 같이 lib 아래 새 폴더와 파일을 만들어준다.




② defalut_layout.dart 코드를 작성한다

  • defalut_layout.dart 코드
import 'package:flutter/material.dart';

class DefalutLayout extends StatelessWidget {
  // 1. 3가지 값 받기
  final String title;
  final Widget body;
  final List<Widget>? actions; // actions는 앱바 오른쪽 긑에 들어갈 값들

  const DefalutLayout({
    super.key,
    // 2. 생성자
    required this.title,
    required this.body,
    this.actions,
  });

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
        actions: actions,
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16),
        child: body,
      ),
    );
  }
}



③ screen/home_screen 폴더 및 파일 생성


  • home_screen.dart 코드
import 'package:flutter/material.dart';
import 'package:riverpod_study/layout/defalut_layout.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  
  Widget build(BuildContext context) {
    // 1. DefalutLayout 넣어주기
    // DefalutLayout 사용하려고 폴더랑 파일 만들었었으니깐!
    return DefalutLayout(
      title: 'HomeScreen',
      body: ListView(
        children: const [],
      ),
    );
  }
}



④ main에 홈스크린 넣어주기

  • main.dart 코드
import 'package:flutter/material.dart';
import 'package:riverpod_study/screen/home_screen.dart';

void main() {
  runApp(
    const MaterialApp(
      home: HomeScreen(),
    ),
  );
}

실행해 보면 아래와 같이 HomeScreen 화면이 잘 나오는 것을 볼 수 있다.




⑤ ElevatedButton 추가

home_screen.dart 코드 중 ListView에다가 테스트해 볼 기능들이 있는
스크린들을 이동할 수 있는 ElevatedButton 을 하나씩 추가.
(눌렀을 때 화면 이동만 되게)



⑥ 이동을 위해 Navigator 추가
home_screen.dart 코드로 가서 onPressed에 이동을 위한 Navigator를 추가해 준다.

  • home_screen.dart 코드
import 'package:flutter/material.dart';
import 'package:riverpod_study/layout/defalut_layout.dart';
import 'package:riverpod_study/screen/state_provider.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  
  Widget build(BuildContext context) {
    // 1. DefalutLayout 넣어주기
    // DefalutLayout 사용하려고 폴더랑 파일 만들었었으니깐!
    return DefalutLayout(
      title: 'HomeScreen',
      body: ListView(
        children: [
          ElevatedButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (_) => const StateProviderScreen(),
                ),
              );
            },
            child: const Text('StateProviderScreen'),
          )
        ],
      ),
    );
  }
}



StateProviderScreen 버튼을 누르면 StateProviderScreen 화면으로 잘 넘어가는 것을 볼 수 있다.


Riverpod 설치하기

플러터에서 쓰려면 그냥 riverpod이 아니라 flutter_riverpod을 설치해야 함!

순서대로 잘 실행하면 riverpod 설치가 완료된다.



⑧ riverpod/state_provider.dart 폴더 및 파일 생성

가장 기본이되는 provider인 state를 사용해보자.


  • state_provider_screen.dart 코드
    • ⑧-1. final numberProvider 라는 변수를 선언한다.
    • ⑧-2. StateProvider 생성한다.
      • StateProvider 파라미터 안에는 하나의 함수((ref) => 0)가 들어가는데,
        그냥 관리하고 싶은 값(0)을 반환해주면 된다.
    • ⑧-3. <> 제너릭에 타입 넣어주기
      • 제너릭에(< >)는 어떤 타입의 값을 관리하게 될지 넣어주면 된다.

import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 변수 선언
// 2. StateProvider 생성
// 0을 넣어 숫자를 상태 관리해보자!
// 3. <> 제너릭에 타입 넣어주기 => 어떤 타입의 값을 관리하게 될지 넣어주는 것!
final numberProvider = StateProvider<int>((ref) => 0);

이렇게 선언해놓고 어떻게 쓸 수 있을까?



⑨ riverpod 사용하기

⑨-1. StatelessWidget을 ConsumerWidget으로 바꾸기
StateProviderScreen으로 돌아가서 StatelessWidget을 ConsumerWidget으로 바꿔준다.

그리고!!!

⑨-2. main.dart에 MaterialApp을 ProviderScope로 감싸준다.
이 두 과정을 거쳐야 riverpod을 사용할 수 있다.



⑩ build 함수에 WidgetRef ref 파라미터 추가하기

ConsumerWidget을 상속받았을 때 달라지는 점은?

기존 StatelessWidget과 99% 같은데, 유일한 1% 차이점은
build 함수 안에 하나의 파라미터를 더 추가해줘야 한다는 것이다.

ref를 사용해 riverpod/state_provider_screen.dart에 선언해 놓은
StateProvider에 접근할 수 있게 된다.




그럼 어떻게 접근할 수 있을까?

⑪ ref.watch 사용해서 특정 provider 바라보기

state_provider_screen.dart 코드에서 build 함수 아래 변수를 선언하고,
ref.watch 사용해서 특정 provider 바라보다가 그 provider가 변경되면 build를 재실행하도록 한다.


⑫ numberProvider 넣어주기
state_provider.dart에 선언해 놓은 numberProvider를 넣어준다.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_study/layout/defalut_layout.dart';
import 'package:riverpod_study/riverpod/state_provider.dart';

class StateProviderScreen extends ConsumerWidget {
  const StateProviderScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    
    // ref.watch => 특정 provider를 바라보고 있다가 
    // 그 provider가 변경되면 build를 재실행 
    final provider = ref.watch(numberProvider);

    return const DefalutLayout(
      title: 'StateProviderScreen',
      body: Column(
        children: [],
      ),
    );
  }
}

⑫ Column에 Text 넣기
provider.toString( )을 해 준 이유는 조금있다 알 수 있음 ~


저장하고 앱을 재실행 해보자!

StateProviderScreen 버튼을 누르면...

StateProviderScreen 화면으로 이동하고 0도 잘 보이는 것을 확인할 수 있다.

그렇다면, 이 0이라는 숫자는 어디서 온걸까?
state_provider.dart 파일의 StateProvider에 있는 0이 바로 이 0이다.

즉, numberProvider를 ref.watch에 넣음으로써 0이라는 반환 값을 가져올 수 있었던 것!



⑬ ElevatedButton 추가하기

그런데 이 값만 가져오는 건 사실 별로 유용하지 않겠지?
진짜 원하는 건 상태를 변경하는 것!
그리고 변경된 데이터를 화면에 보여주고...
이걸 확인하는 작업을 해보자.



⑭ ref 가져오기

그리고 onPressde에 ref를 가져와보자.

여기서는 ref.watch가 아닌 ref.read로 작성해야 한다!

ref.read = 어떤 버튼을 눌렀을 때 실행되는 경우고,
build 함수 안에서 직접적으로 UI에 반영하기 위해 가져올 땐 watch
라고
간단하게 기억하고 있으면 될 것 같다.

  • state_provider_screen.dart 코드
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_study/layout/defalut_layout.dart';
import 'package:riverpod_study/riverpod/state_provider.dart';

class StateProviderScreen extends ConsumerWidget {
  const StateProviderScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch => 특정 provider를 바라보고 있다가
    // 그 provider가 변경되면 build를 재실행
    final provider = ref.watch(numberProvider);

    return DefalutLayout(
      title: 'StateProviderScreen',
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              provider.toString(),
            ),
            ElevatedButton(
              onPressed: () {
                // .update((state) => null );
                // state = 현재 상태 ----> 여기서는 0
                // 그럼 up 버튼을 누르면 state에 +1 더해 반환해줘야겠지?
                ref.read(numberProvider.notifier).update((state) => state + 1);
              },
              child: const Text('UP'),
            ),
          ],
        ),
      ),
    );
  }
}

Image 1 Image 2 Image 3

여기서 스크린을 하나 더 만들어보자!

⑮ StateProviderScreen에 스크린 하나 더 만들기

  • ⑮-1. StatelessWidget으로 _NextScreen을 생성하고,
    StateProviderScreen에서 했던 것처럼 똑같이 DefalutLayout 부분을 복붙해준다.
  • ⑮-2. _NextScreen의 StatelessWidget을 ConsumerWidget으로 변경해 주고,
    build 함수에 WidgetRef ref 파라미터를 추가해준다.
  • ⑮-3. final provider = ref.watch(numberProvider); 도 똑같이 집어넣어준다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_study/layout/defalut_layout.dart';
import 'package:riverpod_study/riverpod/state_provider.dart';

class StateProviderScreen extends ConsumerWidget {
  const StateProviderScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch => 특정 provider를 바라보고 있다가
    // 그 provider가 변경되면 build를 재실행
    final provider = ref.watch(numberProvider);

    return DefalutLayout(
      title: 'StateProviderScreen',
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              provider.toString(),
            ),
            ElevatedButton(
              onPressed: () {
                // .update((state) => null );
                // state = 현재 상태 ----> 여기서는 0
                // 그럼 up 버튼을 누르면 state에 +1 더해 반환해줘야겠지?
                ref.read(numberProvider.notifier).update((state) => state + 1);
              },
              child: const Text('UP'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => const _NextScreen(),
                  ),
                );
              },
              child: const Text('Next Screen'),
            ),
          ],
        ),
      ),
    );
  }
}

class _NextScreen extends ConsumerWidget {
  const _NextScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = ref.watch(numberProvider);

    return DefalutLayout(
      title: 'StateProviderScreen',
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              provider.toString(),
            ),
            ElevatedButton(
              onPressed: () {
                // .update((state) => null );
                // state = 현재 상태 ----> 여기서는 0
                // 그럼 up 버튼을 누르면 state에 +1 더해 반환해줘야겠지?
                ref.read(numberProvider.notifier).update((state) => state + 1);
              },
              child: const Text('UP'),
            ),
          ],
        ),
      ),
    );
  }
}

StateProviderScreen과 _NextScreen 이 둘은 거의 똑같은 코드지만 다른 화면이다.

Image 1 Image 2

여기서 UP 버튼을 누르고 NextScreen 버튼을 눌러서 들어가보면
마지막으로 누른 숫자 그대로 있는 걸 볼 수 있다.

Image 1 Image 2

마찬가지로 NextScreen에서 UP 버튼을 누르고
뒤로가서 StateProviderScreen을 확인해 보면
마지막으로 누른 숫자 그대로 있는 것을 볼 수 있다.


원래 같으면 Navigator를 할 때, final provider라는 값을 변경했으니깐
변경한 값을 _NextScreen에다가 어떻게든 넘겨줘야지 똑같은 값을 공유할 수 있었음.

하지만!!!

riverpod을 사용함으로서 똑같은 numberProvider를
ref.watch 하고만 있으면 간단하게 어디서든 데이터를 서로 넘겨주고 할 필요 없이
같은 프로바이더(numberProvider)를 그냥 불러옴으로써 데이터를 공유할 수 있음!

profile
호떡 신문지에서 개발자로 환생

0개의 댓글