Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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