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?