Websocket ed esecuzione sincrona
Fino ad ora abbiamo solo coperto come creare un Future
.
Abbiamo fatto ciò apposta, dato che i Future
s sono una parte fondamentale di come
le applicazioni che usano Riverpod dovrebbero essere costruite.
Ma, Riverpod supporta anche altri formati se necessario.
In particolare, invece di un Future
, i provider sono liberi di:
- Ritornare in modo sincrono un oggetto, come creare un "Repository".
- Ritornare uno
Stream
, come ascoltare i websocket.
Restituire un Future
e restituire uno Stream
o un oggetto è abbastanza simile nel complesso.
Considera questa pagina come una spiegazione delle sottili differenze e de vari
suggerimenti per questi casi d'uso.
Restituire un oggetto in modo sincrono
Per creare un oggetto in modo sincrono, assicurati che il tuo provider non ritorni un Future:
int synchronousExample(Ref ref) {
return 0;
}
Quando un provider crea un oggetto in modo sincrono, questo impatta come quell'oggetto verrà consumato. In particolare, i valori sincroni non sono contenuti in un "AsyncValue":
Consumer(
builder: (context, ref, child) {
// The value is not wrapped in an "AsyncValue"
int value = ref.watch(synchronousExampleProvider);
return Text('$value');
},
);
La conseguenza di questa differenza è che se il tuo provider lancia un'eccezione,
provare a leggere il valore ricauserà l'errore.
In alternativa, quando si usa ref.listen
, la callback "onError" sarà invocata.
Considerazioni sugli oggetti "Listenable"
Gli oggetti Listenable come ChangeNotifier
or StateNotifier
non sono supportati.
Se, per motivi di compatibilità, è necessario interagire con uno di questi oggetti,
un workaround consiste nel collegare il loro meccanismo di notifica a Riverpod.
/// A provider which creates a ValueNotifier and update its listeners
/// whenever the value changes.
ValueNotifier<int> myListenable(Ref ref) {
final notifier = ValueNotifier(0);
// Dispose of the notifier when the provider is destroyed
ref.onDispose(notifier.dispose);
// Notify listeners of this provider whenever the ValueNotifier updates.
notifier.addListener(ref.notifyListeners);
return notifier;
}
Nel caso in cui tu abbia bisogno di questa logica molte volte, è importante notare che la logica è condivisa! L'oggetto "ref" è progettato per essere componibile. Ciò consente di estrarre la logica di dispose/ascolto dal provider:
extension on Ref {
// We can move the previous logic to a Ref extension.
// This enables reusing the logic between providers
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
onDispose(notifier.dispose);
notifier.addListener(notifyListeners);
// We return the notifier to ease the usage a bit
return notifier;
}
}
ValueNotifier<int> myListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(0));
}
ValueNotifier<int> anotherListenable(Ref ref) {
return ref.disposeAndListenChangeNotifier(ValueNotifier(42));
}
Ascoltare uno Stream
Un caso d'uso comune delle moderne applicazioni è interagire con websockets,
come Firbase o GraphQL subscriptions.
Interagire con queste API è spesso fatto ascoltando uno Stream
.
Per aiutarci in tale scopo, Riverpod supporta di natura gli oggetti Stream
.
Come con i Future
, l'oggetto verrà convertito in un AsyncValue
:
Stream<int> streamExample(Ref ref) async* {
// Every 1 second, yield a number from 0 to 41.
// This could be replaced with a Stream from Firestore or GraphQL or anything else.
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) {
// The stream is listened to and converted to an AsyncValue.
AsyncValue<int> value = ref.watch(streamExampleProvider);
// We can use the AsyncValue to handle loading/error states and show the data.
return switch (value) {
AsyncValue(:final error?) => Text('Error: $error'),
AsyncValue(:final valueOrNull?) => Text('$valueOrNull'),
_ => const CircularProgressIndicator(),
};
}
}
Riverpod non è consapevole delle implementazioni personalizzate di Stream
,
come ad esempio BehaviorSubject
di RX.
Pertanto, restituire un BehaviorSubject
non esporrà il valore
in modo sincrono ai widget, anche se è già disponibile alla creazione.
Di default, Riverpod convertirà Stream
e Future
in AsyncValue
.
Anche se raramente necessario, è possibile disattivare questo comportamento
wrappando il tipo del valore di ritorno in un typedef Raw
.
È generalmente sconsigliato disabilitare la conversione in AsyncValue
.
Fallo solo se sai quello che stai facendo.
Raw<Stream<int>> rawStream(Ref ref) {
// "Raw" is a typedef. No need to wrap the return
// value in a "Raw" constructor.
return const Stream<int>.empty();
}
class Consumer extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// The value is no-longer converted to AsyncValue,
// and the created stream is returned as is.
Stream<int> stream = ref.watch(rawStreamProvider);
return StreamBuilder<int>(
stream: stream,
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
);
}
}