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
34 changes: 15 additions & 19 deletions geolocation/src/main/java/com/example/uc1/OneShotOnClickView.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.views.MainLayout;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.html.H1;
Expand All @@ -20,7 +21,7 @@
* UC1 — One-shot request on user click.
* <p>
* Classic "Use my location" button. The click handler calls
* {@code Geolocation.get(...)} and reacts to either the returned
* {@code Geolocation.getPosition(...)} and reacts to either the returned
* {@link GeolocationPosition} or a {@link GeolocationError}. On success the map
* below is centered on the result and a marker is dropped.
*/
Expand All @@ -41,25 +42,20 @@ public OneShotOnClickView() {
map.setWidthFull();

Button locate = new Button("Use my location", e -> {
getUI().orElseThrow().getGeolocation().get(value -> {
switch (value) {
case GeolocationPosition pos -> {
result.setText("lat=%.5f, lon=%.5f (±%.0f m)".formatted(
pos.coords().latitude(), pos.coords().longitude(),
pos.coords().accuracy()));
Coordinate c = new Coordinate(pos.coords().longitude(),
pos.coords().latitude());
map.setCenter(c);
if (map.getZoom() < 14) {
map.setZoom(14);
}
map.getFeatureLayer().removeAllFeatures();
map.getFeatureLayer().addFeature(new MarkerFeature(c));
Geolocation.getPosition(pos -> {
result.setText("lat=%.5f, lon=%.5f (±%.0f m)".formatted(
pos.coords().latitude(), pos.coords().longitude(),
pos.coords().accuracy()));
Coordinate c = new Coordinate(pos.coords().longitude(),
pos.coords().latitude());
map.setCenter(c);
if (map.getZoom() < 14) {
map.setZoom(14);
}
case GeolocationError err -> result
.setText("Error " + err.code() + ": " + err.message());
}
});
map.getFeatureLayer().removeAllFeatures();
map.getFeatureLayer().addFeature(new MarkerFeature(c));
}, err -> result
.setText("Error " + err.code() + ": " + err.message()));
});

add(locate, result, map);
Expand Down
41 changes: 21 additions & 20 deletions geolocation/src/main/java/com/example/uc2/TrackingView.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
import org.jspecify.annotations.Nullable;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.GeolocationOptions;
import com.vaadin.flow.component.geolocation.GeolocationPending;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.geolocation.GeolocationTracker;
import com.vaadin.flow.component.geolocation.GeolocationWatcher;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.H2;
Expand All @@ -37,14 +38,15 @@
/**
* UC2 — Continuous tracking with reactive signal.
* <p>
* A Start/Stop toggle drives a single {@link GeolocationTracker}. The tracker
* A Start/Stop toggle drives a single {@link GeolocationWatcher}. The watcher
* is created lazily on the first click so a fresh page load does not start —
* and immediately stop — a browser watch. Once created, the status text and
* the toggle's label are both bound via {@code bindText} to computed signals
* over the tracker's active and value signals plus an update counter. A single
* effect handles the imperative side that bindings can't express: appending
* each new reading to the grid and extending the path on the map. Resuming
* does not clear the history — the grid and the map line keep growing.
* over the watcher's active and position signals plus an update counter. A
* single effect handles the imperative side that bindings can't express:
* appending each new reading to the grid and extending the path on the map.
* Resuming does not clear the history — the grid and the map line keep
* growing.
*/
@Route(value = "uc2", layout = MainLayout.class)
@PageTitle("UC2 — Continuous tracking with reactive signal")
Expand All @@ -65,7 +67,7 @@ public class TrackingView extends VerticalLayout {
private @Nullable LineStringFeature pathLine;
private @Nullable MarkerFeature startMarker;

private @Nullable GeolocationTracker tracker;
private @Nullable GeolocationWatcher watcher;
private final ValueSignal<Boolean> hasUpdates = new ValueSignal<>(
Boolean.FALSE);
private final ValueSignal<Integer> updateCount = new ValueSignal<>(0);
Expand Down Expand Up @@ -105,29 +107,28 @@ private void configureGrid() {
}

private void toggleTracking() {
if (tracker == null) {
ensureTracker();
if (watcher == null) {
ensureWatcher();
return;
}
if (tracker.activeSignal().peek()) {
tracker.stop();
if (watcher.activeSignal().peek()) {
watcher.stop();
} else {
tracker.resume();
watcher.resume();
}
}

private void ensureTracker() {
private void ensureWatcher() {
GeolocationOptions options = GeolocationOptions.builder()
.highAccuracy(true).maximumAge(Duration.ZERO).build();
GeolocationTracker t = getUI().orElseThrow().getGeolocation()
.track(this, options);
tracker = t;
GeolocationWatcher w = Geolocation.watchPosition(this, options);
watcher = w;

status.bindText(Signal.computed(() -> {
if (!t.activeSignal().get() && hasUpdates.get()) {
if (!w.activeSignal().get() && hasUpdates.get()) {
return "Stopped after " + updateCount.get() + " updates";
}
return switch (t.valueSignal().get()) {
return switch (w.positionSignal().get()) {
case GeolocationPending p -> "Waiting for first reading…";
case GeolocationPosition pos -> "Update #" + updateCount.get();
case GeolocationError err ->
Expand All @@ -136,13 +137,13 @@ private void ensureTracker() {
}));

Signal.effect(this, () -> {
if (t.valueSignal().get() instanceof GeolocationPosition pos) {
if (w.positionSignal().get() instanceof GeolocationPosition pos) {
appendPosition(pos);
}
});

toggle.bindText(Signal.computed(() -> {
boolean active = t.activeSignal().get();
boolean active = w.activeSignal().get();
if (active) {
return "Stop tracking";
}
Expand Down
27 changes: 12 additions & 15 deletions geolocation/src/main/java/com/example/uc3/AutoFetchView.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationAvailability;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.GeolocationOptions;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.html.Span;
Expand Down Expand Up @@ -50,10 +48,10 @@ public AutoFetchView() {
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
Geolocation geo = attachEvent.getUI().getGeolocation();
permissionHint
.bindText(geo.availabilitySignal().map(AutoFetchView::hintFor));
if (geo.availabilitySignal().peek() == GeolocationAvailability.GRANTED) {
var availability = Geolocation
.availabilityHintSignal(attachEvent.getUI());
permissionHint.bindText(availability.map(AutoFetchView::hintFor));
if (availability.peek() == GeolocationAvailability.GRANTED) {
fetchAndPopulate();
}
// PROMPT / DENIED / UNKNOWN / UNSUPPORTED: do nothing automatic.
Expand All @@ -74,14 +72,13 @@ private void fetchAndPopulate() {
GeolocationOptions opts = GeolocationOptions.builder()
.timeout(Duration.ofSeconds(5))
.maximumAge(Duration.ofMinutes(5)).build();
getUI().orElseThrow().getGeolocation().get(opts, result -> {
switch (result) {
case GeolocationPosition pos -> localContent
.setText("Local content for lat=%.4f, lon=%.4f".formatted(
pos.coords().latitude(), pos.coords().longitude()));
case GeolocationError err ->
localContent.setText("Could not auto-fetch: " + err.message());
}
});
Geolocation.getPosition(
pos -> localContent.setText(
"Local content for lat=%.4f, lon=%.4f".formatted(
pos.coords().latitude(),
pos.coords().longitude())),
err -> localContent
.setText("Could not auto-fetch: " + err.message()),
opts);
}
}
28 changes: 13 additions & 15 deletions geolocation/src/main/java/com/example/uc4/DenialView.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.card.Card;
import com.vaadin.flow.component.card.CardVariant;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationAvailability;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.GeolocationErrorCode;
Expand Down Expand Up @@ -214,8 +215,8 @@ private void handle(GeolocationResult value) {
// ------------------------------------------------------------------

/**
* Runs the real {@code Geolocation.get()} against the real browser so you
* can verify end-to-end behaviour once the simulations look right.
* Runs the real {@code Geolocation.getPosition()} against the real browser
* so you can verify end-to-end behaviour once the simulations look right.
*/
private static class RealBrowserCard extends Card {

Expand Down Expand Up @@ -244,23 +245,20 @@ protected void onAttach(com.vaadin.flow.component.AttachEvent e) {
// Bind to the availability signal so the label tracks browser
// permission flips (granted → denied, prompt → granted) instead
// of showing a snapshot taken on attach.
availabilityLabel
.bindText(e.getUI().getGeolocation().availabilitySignal()
.map(a -> "Current availability: " + a));
availabilityLabel.bindText(Geolocation
.availabilityHintSignal(e.getUI())
.map(a -> "Current availability: " + a));
}

private void runReal() {
getUI().orElseThrow().getGeolocation().get(value -> {
switch (value) {
case GeolocationPosition pos ->
output.setText("Position: lat=%.5f, lon=%.5f (±%.0f m)"
.formatted(pos.coords().latitude(),
Geolocation.getPosition(
pos -> output.setText(
"Position: lat=%.5f, lon=%.5f (±%.0f m)".formatted(
pos.coords().latitude(),
pos.coords().longitude(),
pos.coords().accuracy()));
case GeolocationError err -> output.setText(
"Error (" + err.errorCode() + "): " + err.message());
}
});
pos.coords().accuracy())),
err -> output.setText(
"Error (" + err.errorCode() + "): " + err.message()));
}

}
Expand Down
12 changes: 4 additions & 8 deletions geolocation/src/main/java/com/example/uc5/DetailedDataView.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import com.example.views.MainLayout;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationCoordinates;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.GeolocationOptions;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.html.Div;
Expand Down Expand Up @@ -40,13 +40,9 @@ public DetailedDataView() {
output.removeAll();
GeolocationOptions opts = GeolocationOptions.builder()
.highAccuracy(true).timeout(Duration.ofSeconds(10)).build();
getUI().orElseThrow().getGeolocation().get(opts, value -> {
switch (value) {
case GeolocationPosition pos -> renderPosition(output, pos);
case GeolocationError err ->
output.add(new Span("Error: " + err.message()));
}
});
Geolocation.getPosition(pos -> renderPosition(output, pos),
err -> output.add(new Span("Error: " + err.message())),
opts);
});
add(fetch, output);
}
Expand Down
20 changes: 9 additions & 11 deletions geolocation/src/main/java/com/example/uc6/OptionsView.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import com.example.views.MainLayout;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationOptions;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.H2;
Expand Down Expand Up @@ -59,17 +58,16 @@ private Div profile(String label, GeolocationOptions options) {
Span result = new Span("(not fetched yet)");
Button run = new Button("Run", e -> {
long started = System.currentTimeMillis();
getUI().orElseThrow().getGeolocation().get(options, value -> {
Geolocation.getPosition(pos -> {
long elapsed = System.currentTimeMillis() - started;
switch (value) {
case GeolocationPosition pos ->
result.setText("%.5f, %.5f (±%.0f m) — %d ms".formatted(
pos.coords().latitude(), pos.coords().longitude(),
pos.coords().accuracy(), elapsed));
case GeolocationError err -> result.setText(
result.setText("%.5f, %.5f (±%.0f m) — %d ms".formatted(
pos.coords().latitude(), pos.coords().longitude(),
pos.coords().accuracy(), elapsed));
}, err -> {
long elapsed = System.currentTimeMillis() - started;
result.setText(
"Error: " + err.message() + " (" + elapsed + " ms)");
}
});
}, options);
});
wrapper.add(new HorizontalLayout(run, result));
return wrapper;
Expand Down
25 changes: 10 additions & 15 deletions geolocation/src/main/java/com/example/uc7/FormFieldView.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.jspecify.annotations.Nullable;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.geolocation.GeolocationError;
import com.vaadin.flow.component.geolocation.Geolocation;
import com.vaadin.flow.component.geolocation.GeolocationOptions;
import com.vaadin.flow.component.geolocation.GeolocationPosition;
import com.vaadin.flow.component.grid.Grid;
Expand Down Expand Up @@ -104,21 +104,16 @@ private void capture() {
GeolocationOptions opts = GeolocationOptions.builder()
.highAccuracy(true).timeout(Duration.ofSeconds(10))
.maximumAge(Duration.ZERO).build();
getUI().orElseThrow().getGeolocation().get(opts, value -> {
switch (value) {
case GeolocationPosition pos -> {
if (pos.coords().accuracy() > MAX_ACCURACY_METRES) {
Notification.show(
"Location too imprecise (±%.0f m), please try again."
.formatted(pos.coords().accuracy()));
return;
}
pinnedSignal.set(pos);
Geolocation.getPosition(pos -> {
if (pos.coords().accuracy() > MAX_ACCURACY_METRES) {
Notification.show(
"Location too imprecise (±%.0f m), please try again."
.formatted(pos.coords().accuracy()));
return;
}
case GeolocationError err ->
Notification.show("Could not pin location: " + err.message());
}
});
pinnedSignal.set(pos);
}, err -> Notification
.show("Could not pin location: " + err.message()), opts);
}

private void submit() {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<properties>
<java.version>25</java.version>
<vaadin.version>25.2-SNAPSHOT</vaadin.version>
<flow.version>${vaadin.version}</flow.version>
<flow.version>25.2.geo-api-update-SNAPSHOT</flow.version>
<jspecify.version>1.0.0</jspecify.version>
<spotless.plugin.version>3.1.0</spotless.plugin.version>
<nullability.plugin.version>0.3.0</nullability.plugin.version>
Expand Down
Loading