주요 콘텐츠로 건너뛰기

웹소켓 및 동기 실행

지금까지는 Future를 생성하는 방법만 다루었습니다.
이는 의도적으로 Future가 Riverpod 애플리케이션을 빌드하는 방법의 핵심이기 때문입니다. 하지만 Riverpod는 필요한 경우 다른 형식도 지원합니다.

특히 providers는 Future 대신 자유롭게 객체를 반환할 수 있습니다:

  • 'Repository' 생성 등 객체를 동기적으로 반환할 수 있습니다.
  • 웹소켓을 수신하기 위해 Stream을 반환합니다.

Future를 반환하는 것과 Stream 또는 객체를 반환하는 것은 전반적으로 매우 유사합니다. 이 페이지는 이러한 사용 사례에 대한 미묘한 차이점과 다양한 팁을 설명하는 페이지라고 생각하시면 됩니다.

동기적으로 객체 반환하기

객체를 동기적으로 생성하려면 provice가 Future를 반환하지 않는지 확인하세요:


int synchronousExample(Ref ref) {
return 0;
}

provider가 객체를 동기적으로 생성하면 객체가 소비되는 방식에 영향을 미칩니다. 특히 동기식 값은 "AsyncValue"로 래핑되지 않습니다:

  Consumer(
builder: (context, ref, child) {
// 값이 "AsyncValue"로 래핑되지 않습니다.
int value = ref.watch(synchronousExampleProvider);

return Text('$value');
},
);

이 차이로 인해 provice가 에러를 발생시키면 값을 읽으려고(read) 하면 에러가 다시 발생(rethrow)합니다. 또는 ref.listen을 사용할 경우 "onError" 콜백이 호출됩니다.

수신 가능(Listenable) 객체 고려 사항

ChangeNotifier 또는 StateNotifier과 같은 수신가능 객체는 지원되지 않습니다.
호환성상의 이유로 이러한 객체 중 하나와 상호 작용해야 하는 경우, 한 가지 우회 방법은 해당 알림 메커니즘(notification mechanism)을 Riverpod로 연결(pipe)하는 것입니다.

/// 값이 변경될 때마다 ValueNotifier를 생성하고 리스너를 업데이트하는 provider입니다.

ValueNotifier<int> myListenable(Ref ref) {
final notifier = ValueNotifier(0);

// provider가 폐기되면 notifier를 폐기합니다.
ref.onDispose(notifier.dispose);

// ValueNotifier가 업데이트될 때마다 provider의 리스너들에 알립니다.
notifier.addListener(ref.notifyListeners);

return notifier;
}
정보

이러한 로직이 여러 번 필요한 경우, 공유된 로직에 주목할 가치가 있습니다! "ref" 객체는 컴포저블(composable)하게 설계되었습니다. 이를 통해 provider에서 dispose/listening 로직을 추출할 수 있습니다:

extension on Ref {
// 이전 로직을 Ref extension으로 옮길 수 있습니다.
// 이렇게 하면 provider 간에 로직을 재사용할 수 있습니다.
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
onDispose(notifier.dispose);
notifier.addListener(notifyListeners);
// 사용 편의성을 높이기 위해 notifier를 반환합니다.
return notifier;
}
}


ValueNotifier<int> myListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(0));
}


ValueNotifier<int> anotherListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(42));
}

스트림 수신하기(Listening)

최신 애플리케이션의 일반적인 사용 사례는 websocket과 상호 작용하는 것입니다(예: Firebase 또는 GraphQL 구독).
이러한 API와의 상호 작용은 종종 Stream을 수신하여 수행됩니다.

이를 돕기 위해 Riverpod은 Stream 객체를 자연스럽게 지원합니다. Future 객체와 마찬가지로 이 객체는 AsyncValue로 변환됩니다:


Stream<int> streamExample(Ref ref) async* {
// 1초마다 0에서 41 사이의 숫자를 산출합니다.
// 이것은 Firestore나 GraphQL 또는 다른 것의 스트림으로 대체할 수 있습니다.
for (var i = 0; i < 42; i++) {
yield i;
await Future<void>.delayed(const Duration(seconds: 1));
}
}

class Consumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
// 스트림을 수신하고 AsyncValue로 변환합니다.
AsyncValue<int> value = ref.watch(streamExampleProvider);

// 로딩/오류 상태를 처리하고 데이터를 표시하기 위해 AsyncValue를 사용할 수 있습니다.
return switch (value) {
AsyncValue(:final error?) => Text('Error: $error'),
AsyncValue(:final valueOrNull?) => Text('$valueOrNull'),
_ => const CircularProgressIndicator(),
};
}
}
정보

Riverpod은 RX의 BehaviorSubject와 같은 같은 커스텀 Stream 구현을 인식하지 못합니다. 따라서 생성시 이미 사용 가능하더라도 BehaviorSubject를 반환하면 value가 위젯에 동기적으로 노출되지 않습니다.

Stream/FutureAsyncValue로 변환하지 않기

기본적으로 Riverpod는 StreamFutureAsyncValue로 변환합니다. 거의 필요하지 않지만, 반환 유형을 Raw typedef로 감싸서 이 동작을 비활성화할 수 있습니다.

주의

일반적으로 AsyncValue 변환을 비활성화하는 것은 권장하지 않습니다. 자신이 무엇을 하고 있는지 알고 있는 경우에만 그렇게 하세요.


Raw<Stream<int>> rawStream(Ref ref) {
// "Raw"는 타입 정의입니다.
// 반환값을 "Raw" 생성자로 래핑할 필요가 없습니다.
return const Stream<int>.empty();
}

class Consumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
// 값이 더 이상 AsyncValue로 변환되지 않고
// 생성된 스트림이 그대로 반환됩니다.
Stream<int> stream = ref.watch(rawStreamProvider);
return StreamBuilder<int>(
stream: stream,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}