flutterdartgpsriverpodgeolocator

Using geolocator GPS stream with Riverpod StreamProvider


I'm wrestling with using the streaming GPS feature (StreamSubscription) of the geolocator package with the StreamProvider function of Riverpod in order have a global variable that has constantly updated user GPS coordinates. Using the geolocator package I am able to get and console log the user's live GPS coords, but I'm not able to get those streaming values into a Riverpod variable. My code is below. Would appreciate any thoughts or tips. Many thanks.

https://riverpod.dev/docs/providers/stream_provider

https://pub.dev/documentation/geolocator/latest/

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'dart:developer' as developer;

final userGpsProvider = StreamProvider<Position>((ref) {
  
  LocationSettings locationSettings = const LocationSettings(
    accuracy: LocationAccuracy.bestForNavigation,
    distanceFilter: 20, // in meters before an update is generated
  );

  Position? gpsPosition;

  final StreamSubscription<Position> socket =
      Geolocator.getPositionStream(locationSettings: locationSettings)
          .listen((Position? position) {
    developer.log('position ${position.toString()}');
    gpsPosition = position as Position;
  });

  ref.onDispose(socket?.cancel());

  developer.log('gpsPosition ${gpsPosition.toString()}');
  developer.log('socket ${socket.toString()}');

  return gpsPosition as Stream<Position>;
});

I've modified the above code and taken this a step further and have a working version of a sorts. Unfortunately it involves creating some new globals, which I don't want to do.

/// create global variables position, lng and lat 
/// (I don't want to do this, but it works)
late Position position;
String? lng = '';
String? lat = '';

final userGpsProvider = StreamProvider<Position>((ref) async* {
  LocationSettings locationSettings = const LocationSettings(
    accuracy: LocationAccuracy.bestForNavigation,
    distanceFilter: 20, // in meters before an update is generated
  );
  final StreamSubscription<Position> socket =
      Geolocator.getPositionStream(locationSettings: locationSettings)
          .listen((Position? position) {
    developer.log('position ${position.toString()}');
    lng = position?.longitude.toString();
    lat = position?.latitude.toString();
  });
  ref.onDispose(() => socket.cancel());
});

and then, somewhere else, I call:

final dynamic userGPS = ref.watch(userGpsProvider); 
developer.log('$userGPS.toString()');  // AsyncLoading<Position>()
developer.log('from main lng:  $lng');                 // -74.23
developer.log('from main lat:  $lat');                 // 54.23

The globals lng and lat do have correct values, but I only get AsyncLoading in userGPS. I'm hoping to have lat/lng values in userGPS via the Riverpod provider userGpsProvider, and to not have to use global lng and lat. I'm obviously not getting something right with Riverpod StreamProvider and would appreciate any help. Thanks.


Solution

  • Create a provider to hold position value

    final locationProvider = StateProvider<Position>((ref){
      return Position(longitude: 0.0, latitude: 0.0, timestamp: DateTime.now(), accuracy: 0, altitude: 0, heading: 0, speed: 0,      speedAccuracy: 0);
    });
    

    Update the value of locationProvider inside Geolocator.getPositionStream stream

    final locationStateProvider = StreamProvider.autoDispose<Position>((ref){
      LocationSettings locationSettings = const LocationSettings(accuracy: LocationAccuracy.medium, distanceFilter: 20);
      return Geolocator.getPositionStream(locationSettings: locationSettings).map((position){
        ref.read(locationProvider.notifier).state = position;
        return position;
      });
    });
    

    Watch (initialize) for locationStateProvider anywhere in parent tree. No need to assign it to anything

    ref.watch(locationStateProvider);
    

    Watch for location provider

    final location = ref.watch(locationProvider);