주요 콘텐츠로 건너뛰기

요청에 인자 전달하기

이전 글에서 "provider"를 정의하여 간단한 GET HTTP 요청을 만드는 방법을 살펴봤습니다.
하지만 HTTP 요청은 외부 매개변수(external parameters)에 의존하는 경우가 많습니다.

예를 들어, 이전에는 사용자에게 무작위 액티비티를 제안하기 위해 Bored API를 사용했습니다. 하지만 사용자가 원하는 액티비티 타입을 필터링하거나 가격 요구 사항 등을 원할 수도 있습니다.
이러한 매개변수는 미리 알 수 없습니다. 따라서 이러한 매개변수를 UI에서 providers에 전달할 방법이 필요합니다.

인수를 허용하도록 providers 업데이트

참고로 이전에는 다음과 같이 provider를 정의했습니다:

// A "functional" provider

Future<Activity> activity(ActivityRef ref) async {
// TODO: perform a network request to fetch an activity
return fetchActivity();
}

// Or alternatively, a "notifier"

class ActivityNotifier2 extends _$ActivityNotifier2 {

Future<Activity> build() async {
// TODO: perform a network request to fetch an activity
return fetchActivity();
}
}

provider에게 매개변수(parameters)를 전달하려면 어노테이션이 달린 함수 자체에 매개변수(parameters)를 추가하기만 하면 됩니다.
예를 들어, 원하는 액티비티 타입에 해당하는 String 인수를 받도록 provider를 업데이트할 수 있습니다:


Future<Activity> activity(
ActivityRef ref,
// provider에 인수를 추가할 수 있습니다.
// 매개변수 타입은 원하는 대로 지정할 수 있습니다.
String activityType,
) async {
// "activityType" 인수를 사용하여 URL을 작성할 수 있습니다.
// "https://boredapi.com/api/activity?type=<activityType>"을 가리키게 됩니다.
final response = await http.get(
Uri(
scheme: 'https',
host: 'boredapi.com',
path: '/api/activity',
// 쿼리 매개변수를 수동으로 인코딩할 필요 없이 "Uri" 클래스가 자동으로 인코딩합니다.
queryParameters: {'type': activityType},
),
);
final json = jsonDecode(response.body) as Map<String, dynamic>;
return Activity.fromJson(json);
}
주의

provider에게 인수(arguments)를 전달할 때는 provider에서 "autoDispose"를 활성화하는 것이 좋습니다.
그렇지 않으면 메모리 누수(memory leaks)가 발생할 수 있습니다.
자세한 내용은 캐시 지우기 및 상태 폐기(disposal)에 반응하기를 참조하세요.

인수(arguments)를 전달하도록 UI 업데이트

이전에는 위젯이 다음과 같이 provider를 소비(consume)했습니다:

    AsyncValue<Activity> activity = ref.watch(activityProvider);

하지만 이제 provider가 인수(arguments)를 받으므로 인수를 사용하는 구문이 약간 달라졌습니다. 이제 provider는 요청된 매개 변수를 사용하여 호출해야 하는 함수입니다.
이와 같이 하드코딩된 타입의 액티비티을 전달하도록 UI를 업데이트할 수 있습니다:

    AsyncValue<Activity> activity = ref.watch(
// The provider is now a function expecting the activity type.
// Let's pass a constant string for now, for the sake of simplicity.
activityProvider('recreational'),
);

provider에게 전달된 매개변수(parameters)는 어노테이션이 달린 함수의 매개변수에서 "ref" 매개변수를 뺀 값에 해당합니다.

정보

서로 다른 인수(arguments)를 가진 동일한 provider를 동시에 수신(listen)하는 것은 전적으로 가능합니다.
예를 들어, 우리의 UI는 "오락" 액티비티와 "요리" 액티비티을 모두 렌더링할 수 있습니다:

    return Consumer(
builder: (context, ref, child) {
final recreational = ref.watch(activityProvider('recreational'));
final cooking = ref.watch(activityProvider('cooking'));

// We can then render both activities.
// Both requests will happen in parallel and correctly be cached.
return Column(
children: [
Text(recreational.valueOrNull?.activity ?? ''),
Text(cooking.valueOrNull?.activity ?? ''),
],
);
},
);

Caching considerations and parameter restrictions

매개변수(parameters)를 providers에게 전달할 때 계산은 여전히 캐시됩니다. 차이점은 이제 계산이 인수별로 캐시(cached per-argument)된다는 점입니다.

즉, 두 개의 위젯이 동일한 매개변수로 동일한 provider를 사용하는 경우 네트워크 요청은 한 번만 이루어집니다.
그러나 두 위젯이 서로 다른 매개변수를 가진 동일한 provider를 사용하는 경우 두 번의 네트워크 요청이 이루어집니다.

이를 위해 Riverpod은 매개변수의 == 연산자에 의존합니다.
따라서 provider에게 전달되는 매개변수가 일관된 동일성(consistent equality)을 갖는 것이 중요합니다.

주의

흔히 저지르는 실수는 새 객체가 ==를 재정의하지 않는데도 새 객체를 프로바이더의 매개변수로 직접 인스턴스화하는 것입니다.
예를 들어, List를 이렇게 전달하고 싶은 유혹을 받을 수 있습니다:

    // We could update activityProvider to accept a list of strings instead.
// Then be tempted to create that list directly in the watch call.
ref.watch(activityProvider(['recreational', 'cooking']));

이 코드의 문제점은 ['recreational', 'cooking'] == ['recreational', 'cooking']false라는 것입니다. 따라서 Riverpod은 두 매개변수가 다르다고 판단하고 새로운 네트워크 요청을 시도합니다.
이렇게 되면 네트워크 요청이 무한 반복되어 사용자에게 진행률 표시기가 영구적으로 표시됩니다.

이 문제를 해결하려면 const 목록(const ['레크리에이션', '요리'])을 사용하거나 ==를 재정의하는 사용자 정의 목록 구현을 사용할 수 있습니다.

이 실수를 발견하는 데 도움이 되려면 riverpod_lint를 사용하여 provider_parameters 린트 규칙을 활성화하는 것이 좋습니다. 그러면 위와 같은 스니펫에 경고가 표시됩니다. 설치 단계는 시작하기를 참조하세요.