Skip to content

Commit 5d799fa

Browse files
Release v0.5.0 (#17)
* feat: extending native implementation to use all play-services-location properties * feat: extending useLocationUpdates hook to match the new properties * chore(example): updating example app to use extended properties * chore(docs): updating docs * chore(tests): adjusting and creating tests * chore(tests): adjusting tests to check uncovered lines * chore(version): upgrading package to v0.5.0
1 parent 934c5e0 commit 5d799fa

File tree

19 files changed

+1772
-65
lines changed

19 files changed

+1772
-65
lines changed

CHANGELOG.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,108 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.5.0] - 2025-11-14
9+
10+
### Added
11+
12+
- 📍 **Extended Location Properties**: Full support for all location data from play-services-location:21.3.0
13+
- `accuracy` - Horizontal accuracy in meters
14+
- `altitude` - Altitude in meters above sea level
15+
- `speed` - Speed in meters per second
16+
- `bearing` - Bearing in degrees (0-360)
17+
- `verticalAccuracyMeters` - Vertical accuracy in meters (Android API 26+)
18+
- `speedAccuracyMetersPerSecond` - Speed accuracy in meters per second (Android API 26+)
19+
- `bearingAccuracyDegrees` - Bearing accuracy in degrees (Android API 26+)
20+
- `elapsedRealtimeNanos` - Elapsed realtime in nanoseconds since system boot
21+
- `provider` - Location provider (gps, network, passive, etc.)
22+
- `isFromMockProvider` - Whether the location is from a mock provider (Android API 18+)
23+
- All properties are optional and only included when available from the location provider
24+
25+
- 🛠️ **Utility Functions**: New utility module for object manipulation
26+
- `extractDefinedProperties()` - Generic function to extract all defined properties from objects
27+
- Located in `src/utils/objectUtils.ts` for reusability across the codebase
28+
- Automatically handles optional properties without manual field listing
29+
30+
- 📱 **Enhanced Example App**: Updated example app to demonstrate extended location properties
31+
- Displays all available location properties in real-time
32+
- Shows formatted values (speed in km/h, elapsed time in ms, etc.)
33+
- Visual separation between required and optional properties
34+
- Demonstrates proper usage of extended location data
35+
36+
### Changed
37+
38+
- 🔧 **TypeScript Interfaces**: Extended `Coords` and `LocationUpdateEvent` interfaces
39+
- Added 10 new optional properties with full JSDoc documentation
40+
- Maintains backward compatibility (all new fields are optional)
41+
- Type-safe access to all location data from play-services-location API
42+
43+
- 🔄 **Hook Implementation**: Enhanced `useLocationUpdates` hook
44+
- Automatically extracts and includes all available location properties
45+
- Uses generic utility function for property extraction
46+
- No manual field mapping required for future property additions
47+
48+
- 📦 **Native Android Module**: Extended LocationService and LocationStorage
49+
- `LocationService.handleLocation()` now extracts all available location data
50+
- `LocationService.sendLocationUpdateEvent()` includes all properties in events
51+
- `LocationStorage.saveLocation()` stores all available properties
52+
- `LocationStorage.getLocations()` returns all stored properties
53+
- Proper handling of API-level specific properties (Android 18+, 26+)
54+
55+
### Technical Details
56+
57+
**File Changes:**
58+
- `android/src/main/java/com/backgroundlocation/LocationService.kt`: Extended to extract and emit all location properties
59+
- `android/src/main/java/com/backgroundlocation/LocationStorage.kt`: Extended to save and retrieve all location properties
60+
- `src/types/tracking.ts`: Added optional properties to `Coords` and `LocationUpdateEvent` interfaces
61+
- `src/hooks/useLocationUpdates.ts`: Enhanced to map all properties using `extractDefinedProperties`
62+
- `src/utils/objectUtils.ts`: New utility module for generic property extraction
63+
- `example/src/App.tsx`: Updated to display all location properties
64+
- `example/src/styles.ts`: Added styles for additional properties display
65+
66+
**Architecture:**
67+
- Generic property extraction eliminates manual field mapping
68+
- Future-proof design automatically includes new properties
69+
- Type-safe access to all location data
70+
- Backward compatible (existing code continues to work)
71+
- Proper handling of optional and API-level specific properties
72+
73+
### Migration Guide
74+
75+
If upgrading from 0.4.0:
76+
77+
**No Breaking Changes** - All existing code continues to work without modifications.
78+
79+
**New Features Available:**
80+
81+
```typescript
82+
// Existing code still works
83+
const { locations } = useLocationUpdates();
84+
locations.forEach((location) => {
85+
console.log(location.latitude);
86+
console.log(location.longitude);
87+
console.log(location.timestamp);
88+
});
89+
90+
// New properties are now available (when provided by location provider)
91+
locations.forEach((location) => {
92+
if (location.accuracy !== undefined) {
93+
console.log(`Accuracy: ${location.accuracy} meters`);
94+
}
95+
if (location.speed !== undefined) {
96+
console.log(`Speed: ${location.speed} m/s`);
97+
}
98+
if (location.altitude !== undefined) {
99+
console.log(`Altitude: ${location.altitude} meters`);
100+
}
101+
// ... and more properties
102+
});
103+
```
104+
105+
**Best Practices:**
106+
- Always check for `undefined` before using optional properties
107+
- Properties may not be available on all devices or Android versions
108+
- Some properties require specific Android API levels (18+, 26+)
109+
8110
## [0.4.0] - 2025-11-05
9111

