Skip to content

Commit 9943057

Browse files
committed
refactor: replace two signals with a single GeolocationState sealed type
A single signal with a sealed type covering pending, position, and error states enables exhaustive pattern matching and is more natural to consume than separate value() and error() signals.
1 parent 61357fe commit 9943057

6 files changed

Lines changed: 94 additions & 51 deletions

File tree

flow-server/src/main/java/com/vaadin/flow/component/geolocation/Geolocation.java

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
* <ul>
3333
* <li>{@link #get(SerializableConsumer)} for a one-shot position request with
3434
* callbacks</li>
35-
* <li>{@link #track(Component)} for continuous position tracking via reactive
36-
* {@link Signal}s, automatically tied to the owner component's lifecycle</li>
35+
* <li>{@link #track(Component)} for continuous position tracking via a reactive
36+
* {@link Signal}, automatically tied to the owner component's lifecycle</li>
3737
* </ul>
3838
*
3939
* <p>
@@ -50,9 +50,12 @@
5050
* <pre>
5151
* Geolocation geo = Geolocation.track(this);
5252
* ComponentEffect.effect(this, () -&gt; {
53-
* GeolocationPosition pos = geo.value().get();
54-
* if (pos != null) {
53+
* switch (geo.state().get()) {
54+
* case GeolocationState.Pending p -&gt; {
55+
* }
56+
* case GeolocationPosition pos -&gt;
5557
* map.setCenter(pos.coords().latitude(), pos.coords().longitude());
58+
* case GeolocationError err -&gt; showError(err.message());
5659
* }
5760
* });
5861
* </pre>
@@ -67,8 +70,8 @@ private record GetResult(GeolocationPosition position,
6770
GeolocationError error) {
6871
}
6972

70-
private final ValueSignal<GeolocationPosition> positionSignal = new ValueSignal<>();
71-
private final ValueSignal<GeolocationError> errorSignal = new ValueSignal<>();
73+
private final ValueSignal<GeolocationState> stateSignal = new ValueSignal<>(
74+
new GeolocationState.Pending());
7275

7376
private Geolocation() {
7477
}
@@ -161,14 +164,15 @@ public static void get(GeolocationOptions options,
161164
* Starts continuous position tracking, tied to the owner component's
162165
* lifecycle.
163166
* <p>
164-
* Position updates are available through {@link #value()} and errors
165-
* through {@link #error()}. Tracking stops automatically when the owner
166-
* component detaches.
167+
* The tracking state is available through {@link #state()}, which starts as
168+
* {@link GeolocationState.Pending} and transitions to
169+
* {@link GeolocationPosition} or {@link GeolocationError} as the browser
170+
* reports updates. Tracking stops automatically when the owner component
171+
* detaches.
167172
*
168173
* @param owner
169174
* the component whose lifecycle controls the tracking
170-
* @return a {@link Geolocation} instance with reactive signals for position
171-
* and error
175+
* @return a {@link Geolocation} instance with a reactive state signal
172176
*/
173177
public static Geolocation track(Component owner) {
174178
return track(owner, null);
@@ -178,16 +182,17 @@ public static Geolocation track(Component owner) {
178182
* Starts continuous position tracking with the given options, tied to the
179183
* owner component's lifecycle.
180184
* <p>
181-
* Position updates are available through {@link #value()} and errors
182-
* through {@link #error()}. Tracking stops automatically when the owner
183-
* component detaches.
185+
* The tracking state is available through {@link #state()}, which starts as
186+
* {@link GeolocationState.Pending} and transitions to
187+
* {@link GeolocationPosition} or {@link GeolocationError} as the browser
188+
* reports updates. Tracking stops automatically when the owner component
189+
* detaches.
184190
*
185191
* @param owner
186192
* the component whose lifecycle controls the tracking
187193
* @param options
188194
* the geolocation options, or {@code null} for browser defaults
189-
* @return a {@link Geolocation} instance with reactive signals for position
190-
* and error
195+
* @return a {@link Geolocation} instance with a reactive state signal
191196
*/
192197
public static Geolocation track(Component owner,
193198
GeolocationOptions options) {
@@ -196,14 +201,13 @@ public static Geolocation track(Component owner,
196201

197202
DomListenerRegistration posReg = el
198203
.addEventListener("vaadin-geolocation-position", e -> {
199-
geo.positionSignal
204+
geo.stateSignal
200205
.set(e.getEventDetail(GeolocationPosition.class));
201-
geo.errorSignal.set(null);
202206
}).addEventDetail().allowInert();
203207

204208
DomListenerRegistration errReg = el
205209
.addEventListener("vaadin-geolocation-error", e -> {
206-
geo.errorSignal
210+
geo.stateSignal
207211
.set(e.getEventDetail(GeolocationError.class));
208212
}).addEventDetail().allowInert();
209213

@@ -222,24 +226,14 @@ public static Geolocation track(Component owner,
222226
}
223227

224228
/**
225-
* Returns a signal that holds the latest position received from the
226-
* browser. The signal value is {@code null} until the first position update
227-
* arrives.
228-
*
229-
* @return a read-only signal with the current position
230-
*/
231-
public Signal<GeolocationPosition> value() {
232-
return positionSignal;
233-
}
234-
235-
/**
236-
* Returns a signal that holds the latest error received from the browser,
237-
* or {@code null} if no error has occurred or a successful position update
238-
* has been received since the last error.
229+
* Returns a signal holding the current tracking state. The signal starts as
230+
* {@link GeolocationState.Pending} and transitions to
231+
* {@link GeolocationPosition} or {@link GeolocationError} as the browser
232+
* reports updates.
239233
*
240-
* @return a read-only signal with the current error
234+
* @return a read-only signal with the current geolocation state
241235
*/
242-
public Signal<GeolocationError> error() {
243-
return errorSignal;
236+
public Signal<GeolocationState> state() {
237+
return stateSignal;
244238
}
245239
}

flow-server/src/main/java/com/vaadin/flow/component/geolocation/GeolocationError.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
* @param message
2525
* a human-readable error message
2626
*/
27-
public record GeolocationError(int code, String message) {
27+
public record GeolocationError(int code,
28+
String message) implements GeolocationState {
2829

2930
/**
3031
* The user denied the request for geolocation.

flow-server/src/main/java/com/vaadin/flow/component/geolocation/GeolocationPosition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
* since the Unix epoch
2727
*/
2828
public record GeolocationPosition(GeolocationCoordinates coords,
29-
long timestamp) {
29+
long timestamp) implements GeolocationState {
3030
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.geolocation;
17+
18+
/**
19+
* Represents the state of a geolocation tracking request.
20+
* <p>
21+
* Three states are possible:
22+
* <ul>
23+
* <li>{@link Pending} — initial state before the browser responds</li>
24+
* <li>{@link GeolocationPosition} — a successful position fix</li>
25+
* <li>{@link GeolocationError} — the browser reported an error</li>
26+
* </ul>
27+
* <p>
28+
* The sealed type enables exhaustive pattern matching:
29+
*
30+
* <pre>
31+
* switch (geo.state().get()) {
32+
* case GeolocationState.Pending p -&gt; {
33+
* }
34+
* case GeolocationPosition pos -&gt; map.setCenter(pos.coords());
35+
* case GeolocationError err -&gt; showError(err.message());
36+
* }
37+
* </pre>
38+
*/
39+
public sealed interface GeolocationState permits GeolocationState.Pending,
40+
GeolocationPosition, GeolocationError {
41+
42+
/**
43+
* Initial state before the browser has responded to the tracking request.
44+
*/
45+
record Pending() implements GeolocationState {
46+
}
47+
}

flow-server/src/test/java/com/vaadin/flow/component/geolocation/GeolocationTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,9 @@ void track_registersListenersAndExecutesWatchJs() {
122122
Geolocation geo = Geolocation.track(component);
123123

124124
Assertions.assertNotNull(geo);
125-
Assertions.assertNotNull(geo.value());
126-
Assertions.assertNotNull(geo.error());
127-
Assertions.assertNull(geo.value().get());
128-
Assertions.assertNull(geo.error().get());
125+
Assertions.assertNotNull(geo.state());
126+
Assertions.assertInstanceOf(GeolocationState.Pending.class,
127+
geo.state().get());
129128

130129
List<PendingJavaScriptInvocation> invocations = ui
131130
.dumpPendingJsInvocations();
@@ -159,8 +158,9 @@ void track_signalUpdatesOnPositionEvent() {
159158
fireEvent(component.getElement(), "vaadin-geolocation-position",
160159
eventData);
161160

162-
GeolocationPosition pos = geo.value().get();
163-
Assertions.assertNotNull(pos);
161+
Assertions.assertInstanceOf(GeolocationPosition.class,
162+
geo.state().get());
163+
GeolocationPosition pos = (GeolocationPosition) geo.state().get();
164164
Assertions.assertEquals(60.1699, pos.coords().latitude());
165165
Assertions.assertEquals(24.9384, pos.coords().longitude());
166166
Assertions.assertEquals(10.0, pos.coords().accuracy());
@@ -187,8 +187,8 @@ void track_signalUpdatesOnErrorEvent() {
187187
fireEvent(component.getElement(), "vaadin-geolocation-error",
188188
eventData);
189189

190-
GeolocationError error = geo.error().get();
191-
Assertions.assertNotNull(error);
190+
Assertions.assertInstanceOf(GeolocationError.class, geo.state().get());
191+
GeolocationError error = (GeolocationError) geo.state().get();
192192
Assertions.assertEquals(GeolocationError.PERMISSION_DENIED,
193193
error.code());
194194
Assertions.assertEquals("User denied geolocation", error.message());
@@ -210,7 +210,7 @@ void track_errorClearedOnNewPosition() {
210210
fireEvent(component.getElement(), "vaadin-geolocation-error",
211211
errEventData);
212212

213-
Assertions.assertNotNull(geo.error().get());
213+
Assertions.assertInstanceOf(GeolocationError.class, geo.state().get());
214214

215215
// Then simulate a successful position
216216
ObjectNode posEventData = JacksonUtils.createObjectNode();
@@ -225,8 +225,8 @@ void track_errorClearedOnNewPosition() {
225225
fireEvent(component.getElement(), "vaadin-geolocation-position",
226226
posEventData);
227227

228-
Assertions.assertNull(geo.error().get());
229-
Assertions.assertNotNull(geo.value().get());
228+
Assertions.assertInstanceOf(GeolocationPosition.class,
229+
geo.state().get());
230230
}
231231

232232
@Test

flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/GeolocationView.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.vaadin.flow.component.UI;
1919
import com.vaadin.flow.component.geolocation.Geolocation;
20+
import com.vaadin.flow.component.geolocation.GeolocationPosition;
2021
import com.vaadin.flow.component.html.Div;
2122
import com.vaadin.flow.component.html.NativeButton;
2223
import com.vaadin.flow.router.Route;
@@ -81,8 +82,8 @@ protected void onShow() {
8182
// a short delay via JS round-trip
8283
UI.getCurrent().getPage().executeJs("return true")
8384
.then(Boolean.class, ok -> {
84-
var pos = geo.value().get();
85-
if (pos != null) {
85+
if (geo.state()
86+
.get() instanceof GeolocationPosition pos) {
8687
Div result = new Div();
8788
result.setId("trackResult");
8889
result.setText("lat="

0 commit comments

Comments
 (0)