return ref.watch(findStoreProvider).when(
data: (store) {
_store = store;
return _map;
},
error: (error, stackTrace) => Text(error.toString()),
loading: () => VpLoadingPage(text: 'Loading Google map'));
}
I need the code above to keep in a loading state until store.map$
has had one emission (which happens in findVgnItms()
down the bottom of the provider class). So it is essentially a StreamBuilder that waits for a stream on a future to emit. How is that done? I'm expecting that this: ref.watch(findStoreProvider)
is converted to a stream and combined with store.$map
but that is on the Future so seems impossible?:
return ref.watch(findStoreProvider).when(
data: (store) {
_store = store;
return _map;
},
error: (error, stackTrace) => Text(error.toString()),
loading: () => VpLoadingPage(text: 'Loading Google map'));
}
@override
Widget build(BuildContext context, WidgetRef ref) {
if (defaultTargetPlatform == TargetPlatform.android) {
AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
}
_vm = ref.watch(findPageVm);
_widgetRef = ref;
return ref.watch(findStoreProvider).when(
data: (store) {
_store = store;
return _map;
},
error: (error, stackTrace) => Text(error.toString()),
loading: () => VpLoadingPage(text: 'Loading Google map'));
}
Widget get _map {
return StreamBuilder(
stream: _store.map$,
builder: (context, AsyncSnapshot<VpMap> snapshot) {
return _googleMap(map: snapshot.data);
});
}
Widget _googleMap({VpMap? map}) {
final x = _widgetRef.watch(provider1);
return Stack(children: [
VpLoadingPage(text: 'Loading Google map'),
AnimatedOpacity(
curve: Curves.fastOutSlowIn,
opacity: x ? 1.0 : 0,
duration: const Duration(milliseconds: 600),
child: GoogleMap(
markers: map?.markers ?? const <Marker>{},
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(
_store.userPosition.latitude, _store.userPosition.longitude),
zoom: map?.zoom ?? 13,
),
onMapCreated: (GoogleMapController controller) {
_vm.controller.complete(controller);
Future.delayed(const Duration(milliseconds: 550),
() => _widgetRef.read(provider1.notifier).isLoaded());
},
)),
]);
}
the class that has the future provider with a stream on it:
final findStoreProvider = FutureProvider((provider) async {
final userStore = await provider.watch(userStoreProvider.future);
return FindStore(provider, userStore: userStore);
});
class FindStore extends Store {
FindStore(this.$provider, {required UserStore userStore}) {
map$ ??= _mapController.stream;
findVgnItms();
_userStore = userStore;
}
final Ref $provider;
Stream<VpMap>? map$;
late final StreamController<VpMap> _mapController =
StreamController.broadcast();
late UserStore _userStore;
Set<Marker> markers = <Marker>{};
Stream<String?>? searchTerm;
Position get userPosition => _userStore.userPosition;
Future<List<VgnItmEst>> _$search(String searchTerm) async {
final result =
await $provider.watch(searchResultsProvider(searchTerm).future);
return result;
}
Set<Marker> _mapSearchResultsToMarkers(List<VgnItmEst> searchResults) {
return searchResults
.map((searchResult) => Marker(
markerId: MarkerId(searchResult.id.toString()),
position: LatLng(searchResult.establishment!.location!.lat,
searchResult.establishment!.location!.lng)))
.toSet();
}
Future<FindStore> findVgnItms([String searchTerm = '']) async {
markers = _mapSearchResultsToMarkers(await _$search(searchTerm));
final map = VpMap(position: _userStore.userPosition, markers: markers);
_mapController.add(map);
return this;
}
}
If you need a Stream
that should wait on a Future
to complete, you might want to look into async generators and the async*
and yield*
keywords.
Here's a short example:
Stream<MyObject> getStreamThatWaitsOnFuture() async* {
await functionThatReturnsAFuture();
yield* functionThatReturnsAStream();
}
I've written a short post about it here: https://medium.com/@dumazy/create-a-stream-that-requires-a-future-in-dart-692f6f089a7e