10112
### Added

README.md

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,15 @@ function TrackingScreen() {
157157
<Text>Status: {isTracking ? 'Tracking' : 'Stopped'}</Text>
158158
<Text>Locations: {locations.length}</Text>
159159
{lastLocation && (
160-
<Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
160+
<>
161+
<Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
162+
{lastLocation.accuracy !== undefined && (
163+
<Text>Accuracy: {lastLocation.accuracy.toFixed(2)} m</Text>
164+
)}
165+
{lastLocation.speed !== undefined && (
166+
<Text>Speed: {(lastLocation.speed * 3.6).toFixed(2)} km/h</Text>
167+
)}
168+
</>
161169
)}
162170
<Button
163171
title={isTracking ? 'Stop' : 'Start'}
@@ -192,7 +200,15 @@ function LiveTrackingScreen() {
192200
<View>
193201
<Text>Locations: {locations.length}</Text>
194202
{lastLocation && (
195-
<Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
203+
<>
204+
<Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
205+
{lastLocation.accuracy !== undefined && (
206+
<Text>Accuracy: {lastLocation.accuracy.toFixed(2)} m</Text>
207+
)}
208+
{lastLocation.speed !== undefined && (
209+
<Text>Speed: {(lastLocation.speed * 3.6).toFixed(2)} km/h</Text>
210+
)}
211+
</>
196212
)}
197213
</View>
198214
);
@@ -245,6 +261,18 @@ locations.forEach((location) => {
245261
console.log(location.latitude); // string
246262
console.log(location.longitude); // string
247263
console.log(location.timestamp); // number (Unix timestamp in ms)
264+
265+
// Extended properties (optional, check for undefined)
266+
if (location.accuracy !== undefined) {
267+
console.log(`Accuracy: ${location.accuracy} meters`);
268+
}
269+
if (location.speed !== undefined) {
270+
console.log(`Speed: ${location.speed} m/s`);
271+
}
272+
if (location.altitude !== undefined) {
273+
console.log(`Altitude: ${location.altitude} meters`);
274+
}
275+
// ... and more properties available
248276
});
249277

250278
// Stop tracking
@@ -329,15 +357,25 @@ Retrieves all stored location points for a specific trip.
329357
- **Parameters:**
330358
- `tripId`: The trip identifier.
331359

332-
- **Returns:** Promise resolving to array of location coordinates:
360+
- **Returns:** Promise resolving to array of location coordinates with extended properties:
333361

334362
```typescript
335363
{
336364
latitude: string; // Latitude as string
337365
longitude: string; // Longitude as string
338366
timestamp: number; // Unix timestamp in milliseconds
339-
}
340-
[];
367+
// Extended properties (optional, available when provided by location provider)
368+
accuracy?: number; // Horizontal accuracy in meters
369+
altitude?: number; // Altitude in meters above sea level
370+
speed?: number; // Speed in meters per second
371+
bearing?: number; // Bearing in degrees (0-360)
372+
verticalAccuracyMeters?: number; // Vertical accuracy (Android API 26+)
373+
speedAccuracyMetersPerSecond?: number; // Speed accuracy (Android API 26+)
374+
bearingAccuracyDegrees?: number; // Bearing accuracy (Android API 26+)
375+
elapsedRealtimeNanos?: number; // Elapsed realtime in nanoseconds
376+
provider?: string; // Location provider (gps, network, passive, etc.)
377+
isFromMockProvider?: boolean; // Whether from mock provider (Android API 18+)
378+
}[];
341379
```
342380

343381
- **Throws:**
@@ -361,9 +399,21 @@ Clears all stored location data for a specific trip.
361399

362400
```typescript
363401
interface Coords {
364-
latitude: string;
365-
longitude: string;
366-
timestamp: number;
402+
latitude: string; // Latitude in decimal degrees
403+
longitude: string; // Longitude in decimal degrees
404+
timestamp: number; // Timestamp in milliseconds since Unix epoch
405+
406+
// Extended location properties (optional, available when provided by location provider)
407+
accuracy?: number; // Horizontal accuracy in meters
408+
altitude?: number; // Altitude in meters above sea level
409+
speed?: number; // Speed in meters per second
410+
bearing?: number; // Bearing in degrees (0-360)
411+
verticalAccuracyMeters?: number; // Vertical accuracy in meters (Android API 26+)
412+
speedAccuracyMetersPerSecond?: number; // Speed accuracy in meters per second (Android API 26+)
413+
bearingAccuracyDegrees?: number; // Bearing accuracy in degrees (Android API 26+)
414+
elapsedRealtimeNanos?: number; // Elapsed realtime in nanoseconds since system boot
415+
provider?: string; // Location provider (gps, network, passive, etc.)
416+
isFromMockProvider?: boolean; // Whether the location is from a mock provider (Android API 18+)
367417
}
368418

369419
interface TrackingStatus {

android/src/main/java/com/backgroundlocation/LocationService.kt

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import android.os.Looper
1111
import androidx.core.app.NotificationCompat
1212
import com.facebook.react.bridge.Arguments
1313
import com.facebook.react.bridge.ReactContext
14+
import com.facebook.react.bridge.WritableMap
1415
import com.facebook.react.modules.core.DeviceEventManagerModule
1516
import com.google.android.gms.location.*
1617

@@ -126,11 +127,51 @@ class LocationService : Service() {
126127

127128
private fun handleLocation(location: Location) {
128129
currentTripId?.let { tripId ->
130+
// Extract all available location data
131+
val accuracy = if (location.hasAccuracy()) location.accuracy else null
132+
val altitude = if (location.hasAltitude()) location.altitude else null
133+
val speed = if (location.hasSpeed()) location.speed else null
134+
val bearing = if (location.hasBearing()) location.bearing else null
135+
136+
// API 26+ fields - check if values are valid (not NaN)
137+
val verticalAccuracyMeters = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
138+
val value = location.verticalAccuracyMeters
139+
if (!value.isNaN()) value else null
140+
} else null
141+
142+
val speedAccuracyMetersPerSecond = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
143+
val value = location.speedAccuracyMetersPerSecond
144+
if (!value.isNaN()) value else null
145+
} else null
146+
147+
val bearingAccuracyDegrees = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
148+
val value = location.bearingAccuracyDegrees
149+
if (!value.isNaN()) value else null
150+
} else null
151+
152+
val elapsedRealtimeNanos = location.elapsedRealtimeNanos
153+
val provider = location.provider
154+
155+
// API 18+ field
156+
val isFromMockProvider = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
157+
location.isFromMockProvider
158+
} else null
159+
129160
storage.saveLocation(
130161
tripId = tripId,
131162
latitude = location.latitude,
132163
longitude = location.longitude,
133-
timestamp = location.time
164+
timestamp = location.time,
165+
accuracy = accuracy,
166+
altitude = altitude,
167+
speed = speed,
168+
bearing = bearing,
169+
verticalAccuracyMeters = verticalAccuracyMeters,
170+
speedAccuracyMetersPerSecond = speedAccuracyMetersPerSecond,
171+
bearingAccuracyDegrees = bearingAccuracyDegrees,
172+
elapsedRealtimeNanos = elapsedRealtimeNanos,
173+
provider = provider,
174+
isFromMockProvider = isFromMockProvider
134175
)
135176

136177
// Emit location update event to React Native
@@ -139,17 +180,12 @@ class LocationService : Service() {
139180
}
140181

141182
/**
142-
* Sends a location update event to React Native
183+
* Sends a location update event to React Native with extended location data
143184
*/
144185
private fun sendLocationUpdateEvent(tripId: String, location: Location) {
145186
reactContext?.let { context ->
146187
try {
147-
val eventData = Arguments.createMap().apply {
148-
putString("tripId", tripId)
149-
putString("latitude", location.latitude.toString())
150-
putString("longitude", location.longitude.toString())
151-
putDouble("timestamp", location.time.toDouble())
152-
}
188+
val eventData = createLocationMap(tripId, location)
153189

154190
context
155191
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
@@ -160,6 +196,60 @@ class LocationService : Service() {
160196
}
161197
}
162198
}
199+
200+
/**
201+
* Creates a WritableMap with all available location data
202+
*/
203+
private fun createLocationMap(tripId: String, location: Location): WritableMap {
204+
val map = Arguments.createMap().apply {
205+
putString("tripId", tripId)
206+
putString("latitude", location.latitude.toString())
207+
putString("longitude", location.longitude.toString())
208+
putDouble("timestamp", location.time.toDouble())
209+
210+
// Add optional fields if available
211+
if (location.hasAccuracy()) {
212+
putDouble("accuracy", location.accuracy.toDouble())
213+
}
214+
if (location.hasAltitude()) {
215+
putDouble("altitude", location.altitude)
216+
}
217+
if (location.hasSpeed()) {
218+
putDouble("speed", location.speed.toDouble())
219+
}
220+
if (location.hasBearing()) {
221+
putDouble("bearing", location.bearing.toDouble())
222+
}
223+
224+
// API 26+ fields - check if values are valid (not NaN)
225+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
226+
val verticalAccuracy = location.verticalAccuracyMeters
227+
if (!verticalAccuracy.isNaN()) {
228+
putDouble("verticalAccuracyMeters", verticalAccuracy.toDouble())
229+
}
230+
231+
val speedAccuracy = location.speedAccuracyMetersPerSecond
232+
if (!speedAccuracy.isNaN()) {
233+
putDouble("speedAccuracyMetersPerSecond", speedAccuracy.toDouble())
234+
}
235+
236+
val bearingAccuracy = location.bearingAccuracyDegrees
237+
if (!bearingAccuracy.isNaN()) {
238+
putDouble("bearingAccuracyDegrees", bearingAccuracy.toDouble())
239+
}
240+
}
241+
242+
// Always available fields
243+
putDouble("elapsedRealtimeNanos", location.elapsedRealtimeNanos.toDouble())
244+
location.provider?.let { putString("provider", it) }
245+
246+
// API 18+ field
247+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
248+
putBoolean("isFromMockProvider", location.isFromMockProvider)
249+
}
250+
}
251+
return map
252+
}
163253

164254
private fun createNotificationChannel() {
165255
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

0 commit comments

Comments
 (0)