Flutter: Riverpod - Riverpod란 무엇인가?, Riverpod을 사용하는 방법

yeahsilver·2023년 6월 16일
0
post-thumbnail

Riverpod이란?

: 경랑화된 상태관리 라이브러리

Riverpod의 특징

  • 런타임이 아닌 컴파일 시간에 프로그램임 오류를 감지
  • 어플리케이션 상태를 쉽게 관리하고 액세스하는 동시에 상태 불변성 (state immutabilty), 상태 격리 (state isolation), 의존성 주입 (Dependency Injection)과 같은 강력한 기능을 제공
  • StreamProvider, FutureProvider를 사용하여 어플리케이션 상태를 용이하게 관리 가능
  • 테스팅할 수 있는 코드 작성 가능
  • 상태에 더욱 쉽게 접근 가능하고, 위젯간 쉽게 공유 가능
  • 프로그램이 효율적으로 실행되고 있는지 확인 가능

Riverpod이 만들어진 이유

  • 위젯 재빌드 시 상태값 손실에 대한 걱정 없이 안전하게 상태를 생성, dispose 할 수 있게 하기 위해
  • 여러개의 위젯이 존재할 시 상속된 위젯의 가독성을 향상 시키기 위해
  • 단일 방향 데이터 흐름을 통해 어플리케이션의 확장성을 높이기 위해
  • Runtime exception 없이 사용 가능하게 하기 위해

등 여러 이유가 존재한다.

Provider를 생성하는 방법

1. 기본 프로바이더 작성

final exampleProvider = Provider<int>((ref) => 100);
  • 정수값을 반환하는 프로바이더를 생성하고 싶을 경우 위와 같이 작성
  • 해당 프로바이더는 데이터를 읽기만 할 수 있음 (데이터 변경 불가)
  • 프로바이더를 작성할 때에는 무조건 final 사용

2. ProviderScope

  • 프로바이더를 작동할 수 있게하는 위젯
  • 루트 위젯에 Provider를 감싸주는 형태로 구현
void main() {
	runApp(ProviderScope(child: MyApp());
}

Provider 사용법

Provider의 데이터를 사용하기 위해서는 두가지 방법이 존재

1. Consumer 위젯 사용

final myValueProvider = Provider<int>((ref) {
  return 100;
});

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer(       // <==
        builder: (context, ref, child) {
          final value = ref.watch(myValueProvider);
          return Text(value.toString());
        },
      ),
    );
  }
}
  • 텍스트 위젯을 Consumer위젯으로 감싼 형태로 구현
  • Consumer 위젯을 사용할 시 builder라는 매개 변수 존재
  • 해당 코드의 의미는 value (= myValueProvider)가 변경될 때 마다 Consumer 위젯 내부에 있는 위젯만 새로 빌드되고, 나머지 위젯은 그대로 유지
  • 해당과 같이 사용하게 되면, 앱의 성능 향상 가능

2. ConsumerWidget을 StatelessWidget 대신 사용

final myValueProvider = Provider<int>((ref) {
  return 100;
});

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(myValueProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        child: Text(
          value.toString(),
          style: Theme.of(context).textTheme.headline2,
        ),
      ),
    );
  }
}
  • Consumer위젯으로 상태 변경을 하고 싶은 위젯을 감싸는 대신, 위젯 자체를 ConsumerWidget으로 변경하여 Consumer 위젯과 동일하게 기능 작동
  • ConsumerWidget== StatelessWidget + Provider 사용

Ref

1. ref.read()

  • 값을 listen하지 않고 provider의 값을 직접 읽는 방법
  • 버튼을 클릭했을 시와 같이 유저의 인터랙션을 통해 trigger

예시

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            ref.read(loginRepository.notifier).login();
          },
          child: Text(
            value.toString(),
            style: Theme.of(context).textTheme.headline2,
          ),
        ),
      ),
    );
  }
}

2. ref.watch()

  • ref는 다른 프로바이더를 가지고 있을 수 있음
  • 그래서, 생성한 provider가 다른 provider 값에 의존하는 경우, 다른 provider의 정보를 가지고 오거나 listen할 때 사용
final helloStringProvider = StateProvider<String>((ref) {
  return 'Hello';
});

final worldStringProvider = StateProvider<String>((ref) {
  return 'World';
});

final helloWorldStringProvider = Provider<String>((ref) {
 final hello = ref.watch(helloStringProvider); // obtaining the helloStringProvider value inside this provider.
 final world= ref.watch(worldStringProvider); // obtaining the worldStringProvider value inside this provider.
 return '$hello$world';
});

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(helloWorldStringProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        child: Text(
          value,
          style: Theme.of(context).textTheme.headline2,
        ),
      ),
    );
  }
}
  • watch하고 있는 provider의 값이 변경되는 경우, provider를 subscribe하고 있는 위젯이 재빌드

3. ref.listen()

  • 새로운 페이지를 안내, 또는 modal, snack bar를 provider가 변경시 띄우고 싶을 때 사용
  • ref.listen()은 operation을 수행 또는 함수를 호출할 때 사용 (ref.watch()는 listen하고 있는 porivder가 변경되었을 때 위젯과 provider를 재빌드)
final numberProvider = StateProvider<num>((ref) {
  return 1;
});

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

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

    ref.listen(helloStringProvider, (previousValue, newValue) {
      log('Number Changed: $newValue');
    });

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod'),
      ),
      body: Center(
        child: Text(
          value.toString(),
          style: Theme.of(context).textTheme.headline2,
        ),
      ),
    );
  }
}

read, watch, listen 사용법

1. ref.read()

  • 버튼을 클릭하는 등의 액션을 취했을 때, 다른 provider로 부터 값을 얻는 경우 사용
  • ref.read() 은 reactive하지 않기 때문에 사용을 최대한 피하는 것이 좋음.

2. ref.watch()

  • ref.read(), ref.listen()을 사용하는 것 보다 ref.watch()를 사용하는 것을 더욱 선호
    이유) reactive하고 선언적이기 때문에, 더 관리하기 용이
  • onPressed()와 같이 비동기적으로 처리하면 안됨.
  • initState 또는 state 라이프사이클 내부에서 사용해서는 안됨

3. ref.listen()

  • 상태가 변경될 때 무언가를 수행하려고 하는 경우 사용
  • ref.watch()와 동일하게 비동기적으로 처리x, initState 또는 state 라이프사이클 내부에서 사용해서는 안됨

Reference

profile
Hello yeahsilver's world!

0개의 댓글