[Flutter] Riverpod 으로 상태 관리 하기 3

동동·2022년 7월 5일
3

flutter_riverpod

목록 보기
3/3
post-thumbnail

드디어 riverpod 에 대한 마지막 글을 적어 볼려고 한다. 이전 내용으로도 riverpod 을 사용하는데 기본적인 내용은 다 들어갔지만, 좀 더 멋있게 사용하고 싶다면 이번 내용을 참고하길 바란다.

Modifier

.family

지금 까지 provider 는 기존에 정의된 내용의 정보와 상태를 가지고 오는것에만 특화 되어 있었다. 하지만 종종 호출하는 쪽에서 원하는 정보만 참고하기 위해 정보(파라미터)를 전달해야 하는 경우가 있다. 이 경우 사용하는 것이 family 수식어 이다.

통상적인 family 수식어의 사용 예는 다음과 같다.

  • FutureProvider 와 결합하여 네트워크 요청을 위한 파라미터를 전달하는 경우
  • localization 을 위해 현재 Locale 값을 전달하는 경우

사용법은 예제로 확인 해 보자.

email, password 정보를 입력 받아서 login 을 수행하는 FutureProvider 가 있다고 하자.

class LoginInfo {
  String email;
  String password;

  LoginInfo(this.email, this.password);
}

final doEmailLogin = FutureProvider.family<bool, LoginInfo>((ref, loginInfo) async {
  String email = loginInfo.email;
  String password = loginInfo.password;

  return await dio.get('https://myApi.login', data: {'email', email, 'password', password});
});

여기서 return 부분은 login API 를 호출하고 login 여부를 bool 값으로 반환한다고 가정하자.

family 를 이용하여 provider는 LoginInfo 라는 파라미터를 전달 받을 수 있게 된다.
여기서 family 수식어 사용법은 좀 더 자세히 알아보면, family<반환 타입, 전달 타입> 으로 family 타입을 선언하고, provider 의 인수로 전달되는 함수에 ref 객체와 전달되는 객체을 전달하면 된다.
전달되는 파라미터는 하나가 최대이기에 여러 인수를 한꺼번에 넘기기 위해서는 LoginInfo 와 같이 객체화 시켜서 넘겨주면 된다.

⚠️ 주의
사실 여기서 사용할 수 있는 매개변수는 컴퓨터 주요 자료형 (bool, int, double, String), provider, 그리고 ==hashCode 를 오버라이드 할 수 있는 immutable 객체이어야 한다. immutable 객체를 사용하기 위해서는 Freezed 또는 equatable 을 사용해 주어야 하지만 이번 예제에서는 다루지 않았다.
해당 내용에 대해서는 기회가 되면 추후 따로 글을 작성하도로 하겠다.

이 provider 의 사용 방법은 기존과 동일하게, ref.watch 를 이용하면 되지만, 호출시 인수를 같이 넘겨주면 된다.

Widget build(BuildContext context, WidgetRef ref) {
  LoginInfo loginInfo = LoginInfo('test@abc.com', '1q2w3e4r');
  final response = ref.watch(doEmailLogin(loginInfo));
}

.autoDispose

지금까지 provider 를 사용하면서 느꼈을 지도 모르겠지만, provider도 결국은 변수 이다. 변수는 선언을 하고 더이상 사용하지 않을 경우 메모리에서 해제 시켜주는것이 좋다. 이 때 사용하는 것이 autoDispose 이다.

통상적인 autoDispose 의 유즈 케이스는 다음과 같다.

  • 불필요한 코스트 발생을 피하기 위해 연결을 끊는 경우
  • 사용자가 화면상에서 떠나고 다시 진입했을 때 상태를 초기화 할 경우

위와 같은 경우 autoDispose 를 사용해주면 된다.
사용법은 간단하게 provider 뒤에 .autoDispose 를 붙여주면 된다.

final userProvider = StreamProvider.autoDispose<User>((ref) {

});

또한 autoDispsefamily 수식어는 함께 사용 할 수 있다.

final userProvider = StreamProvider.autoDispose.family<User, String>((ref, id) {

});

Provider Observer

ProviderObserver 는 ProviderContainer 의 변화를 관찰하여, provider 의 상태를 모니터링 할 수 있게 해줍니다. providerObserver 를 통해 현재 앱에서 사용되는 상태(state)들을 모니터링 할 수 있고, 문제 발생시 원인을 파악하는데 도움을 줄 수 있다

ProviderOberver 클래스를 상속 받아서 사용할 수 있고, 아래 3가지 메소드를 override 해서 사용할 수 있다.

  • didAddProvider : provider 가 초기화 될때 마다 호출
  • didDisposeProvider : provider 가 Dispose 될때 마다 호출
  • didUpdateProvider : provider 값이 변경 될때 마다 호출
class Logger extends ProviderObserver {
  
  void didUpdateProvider(
    ProviderBase provider,
    Object? previousValue,
    Object? newValue,
    ProviderContainer container,
  ) {
    print('''
{
  "provider": "${provider.name ?? provider.runtimeType}",
  "newValue": "$newValue"
}''');
  }
}

void main() {
  runApp(
    ProviderScope(observers: [Logger()], child: const MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(home: Home());
  }
}

final counterProvider = StateProvider((ref) => 0, name: 'counter');

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

    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Text('$count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

1개의 댓글

comment-user-thumbnail
2023년 10월 3일

잘봤습니다

답글 달기