0

I'm new to Flutter and now I'm trying to create a custom hook function that stores some information from the geolocation service. I'd like to implement it using the existing hooks from flutter_hooks library, without creating a class.

The main idea is to initially load whether the location is enabled or not (Geolocator.isLocationServiceEnabled()) and the latest permission given (Geolocator.checkPermission()).

Then, whoever use the custom hook can call isPermissionGranted(), requestPermission(), and updatePosition() to check and request a new position.

Everything works great at the beginning, but when I call updatePosition() when the user grants permission, my component is not refreshed after getCurrentPositionRefreshKey state update, so Geolocator.getCurrentPosition() is never called.

Here is the code:

Geolocation useGeolocation() {
  // We use these unique keys to have a way to refresh the futures
  ValueNotifier<UniqueKey> isLocationServiceEnabledRefreshKey = useState(UniqueKey());
  ValueNotifier<UniqueKey> checkPermissionRefreshKey = useState(UniqueKey());
  ValueNotifier<UniqueKey?> requestPermissionRefreshKey = useState(null);
  ValueNotifier<UniqueKey?> getCurrentPositionRefreshKey = useState(null);

  // Future that returns whether the location service is enabled or not
  Future<bool> isLocationServiceEnabledFuture = useMemoized(
    () => Geolocator.isLocationServiceEnabled(),
    <UniqueKey>[isLocationServiceEnabledRefreshKey.value],
  );
  AsyncSnapshot<bool> isLocationServiceEnabledSnapshot = useFuture(isLocationServiceEnabledFuture);

  // Future that returns initial permission
  Future<LocationPermission> checkPermissionFuture = useMemoized(
    () => Geolocator.checkPermission(),
    <UniqueKey>[checkPermissionRefreshKey.value],
  );
  AsyncSnapshot<LocationPermission> checkPermissionSnapshot = useFuture(checkPermissionFuture);

  // Future that requests permission to the user
  Future<LocationPermission?> requestPermissionFuture = useMemoized(
    () => requestPermissionRefreshKey.value == null
      ? Future<LocationPermission?>.value(null)
      : Geolocator.requestPermission(),
    <UniqueKey?>[requestPermissionRefreshKey.value],
  );
  AsyncSnapshot<LocationPermission?> requestPermissionSnapshot = useFuture(requestPermissionFuture);

  // Future that gets a new location from geolocation service
  Future<Position?> getCurrentPositionFuture = useMemoized(
    () => getCurrentPositionRefreshKey.value == null
      ? Future<Position?>.value(null)
      : Geolocator.getCurrentPosition(),
    <UniqueKey?>[getCurrentPositionRefreshKey.value],
  );
  AsyncSnapshot<Position?> getCurrentPositionSnapshot = useFuture(getCurrentPositionFuture);

  // Forces future to run again
  void checkLocationService() {
    isLocationServiceEnabledRefreshKey.value = UniqueKey();
  }

  // Forces future to run again
  void checkPermission() {
    checkPermissionRefreshKey.value = UniqueKey();
  }

  // Forces future to run again
  void requestPermission() {
    requestPermissionRefreshKey.value = UniqueKey();
  }

  // Forces future to run again
  void updatePosition() {
    getCurrentPositionRefreshKey.value = UniqueKey();
  }

  bool isPermissionGranted() {
    LocationPermission permission = requestPermissionSnapshot.data ??
        checkPermissionSnapshot.data ??
        LocationPermission.unableToDetermine;
    return permission == LocationPermission.whileInUse || permission == LocationPermission.always;
  }

  return Geolocation(
    locationServiceEnabled: isLocationServiceEnabledSnapshot.data ?? false,
    permission: requestPermissionSnapshot.data ?? checkPermissionSnapshot.data ?? LocationPermission.unableToDetermine,
    position: getCurrentPositionSnapshot.data,
    checkLocationService: checkLocationService,
    checkPermission: checkPermission,
    requestPermission: requestPermission,
    updatePosition: updatePosition,
    isPermissionGranted: isPermissionGranted,
  );
}

@immutable
class Geolocation {
  final bool locationServiceEnabled;
  final LocationPermission permission;
  final Position? position;
  final void Function() checkLocationService;
  final void Function() checkPermission;
  final void Function() requestPermission;
  final void Function() updatePosition;
  final bool Function() isPermissionGranted;

  const Geolocation({
    required this.locationServiceEnabled,
    required this.permission,
    required this.position,
    required this.checkLocationService,
    required this.checkPermission,
    required this.requestPermission,
    required this.updatePosition,
    required this.isPermissionGranted,
  });
}

Here is how I use it:

class Home extends HookWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    Geolocation geolocation = useGeolocation();

    // When the permission changes and the user grants permission, we try to update user location
    useEffect(() {
      if (geolocation.isPermissionGranted()) {
        geolocation.updatePosition();
      }
      return null;
    }, <LocationPermission>[geolocation.permission]);

    // When the location is updated, we send it to the backend.
    // updateUserLocation is never called 
    useEffect(() {
      Position? latestPosition = geolocation.position;
      if (latestPosition != null) {
        updateUserLocation(latestPosition);
      }
      return null;
    }, <Position?>[geolocation.position]);

    // Display the position here
    return SOMETHING_HERE;
  }
}

Does anyone know why my widget is not refreshed along with getCurrentPositionFuture after getCurrentPositionRefreshKey state update?

Sandro Simas
  • 1,268
  • 3
  • 21
  • 35

0 Answers0