Skip to content
Lucent
SEARCH LUCENT

Search guides, APIs, and examples.

GitHub

Very early and experimental. The language, the generated native code and every package API change without notice. Do not use Lucent in production.

6. Send live positions to JavaScript

A CoreLocation delegate and an Android listener that call a JS function with each new position.

src/location.lucent.ts
import { PLATFORM } from "lucent:platform";
import { CLLocation, CLLocationManager } from "lucent:ios/CoreLocation";
import {
CLLocation,
CLLocationManager,
type CLLocationManagerDelegate,
} from "lucent:ios/CoreLocation";
import { Location, LocationManager } from "lucent:android/android.location";
import { Looper } from "lucent:android/android.os";
import { appContext } from "lucent:android";
import { main } from "lucent:thread";
import { error } from "lucent:core";
import type { Fix } from "./trip.lucent";
// --- iOS ---------------------------------------------------------------------
…
return { latitude: c.latitude, longitude: c.longitude, time: location.timestamp.getTime() };
}
/** Forwards a manager's updates to a Lucent function. */
class Updates implements CLLocationManagerDelegate {
constructor(private readonly onFix: (fix: Fix) => void) {}
locationManager_didUpdateLocations(manager: CLLocationManager, locations: CLLocation[]): void {
for (const location of locations) this.onFix(fromCLLocation(location));
}
}
// Managers deliver to the thread that made them: made on the main thread, kept here.
const managers = new Map<number, CLLocationManager>();
// --- Android -----------------------------------------------------------------
function fromLocation(location: Location): Fix {
…
};
}
const listeners = new Map<number, (location: Location) => void>();
// --- The module --------------------------------------------------------------
let nextId = 1;
/** The last position the device knows, without waiting for a new one. */
export async function lastFix(): Promise<Fix | null> {
if (PLATFORM === "ios") {
…
return location ? fromLocation(location) : null;
}
}
/** Calls `onFix` with each new position, until `stop(id)`. */
export async function watch(onFix: (fix: Fix) => void): Promise<number> {
const id = nextId++;
if (PLATFORM === "ios") {
const updates = new Updates(onFix);
await main(() => {
const manager = new CLLocationManager();
manager.delegate = updates;
managers.set(id, manager);
manager.startUpdatingLocation();
});
} else {
const manager = appContext().getSystemService(LocationManager);
const looper = Looper.getMainLooper();
if (!manager || !looper)
throw error("E_NO_LOCATION", "Location isn't available on this device");
const listener = (location: Location) => onFix(fromLocation(location));
listeners.set(id, listener);
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000n, 0, listener, looper);
}
return id;
}
/** Stops the updates `watch` started, and lets go of its callback. */
export async function stop(id: number): Promise<void> {
if (PLATFORM === "ios") {
const manager = managers.get(id);
managers.delete(id);
if (manager) await main(() => manager.stopUpdatingLocation());
} else {
const listener = listeners.get(id);
listeners.delete(id);
if (listener) appContext().getSystemService(LocationManager)?.removeUpdates(listener);
}
}

Updates implements CLLocationManagerDelegate. Lucent makes an Objective-C object for it that forwards locationManager(_:didUpdateLocations:) to locationManager_didUpdateLocations: Swift's name, with its labels joined by _.

LocationListener has one method to implement, so a plain function is one. removeUpdates takes the same function back, which is why watch keeps it in listeners.

onFix is a JS function. Each update arrives on the Lucent thread and calls it; the call is posted to the JS thread. watch returns an id, and stop(id) removes the manager or listener, which lets go of the callback.

With a permission granted, move the simulated location: the summary updates with each fix.