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: 3 additions & 4 deletions DESIGN_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ 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.watchPosition()` returning a `GeolocationWatcher`), make the
handle's constructor **package-private** so application code cannot bypass
the facade.
If a feature 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 entry point.

### Keep internal mutators off user-facing classes

Expand Down
14 changes: 0 additions & 14 deletions flow-server/src/main/java/com/vaadin/flow/component/UI.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import tools.jackson.databind.node.BaseJsonNode;

import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.internal.JavaScriptNavigationStateRenderer;
import com.vaadin.flow.component.internal.UIInternalUpdater;
import com.vaadin.flow.component.internal.UIInternals;
Expand Down Expand Up @@ -136,8 +135,6 @@ public class UI extends Component

private final Page page;

private final Geolocation geolocation;

/*
* Despite section 6 of RFC 4122, this particular use of UUID *is* adequate
* for security capabilities. Type 4 UUIDs contain 122 bits of random data,
Expand Down Expand Up @@ -168,7 +165,6 @@ protected UI(UIInternalUpdater internalsHandler) {
Component.setElement(this, Element.get(getNode()));
pushConfiguration = new PushConfigurationImpl(this);
page = new Page(this);
geolocation = new Geolocation(this);
}

/**
Expand Down Expand Up @@ -951,16 +947,6 @@ public Page getPage() {
return page;
}

/**
* Returns the {@link Geolocation} facade for this UI, used to read the end
* user's physical location from the browser.
*
* @return the Geolocation facade
*/
public Geolocation getGeolocation() {
return geolocation;
}

/**
* Updates this UI to show the view corresponding to the given navigation
* target.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,12 @@
import com.vaadin.flow.shared.Registration;

/**
* Port between the {@link Geolocation} facade and whatever delivers actual
* position data — the browser in production, an in-memory test driver in unit
* tests.
* <p>
* <b>Framework internal.</b> Application code does not implement this interface
* directly. Replacement clients are installed at facade construction time by
* registering a {@link GeolocationClientFactory} through Vaadin's
* {@link com.vaadin.flow.di.Lookup Lookup}; {@link Geolocation} then hands the
* resulting client every {@link #get}, {@link #startWatch} and
* {@link #subscribeAvailability} call. When no factory is registered,
* {@code Geolocation} uses the built-in browser-backed client.
* Framework-internal port between the {@link Geolocation} static API and
* whatever delivers actual position data — the browser in production, an
* in-memory driver in browserless tests. Application code does not interact
* with this interface; it is exposed so external test drivers can replace the
* production client via
* {@link com.vaadin.flow.component.internal.UIInternals#setGeolocationClient(GeolocationClient)}.
* <p>
* <b>Threading:</b> all callbacks on this interface (the future returned by
* {@link #get}, the {@code onUpdate} consumer passed to {@link #startWatch},
Expand All @@ -49,10 +44,6 @@ public interface GeolocationClient extends Serializable {
/**
* Issues a one-shot position request. The future completes once the client
* has an answer (a position or an error).
*
* @param options
* tuning options, or {@code null} for browser defaults
* @return a future that completes with the outcome on the UI thread
Comment thread
Artur- marked this conversation as resolved.
*/
CompletableFuture<GeolocationOutcome> get(
@Nullable GeolocationOptions options);
Expand All @@ -61,15 +52,6 @@ CompletableFuture<GeolocationOutcome> get(
* Starts a watch session bound to {@code owner}. Position and error pushes
* are delivered via {@code onUpdate}. The returned handle is used to stop
* the watch and to query whether it is still active.
*
* @param owner
* the component that owns this watch; detaching the component
* does not auto-stop the watch — the caller is responsible
* @param options
* tuning options, or {@code null} for browser defaults
* @param onUpdate
* consumer invoked on the UI thread for every push
* @return a handle for stopping the watch
*/
WatchHandle startWatch(Component owner,
@Nullable GeolocationOptions options,
Expand All @@ -78,35 +60,28 @@ WatchHandle startWatch(Component owner,
/**
* Subscribes to availability changes. The returned registration removes the
* subscription.
*
* @param onChange
* consumer invoked on the UI thread for every availability
* change
* @return a registration that removes the subscription when called
*/
Registration subscribeAvailability(
SerializableConsumer<GeolocationAvailability> onChange);

/**
* Returns the most recently observed availability. Implementations must
* seed an initial value at construction; the result is never null.
*
* @return the current availability
*/
GeolocationAvailability currentAvailability();

/**
* Releases any resources held by this client. Called on UI detach.
* Idempotent: calling more than once is a no-op. After {@code close()}, the
* behavior of {@link #get} and {@link #startWatch} is undefined and the
* facade must not call them.
* Releases any resources held by this client. Called when one client is
* being replaced by another (e.g. when a test driver is installed) and on
* UI detach. Idempotent: calling more than once is a no-op. After
* {@code close()}, the behavior of {@link #get} and {@link #startWatch} is
* undefined and callers must not invoke them.
*/
void close();

/**
* Handle to a watcher watch session. The handle is alive while the
* underlying watch is active; calling {@link #stop()} idempotently tears it
* down.
* Handle to a watch session. The handle is alive while the underlying watch
* is active; calling {@link #stop()} idempotently tears it down.
*/
interface WatchHandle extends Serializable {
/**
Expand All @@ -118,8 +93,6 @@ interface WatchHandle extends Serializable {
/**
* Returns whether the watch is currently active (has not yet been
* stopped or auto-cancelled).
*
* @return {@code true} if the watch is still receiving updates
*/
boolean isActive();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/**
* <b>Framework internal.</b> Factory SPI that produces
* {@link GeolocationClient} instances per {@link UI}, resolved via
* {@link Lookup} when a {@link Geolocation} facade is constructed. When a
* {@link Lookup} the first time {@link Geolocation} is used for a UI. When a
* factory is registered the resulting client replaces the built-in
* browser-backed client for every {@code UI} in the application; when none is,
* {@code Geolocation} uses the browser-backed client.
Expand All @@ -41,7 +41,7 @@ public interface GeolocationClientFactory extends Serializable {

/**
* Creates a {@link GeolocationClient} for the given UI. Called once per UI,
* the first time {@link UI#getGeolocation()} is invoked.
* the first time a {@link Geolocation} entry point is invoked for that UI.
*
* @param ui
* the UI for which the client is created
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,27 @@
*/
package com.vaadin.flow.component.geolocation;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* A failed location reading: the request did not produce a
* {@link GeolocationPosition}.
* <p>
* This is one of the three possible values of a
* {@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:
* Delivered to the error consumer of
* {@link Geolocation#getPosition(com.vaadin.flow.function.SerializableConsumer, com.vaadin.flow.function.SerializableConsumer)}
* and held by the {@link GeolocationWatcher#positionSignal()} signal. Typical
* application code switches on {@link #errorCode()} to react to the specific
* reason:
*
* <pre>
* ui.getGeolocation().getPosition(pos -&gt; showNearest(pos), err -&gt; {
* Geolocation.getPosition(pos -&gt; showOnMap(pos), err -&gt; {
* switch (err.errorCode()) {
* case PERMISSION_DENIED -&gt;
* showExplanation("Location is blocked for this site.");
* case POSITION_UNAVAILABLE -&gt;
* showRetry("Could not determine your location.");
* case TIMEOUT -&gt; showRetry("Location request took too long.");
* case UNKNOWN -&gt; showGenericError("Could not read your location.");
* case UNKNOWN -&gt; showGenericError();
* }
* });
* </pre>
Expand All @@ -45,13 +47,16 @@
* the raw numeric error code as reported by the browser.
* Applications should usually call {@link #errorCode()} instead of
* comparing this directly
* @param message
* a human-readable message from the browser. Mainly useful for
* logging — the wording is not standardised and should not be shown
* to end users as-is
* @param debugInfo
* a free-form description of the failure as reported by the browser.
* Useful for log lines and bug reports — the wording is not
* standardised across browsers and must not be shown to end users
* as-is
*/
public record GeolocationError(int code,
String message) implements GeolocationOutcome {
@JsonProperty("message") String debugInfo)
implements
GeolocationOutcome {

/**
* Returns the error reason as a typed enum suitable for exhaustive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@
package com.vaadin.flow.component.geolocation;

/**
* The actual answer to a geolocation request — either a successful reading or
* an error. Narrower than {@link GeolocationResult}: the "waiting for first
* reading" {@link GeolocationPending} state is excluded because a one-shot
* request never produces it.
* Internal narrowing of {@link GeolocationResult} to the two states that the
* client port resolves to: {@link GeolocationPosition} or
* {@link GeolocationError}. Excludes the "waiting for first reading"
* {@link GeolocationPending} state, which is only meaningful for an active
* {@link GeolocationWatcher} signal.
* <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#getPosition
* Geolocation.getPosition} delivers the position or the error through separate
* callbacks.
* Package-private; the public API exposes successes and errors as separate
* consumers instead.
*/
public sealed interface GeolocationOutcome extends GeolocationResult
sealed interface GeolocationOutcome extends GeolocationResult
permits GeolocationPosition, GeolocationError {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
package com.vaadin.flow.component.geolocation;

/**
* 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.
* The initial state of a newly started watch session, held by
* {@link GeolocationWatcher#positionSignal()} until the browser reports its
* first position or error. One-shot {@link Geolocation#getPosition} callers
* never observe this value.
*/
public record GeolocationPending() implements GeolocationResult {
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
* A successful location reading: the coordinates the browser reported and the
* moment in time they were taken.
* <p>
* This is one of the three possible values of a
* {@link GeolocationWatcher#valueSignal()} signal, and the value passed to the
* success callback of {@link Geolocation#getPosition Geolocation.getPosition}.
* Delivered to the success consumer of
* {@link Geolocation#getPosition(com.vaadin.flow.function.SerializableConsumer, com.vaadin.flow.function.SerializableConsumer)}
* and held by the {@link GeolocationWatcher#positionSignal()} signal.
*
* @param coords
* the latitude/longitude and related fields; see
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,22 @@
import java.io.Serializable;

/**
* 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 GeolocationWatcher#valueSignal()}. A
* {@code GeolocationResult} is always exactly one of three things:
* The value held by {@link GeolocationWatcher#positionSignal()} — a successful
* reading, an error, or the initial "waiting for first reading" state. Always
* exactly one of:
* <ul>
* <li>{@link GeolocationPending} — the initial state of a newly started
* watcher, before the browser has reported anything.</li>
* <li>{@link GeolocationPending} — initial state, 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#getPosition} requests never produce
* {@link GeolocationPending}; they deliver the position and the error through
* separate callbacks instead.
* <p>
* The sealed hierarchy is designed for exhaustive pattern matching. A
* {@code switch} covering the three permitted variants is guaranteed complete
* at compile time.
* The sealed hierarchy supports exhaustive pattern matching:
*
* <pre>
* switch (watcher.valueSignal().get()) {
* switch (watcher.positionSignal().get()) {
* case GeolocationPending p -&gt; showSpinner();
* case GeolocationPosition pos -&gt; map.setCenter(pos.coords());
* case GeolocationError err -&gt; showError(err.message());
* case GeolocationError err -&gt; showError(err.errorCode());
* }
* </pre>
*/
Expand Down
Loading
Loading