Riverpod는 family와 autoDispose 두 가지 수식어(modifiers)를 제공합니다. 이 글에서는 family와 autoDispose 수식어에 대해서 설명합니다.
family
family
의 목적은 외부 파라미터를 전달하여 고유한 프로바이더를 가져오는 것입니다.
일반적인 family
의 사용 사례는 다음과 같습니다.
- FutureProvider와
family
를 결합하여 ID에서Message
를 가져오는 경우. - 번역을 처리하기 위해 현재
Locale
값을 프로바이더로 전달하는 경우.
사용방법
family
수식어를 사용하여 프로바이더를 생성하면 파라미터가 추가됩니다. 그러면 프로바이더는 이 파라미터를 사용하여 일부 상태를 계산하는 요소로 사용할 수 있습니다.
예를 들어 FutureProvider에 family
수식어를 사용하면, id
값을 외부에서 전달받아 Message
를 가져올 수 있습니다.
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
return dio.get('http://my_api.dev/messages/$id');
});
그리고 messagesFamily
프로바이더를 사용할 때 문법(syntax)이 약간 달라집니다. 일반적인 문법(syntax)은 더 이상 작동하지 않습니다.
Widget build(BuildContext context, WidgetRef ref) {
// 에러 – messagesFamily는 프로바이더가 아닙니다.
final response = ref.watch(messagesFamily);
}
다음과 같이 messagesFamily
에 파라미터를 전달해야 합니다.
Widget build(BuildContext context, WidgetRef ref) {
final response = ref.watch(messagesFamily('id'));
}
[!info] 정보
family
를 사용하는 프로바이더에 서로 다른 파라미터를 동시에 전달하는 것도 가능합니다.
예를 들어,titleFamily
프로바이더에 각기 다른 Locale를 파라미터로 전달하여 프랑스어와 영어를 동시에 읽을 수 있습니다.@override Widget build(BuildContext context, WidgetRef ref) { final frenchTitle = ref.watch(titleFamily(const Locale('fr'))); final englishTitle = ref.watch(titleFamily(const Locale('en'))); return Text('fr: $frenchTitle en: $englishTitle'); }
파라미터 제한하기
family
가 올바르게 작동하려면 프로바이더에 전달되는 파라미터의 hashCode
와 ==
가 일치하는 것이 중요합니다. 파라미터는 primitive(bool/int/double/String), constant(providers) 또는 ==
및 hashCode
를 오버라이드 할 수 있는 불변(immutable) 객체여야 하는 것이 가장 이상적입니다.
- primitive 자료형: 컴퓨터 프로그램을 만드는 데 사용되는 기초적인 언어 구성.
[중요] 파라미터가 일정하지 않은 경우에는 autoDispose
를 사용하세요.
family
를 사용하여 검색 필드에 입력된 값을 프로바이더의 파라미터로 전달할 수 있습니다. 하지만 이 값은 자주 변경될 수 있고, 그 값이 다시는 재사용되지 않을 수도 있습니다. 프로바이더가 더 이상 사용되지 않더라도 기본적으로 프로바이더는 절대 해제(destroyed)되지 않기 때문에 메모리 누수(memory leaks)가 발생할 수 있습니다.
family
와 autoDispose
를 함께 사용하면 메모리 누수가 해결됩니다:
final characters = FutureProviderautoDispose.family<List<Character>, String>((ref, filter) async {
return fetchCharacters(filter: filter);
});
멀티 파라미터를 프로바이더에 전달하기
family
는 기본적으로 멀티 파라미터를 프로바이더에 전달하는것이 불가능합니다. family
로 생성한 프로바이더에는 하나의 파라미터만 전달 가능합니다.
하지만 아래의 패키지를 이용하면 여러 값을 하나의 객체에 담아 프로바이더에 전달 할 수 있습니다.
- tuple 패키지의 튜플 객체
- Freezed 또는 built_value 패키지로 생성된 객체
- equatable 사용한 객체
다음은 Freezed 와 equatable를 사용한 샘플 코드입니다.
Freezed
@freezed
abstract class MyParameter with _$MyParameter {
factory MyParameter({
required int userId,
required Locale locale,
}) = _MyParameter;
}
final exampleProvider = ProviderautoDispose.family<Something, MyParameter>((ref, myParameter) {
print(myParameter.userId);
print(myParameter.locale);
// userId/locale를 사용하여 무언가를 처리
});
@override
Widget build(BuildContext context, WidgetRef ref) {
int userId; // userId를 어디선가 읽음.
final locale = Localizations.localeOf(context);
final something = ref.watch(
exampleProvider(MyParameter(userId: userId, locale: locale)),
);
...
}
Equatable
class MyParameter extends Equatable {
MyParameter({
required this.userId,
required this.locale,
});
final int userId;
final Locale locale;
@override
List<Object> get props => [userId, locale];
}
final exampleProvider = Provider.family<Something, MyParameter>((ref, myParameter) {
print(myParameter.userId);
print(myParameter.locale);
// userId/locale를 사용하여 무언가를 처리
});
@override
Widget build(BuildContext context, WidgetRef ref) {
int userId; // userId를 어디선가 읽음.
final locale = Localizations.localeOf(context);
final something = ref.watch(
exampleProvider(MyParameter(userId: userId, locale: locale)),
);
...
}
autoDispose
autoDispose
의 사용 목적은 더 이상 사용하지 않는 프로바이더를 해제(destroy)하는 것입니다.
일반적인 autoDispose
의 사용 사례는 다음과 같습니다.
- Firebase를 사용할 때 불필요한 비용 발생을 피하기 위해 연결(connection)을 끊는 경우
- 사용자가 화면을 나갔다가 다시 들어왔을 때 상태를 초기화 해야하는 경우
사용방법
더 이상 사용하지 않는 프로바이더를 자동으로 해제(destroy)하도록 지시하려면, 프로바이더에 autoDispose
를 추가하기만 하면 됩니다.
final userProvider = StreamProvider.autoDispose<User>((ref) {
// ⋯
});
이게 다입니다. 이제 userProvider
가 더 이상 사용되지 않으면 자동으로 소멸됩니다.
제네릭 파라미터가 autoDispose
이전이 아닌 autoDispose
이후에 전달되는 방식에 주목하세요. autoDispose
는 명명된 생성자(a named constructor)가 아닙니다.
[!note] 노트
필요한 경우autoDipose
를 다른 수식어와 결합할 수 있습니다.final userProvider = StreamProvider.autoDispose.family<User, String>((ref, id) { // ⋯ });
ref.keepAlive
프로바이더에 autoDispose
수식어를 사용하면 ref
객체에 keepAlive
메소드가 추가됩니다.
keepAlive
메소드는 프로바이더가 더 이상 사용되지 않더라도 상태를 유지하도록 Riverpod에 알리는 데 사용됩니다.
다음은 HTTP 요청이 완료된 후 keepAlive
메소드를 실행하는 예제 코드입니다.
final myProvider = FutureProvider.autoDispose((ref) async {
final response = await httpClient.get(...);
ref.keepAlive();
return response;
});
이렇게 하면 HTTP 요청이 완료되기 전, 사용자가 화면을 떠났다가 다시 들어오면 HTTP 요청이 다시 실행됩니다. 그러나 만약 HTTP 요청이 성공적으로 완료된다면 상태는 유지되고 사용자가 화면에 재 진입해도 새로운 요청이 트리거되지는 않습니다.
[!info] 정보
버전 1.0.x에서keepAlive
에 해당하는 속성은maintainState
라는 속성입니다.
예시: 프로바이더를 더 이상 사용하지 않을 때 HTTP 요청 취소하기
FutureProvider와 ref.onDispose
를 결합하여 프로바이더가 더 이상 필요하지 않을 때 HTTP 요청을 쉽게 취소할 수 있습니다.
우리의 목표는 다음과 같습니다.
- 사용자가 화면에 들어왔을 때 HTTP 요청을 시작합니다.
- 만약 HTTP 요청이 완료되기 전에 사용자가 화면을 나가면 HTTP 요청을 취소합니다.
- 만약 HTTP 요청이 성공했다면, 화면을 나갔다가 다시 들어와도 새로운 요청을 시작하지 않습니다.
코드로 구현해 본다면 아래와 같습니다.
final myProvider = FutureProvider.autoDispose((ref) async {
// http 요청을 취소할 수 있는 package:dio의 객체
final cancelToken = CancelToken();
// 프로바이더가 해제된 경우, HTTP 요청을 취소합니다.
ref.onDispose(() => cancelToken.cancel());
// 데이터를 가져오고 취소하기 위한 'cancelToken'을 전달합니다.
final response = await dio.get('path', cancelToken: cancelToken);
// 만약 HTTP 요청이 성공적으로 완료되었다면 상태를 유지합니다.
ref.keepAlive();
return response;
});
인수 유형 'AutoDisposeProvider’는 매개 변수 유형 ‘AlwaysAliveProviderBase’ 에 할당할 수 없습니다.
autoDispose
를 사용할 때, 아래와 같은 유사한 에러와 함께 컴파일이 되지 않는 상황이 발생할 수 있습니다.
The argument type ‘AutoDisposeProvider’ can’t be assigned to the parameter type ‘AlwaysAliveProviderBase’
걱정할 필요가 없습니다! 이 에러는 버그가 발생할 가능성이 높기 때문에 발생하는 오류입니다.
원인은 다음과 같이 autoDispose
를 사용하지 않은 프로바이더에서 autoDispose
를 사용한 프로바이더를 구독하려고 시도한 경우에 발생할 수 있습니다.
final firstProvider = Provider.autoDispose((ref) => 0);
final secondProvider = Provider((ref) {
// 'AutoDisposeProvider<int>'인자 값을
// 'AlwaysAliveProviderBase<Object, Null>' 파라미터 타입으로 할당할 수 없습니다.
ref.watch(firstProvider);
});
위의 코드의 경우에는 firstProvider
가 절대로 해제(disposed)되지 않으므로 바람직하지 않습니다.
이 문제를 해결하려면, 다음과 같이 secondProvider
에도 autoDispose
를 표시하는 것이 좋습니다.
final firstProvider = Provider.autoDispose((ref) => 0);
final secondProvider = Provider.autoDispose((ref) {
ref.watch(firstProvider);
});
or
'개발 > 플러터(Flutter)' 카테고리의 다른 글
(Flutter) Riverpod의 Provider Lifecycles (0) | 2023.03.29 |
---|---|
(Flutter) Riverpod의 ProviderObserver (0) | 2023.03.29 |
(Flutter) Riverpod의 Provider 상태 결합하기 (0) | 2023.03.27 |
(Flutter) Riverpod의 Provider 사용 방법 (0) | 2023.03.26 |
(Flutter) Riverpod의 Provider란 (0) | 2023.03.26 |