Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions DESIGN_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ holds `private final UI ui`, and all `executeJs` calls go through
`ui.getElement()` or `ui.getPage()`. Follow the `Page` / `History`
pattern. Enforce single-instance creation in the constructor if needed.

If the facade hands out a stateful handle (e.g. `Geolocation.track()`
returning a `GeolocationTracker`), make the handle's constructor
**package-private** so application code cannot bypass the facade.
If the facade hands out a stateful handle (e.g.
`Geolocation.watchPosition()` returning a `GeolocationWatcher`), make the
handle's constructor **package-private** so application code cannot bypass
the facade.

### Keep internal mutators off user-facing classes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@
* <p>
* <b>Two usage modes:</b>
* <ul>
* <li>{@link #get(SerializableConsumer, SerializableConsumer)} — one-shot
* position request. Use this when the application only needs to know the user's
* location at a single moment (e.g. on a button click). Takes a pair of
* callbacks — one for a successful {@link GeolocationPosition}, one for a
* <li>{@link #getPosition(SerializableConsumer, SerializableConsumer)} —
* one-shot position request. Use this when the application only needs to know
* the user's location at a single moment (e.g. on a button click). Takes a pair
* of callbacks — one for a successful {@link GeolocationPosition}, one for a
* {@link GeolocationError} — mirroring the W3C
* {@code getCurrentPosition(success, error)} pair and matching
* {@link GeolocationTracker#addPositionListener
* GeolocationTracker.addPositionListener}. An overload accepts a trailing
* {@link GeolocationWatcher#addPositionListener
* GeolocationWatcher.addPositionListener}. An overload accepts a trailing
* {@link GeolocationOptions} for accuracy / timeout / cache-age tuning.</li>
* <li>{@link #track(Component)} — continuous tracking that keeps the server
* updated as the user moves. Returns a {@link GeolocationTracker} whose
* {@link GeolocationTracker#valueSignal() valueSignal()} is a reactive signal
* <li>{@link #watchPosition(Component)} — continuous watching that keeps the
* server updated as the user moves. Returns a {@link GeolocationWatcher} whose
* {@link GeolocationWatcher#valueSignal() valueSignal()} is a reactive signal
* of {@link GeolocationResult}. The browser watch is automatically cancelled
* when the owning component detaches; use {@link GeolocationTracker#stop()} to
* cancel it sooner and {@link GeolocationTracker#resume()} to resume.</li>
* when the owning component detaches; use {@link GeolocationWatcher#stop()} to
* cancel it sooner and {@link GeolocationWatcher#resume()} to resume.</li>
* </ul>
* <b>Availability check:</b>
* <ul>
Expand All @@ -80,19 +80,20 @@
* <pre>
* Button locate = new Button("Use my location");
* locate.addClickListener(
* e -&gt; e.getUI().getGeolocation()
* .get(pos -&gt; showNearest(pos.coords().latitude(),
* e -&gt; e.getUI().getGeolocation().getPosition(
* pos -&gt; showNearest(pos.coords().latitude(),
* pos.coords().longitude()),
* err -&gt; showManualEntry()));
* err -&gt; showManualEntry()));
* </pre>
*
* <p>
* <b>Tracking example:</b>
* <b>Watching example:</b>
*
* <pre>
* GeolocationTracker tracker = UI.getCurrent().getGeolocation().track(this);
* GeolocationWatcher watcher = UI.getCurrent().getGeolocation()
* .watchPosition(this);
* Signal.effect(this, () -&gt; {
* switch (tracker.valueSignal().get()) {
* switch (watcher.valueSignal().get()) {
* case GeolocationPending p -&gt; {
* // waiting for first reading
* }
Expand Down Expand Up @@ -183,8 +184,8 @@ private static GeolocationClient resolveClient(UI ui) {
* browser reports an error instead {@code onError} is invoked with the
* {@link GeolocationError}. The pair mirrors the W3C
* {@code getCurrentPosition(success, error)} signature and matches
* {@link GeolocationTracker#addPositionListener
* GeolocationTracker.addPositionListener}, so callers can share the same
* {@link GeolocationWatcher#addPositionListener
* GeolocationWatcher.addPositionListener}, so callers can share the same
* handler shape between one-shot and watch APIs.
* <p>
* The call returns immediately. The browser may show a permission dialog on
Expand All @@ -198,9 +199,9 @@ private static GeolocationClient resolveClient(UI ui) {
* invoked with the error if the browser reports one; not
* {@code null}
*/
public void get(SerializableConsumer<GeolocationPosition> onSuccess,
public void getPosition(SerializableConsumer<GeolocationPosition> onSuccess,
SerializableConsumer<GeolocationError> onError) {
get(onSuccess, onError, null);
getPosition(onSuccess, onError, null);
}

/**
Expand All @@ -222,14 +223,14 @@ public void get(SerializableConsumer<GeolocationPosition> onSuccess,
* accuracy / timeout / cache-age tuning, or {@code null} to use
* the browser defaults
*/
public void get(SerializableConsumer<GeolocationPosition> onSuccess,
public void getPosition(SerializableConsumer<GeolocationPosition> onSuccess,
SerializableConsumer<GeolocationError> onError,
@Nullable GeolocationOptions options) {
Objects.requireNonNull(onSuccess, "onSuccess callback cannot be null");
Objects.requireNonNull(onError, "onError callback cannot be null");
client.get(options).whenComplete((outcome, error) -> {
if (error != null) {
LOGGER.debug("Geolocation get() failed", error);
LOGGER.debug("Geolocation getPosition() failed", error);
onError.accept(new GeolocationError(
GeolocationErrorCode.UNKNOWN.code(),
"Client-side geolocation bridge failure"));
Expand All @@ -247,61 +248,61 @@ public void get(SerializableConsumer<GeolocationPosition> onSuccess,
* component's lifecycle.
* <p>
* The browser reports new positions whenever it detects movement. Each
* report is delivered to the returned tracker's
* {@link GeolocationTracker#valueSignal() valueSignal()} signal on the UI
* report is delivered to the returned watcher's
* {@link GeolocationWatcher#valueSignal() valueSignal()} signal on the UI
* thread. The initial value is {@link GeolocationPending} until the first
* reading arrives, then transitions to {@link GeolocationPosition} (updated
* on every subsequent reading) or {@link GeolocationError}.
* <p>
* The underlying browser watch is automatically cancelled when
* {@code owner} detaches, so the application does not need to write cleanup
* code for navigation. For cancelling while the view is still attached
* (e.g. a "Stop tracking" button), call {@link GeolocationTracker#stop()}
* on the returned tracker.
* (e.g. a "Stop watching" button), call {@link GeolocationWatcher#stop()}
* on the returned watcher.
* <p>
* <b>Permission-revoke caveat.</b> If the user revokes geolocation
* permission while a watch is active and then grants it again, the browser
* silently stops delivering position updates to the existing watch — this
* is the W3C Geolocation API's documented behavior across browsers, not a
* Flow-specific limitation. To recover after a revoke/regrant cycle, call
* {@link GeolocationTracker#stop()} followed by
* {@link GeolocationTracker#resume()}, which installs a fresh browser
* {@link GeolocationWatcher#stop()} followed by
* {@link GeolocationWatcher#resume()}, which installs a fresh browser
* watch. Applications that want this to happen automatically can subscribe
* to {@link #availabilitySignal()} with {@code Signal.effect(owner, ...)}
* and trigger the stop/resume when the availability transitions back to
* {@link GeolocationAvailability#GRANTED GRANTED}.
*
* @param owner
* the component that owns this tracking session; detaching the
* the component that owns this watching session; detaching the
* component automatically stops the watch
* @return a tracker whose {@link GeolocationTracker#valueSignal()} reports
* progress and whose {@link GeolocationTracker#stop()} cancels the
* @return a watcher whose {@link GeolocationWatcher#valueSignal()} reports
* progress and whose {@link GeolocationWatcher#stop()} cancels the
* watch
*/
public GeolocationTracker track(Component owner) {
return track(owner, null);
public GeolocationWatcher watchPosition(Component owner) {
return watchPosition(owner, null);
}

/**
* Starts continuously watching the user's position with tuning options,
* tied to the owner component's lifecycle. Behaves like
* {@link #track(Component)} but lets the caller request high accuracy, set
* a failure timeout, or accept cached readings. See
* {@link #watchPosition(Component)} but lets the caller request high
* accuracy, set a failure timeout, or accept cached readings. See
* {@link GeolocationOptions} for the available settings.
*
* @param owner
* the component that owns this tracking session; detaching the
* the component that owns this watching session; detaching the
* component automatically stops the watch
* @param options
* accuracy / timeout / cache-age tuning, or {@code null} to use
* the browser defaults
* @return a tracker whose {@link GeolocationTracker#valueSignal()} reports
* progress and whose {@link GeolocationTracker#stop()} cancels the
* @return a watcher whose {@link GeolocationWatcher#valueSignal()} reports
* progress and whose {@link GeolocationWatcher#stop()} cancels the
* watch
*/
public GeolocationTracker track(Component owner,
public GeolocationWatcher watchPosition(Component owner,
@Nullable GeolocationOptions options) {
return new GeolocationTracker(owner, options, client);
return new GeolocationWatcher(owner, options, client);
}

/**
Expand All @@ -317,8 +318,8 @@ public GeolocationTracker track(Component owner,
* <p>
* The signal starts as {@link GeolocationAvailability#UNKNOWN UNKNOWN},
* transitions to the value reported during the initial client bootstrap,
* and updates on every {@link #get} / {@link #track} outcome and on browser
* permission-change events where supported.
* and updates on every {@link #getPosition} / {@link #watchPosition}
* outcome and on browser permission-change events where supported.
* <p>
* <b>Reliability caveats.</b> The value is best-effort, not authoritative —
* it reflects what the browser last reported, and can be briefly stale in
Expand All @@ -337,15 +338,15 @@ public GeolocationTracker track(Component owner,
* correctly.</li>
* <li>On Firefox, permission changes the user makes in browser settings are
* not reliably propagated back — the signal can stay stale until the next
* {@link #get} or {@link #track} call.</li>
* {@link #getPosition} or {@link #watchPosition} call.</li>
* <li>On Chromium, the value updates promptly when the user flips the site
* permission, but there is still a small propagation delay between the
* browser event and the cache update.</li>
* </ul>
* Treat the value as a hint for pre-rendering decisions (e.g. auto-fetching
* on return visits, hiding controls in unsupported contexts). For critical
* paths, call {@link #get} and handle the authoritative result in the
* callback.
* paths, call {@link #getPosition} and handle the authoritative result in
* the callback.
*
* @return the availability signal
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
* <p>
* Returned by {@link Geolocation#availabilitySignal()}. Reading the value does
* <b>not</b> show a permission dialog — it reports whether a dialog would
* appear on the next {@link Geolocation#get} or {@link Geolocation#track} call,
* or whether the call would fail regardless because the feature is unusable in
* this context.
* appear on the next {@link Geolocation#getPosition} or
* {@link Geolocation#watchPosition} call, or whether the call would fail
* regardless because the feature is unusable in this context.
* <p>
* Typical usage:
* <ul>
Expand All @@ -39,8 +39,8 @@
public enum GeolocationAvailability {
/**
* The user has previously granted permission for this origin. A subsequent
* {@link Geolocation#get} or {@link Geolocation#track} call will proceed
* without showing a dialog.
* {@link Geolocation#getPosition} or {@link Geolocation#watchPosition} call
* will proceed without showing a dialog.
*/
GRANTED,

Expand All @@ -53,8 +53,9 @@ public enum GeolocationAvailability {
DENIED,

/**
* Permission has not yet been decided. The next {@link Geolocation#get} or
* {@link Geolocation#track} call will show the browser's permission dialog.
* Permission has not yet been decided. The next
* {@link Geolocation#getPosition} or {@link Geolocation#watchPosition} call
* will show the browser's permission dialog.
*/
PROMPT,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Registration subscribeAvailability(
void close();

/**
* Handle to a tracker watch session. The handle is alive while the
* Handle to a watcher watch session. The handle is alive while the
* underlying watch is active; calling {@link #stop()} idempotently tears it
* down.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
* {@link GeolocationPosition}.
* <p>
* This is one of the three possible values of a
* {@link GeolocationTracker#valueSignal()} signal, and the value passed to the
* error callback of {@link Geolocation#get Geolocation.get}. Typical
* application code switches on {@link #errorCode()} to react to the specific
* reason:
* {@link GeolocationWatcher#valueSignal()} signal, and the value passed to the
* error callback of {@link Geolocation#getPosition Geolocation.getPosition}.
* Typical application code switches on {@link #errorCode()} to react to the
* specific reason:
*
* <pre>
* ui.getGeolocation().get(pos -&gt; showNearest(pos), err -&gt; {
* ui.getGeolocation().getPosition(pos -&gt; showNearest(pos), err -&gt; {
* switch (err.errorCode()) {
* case PERMISSION_DENIED -&gt;
* showExplanation("Location is blocked for this site.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

/**
* Tuning knobs for a geolocation request — controls the accuracy / battery /
* speed / freshness trade-off of a single {@link Geolocation#get} or
* {@link Geolocation#track} call.
* speed / freshness trade-off of a single {@link Geolocation#getPosition} or
* {@link Geolocation#watchPosition} call.
* <p>
* Every field is optional. A {@code null} field means "let the browser decide":
* high accuracy defaults to {@code false}, timeout defaults to no timeout at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
* <p>
* Used as the result type of the internal {@link GeolocationClient#get} future,
* where the sum-type encoding keeps Pending out of the contract. Application
* code rarely references this type directly: {@link Geolocation#get
* Geolocation.get} delivers the position or the error through separate
* code rarely references this type directly: {@link Geolocation#getPosition
* Geolocation.getPosition} delivers the position or the error through separate
* callbacks.
*/
public sealed interface GeolocationOutcome extends GeolocationResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
package com.vaadin.flow.component.geolocation;

/**
* The initial state of a newly started tracking session, held by
* {@link GeolocationTracker#valueSignal()} until the browser reports its first
* position or error. One-shot {@link Geolocation#get} requests never produce
* this value — they deliver a position or an error through separate callbacks.
* The initial state of a newly started watching session, held by
* {@link GeolocationWatcher#valueSignal()} until the browser reports its first
* position or error. One-shot {@link Geolocation#getPosition} requests never
* produce this value — they deliver a position or an error through separate
* callbacks.
*/
public record GeolocationPending() implements GeolocationResult {
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
* moment in time they were taken.
* <p>
* This is one of the three possible values of a
* {@link GeolocationTracker#valueSignal()} signal, and the value passed to the
* success callback of {@link Geolocation#get Geolocation.get}.
* {@link GeolocationWatcher#valueSignal()} signal, and the value passed to the
* success callback of {@link Geolocation#getPosition Geolocation.getPosition}.
*
* @param coords
* the latitude/longitude and related fields; see
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
import java.io.Serializable;

/**
* Anything a tracker can currently hold — a successful reading, an error, or
* Anything a watcher can currently hold — a successful reading, an error, or
* the initial "waiting for first reading" state.
* <p>
* Held by the signal exposed by {@link GeolocationTracker#valueSignal()}. A
* Held by the signal exposed by {@link GeolocationWatcher#valueSignal()}. A
* {@code GeolocationResult} is always exactly one of three things:
* <ul>
* <li>{@link GeolocationPending} — the initial state of a newly started
* tracker, before the browser has reported anything.</li>
* watcher, before the browser has reported anything.</li>
* <li>{@link GeolocationPosition} — a successful reading.</li>
* <li>{@link GeolocationError} — the browser reported an error.</li>
* </ul>
* One-shot {@link Geolocation#get} requests never produce
* One-shot {@link Geolocation#getPosition} requests never produce
* {@link GeolocationPending}; they deliver the position and the error through
* separate callbacks instead.
* <p>
Expand All @@ -38,7 +38,7 @@
* at compile time.
*
* <pre>
* switch (tracker.valueSignal().get()) {
* switch (watcher.valueSignal().get()) {
* case GeolocationPending p -&gt; showSpinner();
* case GeolocationPosition pos -&gt; map.setCenter(pos.coords());
* case GeolocationError err -&gt; showError(err.message());
Expand Down
Loading
Loading