<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ion Samoil</title>
    <description>The latest articles on DEV Community by Ion Samoil (@ion_samoil_363a90a2fe0e54).</description>
    <link>https://dev.to/ion_samoil_363a90a2fe0e54</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4102536%2F0cca61db-5e7f-41e2-ac94-23c6f3e4663a.jpg</url>
      <title>DEV Community: Ion Samoil</title>
      <link>https://dev.to/ion_samoil_363a90a2fe0e54</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ion_samoil_363a90a2fe0e54"/>
    <language>en</language>
    <item>
      <title>How Hybrid Frameworks Handle Background Location: From Flutter/Ionic to Native iOS and Android</title>
      <dc:creator>Ion Samoil</dc:creator>
      <pubDate>Mon, 31 Aug 2026 10:52:27 +0000</pubDate>
      <link>https://dev.to/ion_samoil_363a90a2fe0e54/how-hybrid-frameworks-handle-background-location-from-flutterionic-to-native-ios-and-android-kbc</link>
      <guid>https://dev.to/ion_samoil_363a90a2fe0e54/how-hybrid-frameworks-handle-background-location-from-flutterionic-to-native-ios-and-android-kbc</guid>
      <description>&lt;p&gt;Background Location in Hybrid Apps: Going Native on iOS&lt;/p&gt;

&lt;p&gt;Hybrid frameworks such as Ionic, Flutter, and React Native cover most mobile application needs without requiring much native code.&lt;/p&gt;

&lt;p&gt;But background location is one of those features where going native can make sense.&lt;/p&gt;

&lt;p&gt;In my case, I needed an Ionic + Angular + Capacitor application to continue tracking location when:&lt;/p&gt;

&lt;p&gt;the app is in the background;&lt;br&gt;
the screen is locked;&lt;br&gt;
the app is no longer actively running in the foreground.&lt;/p&gt;

&lt;p&gt;Instead of trying to keep the Angular/WebView layer alive, I moved location tracking to native iOS code.&lt;/p&gt;

&lt;p&gt;The resulting architecture is simple:&lt;/p&gt;

&lt;p&gt;Ionic / Angular&lt;br&gt;
      │&lt;br&gt;
      │ configuration&lt;br&gt;
      ▼&lt;br&gt;
Capacitor Storage&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Native Swift&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Core Location&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Backend API&lt;/p&gt;

&lt;p&gt;Let's look at the implementation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the configuration from Ionic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Angular application stores the information required by the native layer:&lt;/p&gt;

&lt;p&gt;async setUserData(userData: any | null): Promise {&lt;br&gt;
  if (!userData) {&lt;br&gt;
    await this.clear(this._locationDataKey);&lt;br&gt;
    return this.clear(this._userKey);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const id = await Device.getId();&lt;/p&gt;

&lt;p&gt;await this.store(&lt;br&gt;
    this._locationDataKey,&lt;br&gt;
    JSON.stringify({&lt;br&gt;
      apiUrl: environment.apiUrl,&lt;br&gt;
      userId: userData.userId,&lt;br&gt;
      on_duty: true,&lt;br&gt;
      identifier: id.identifier,&lt;br&gt;
      platform: Capacitor.getPlatform(),&lt;br&gt;
    })&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;await this.store(this._userKey, JSON.stringify(userData));&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The important idea here is that Angular doesn't process the background locations.&lt;/p&gt;

&lt;p&gt;It only shares configuration with the native layer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable background location on iOS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add the location permissions to Info.plist:&lt;/p&gt;

&lt;p&gt;NSLocationWhenInUseUsageDescription&lt;br&gt;
&lt;br&gt;
    We need your location while using the app.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;NSLocationAlwaysAndWhenInUseUsageDescription&lt;br&gt;
&lt;br&gt;
    We need your location while the app is in the background.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;UIBackgroundModes&lt;br&gt;
&lt;br&gt;
    location&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You should of course replace these descriptions with the actual reason your application needs the user's location.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the native LocationManager&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we can move location tracking into Swift.&lt;/p&gt;

&lt;p&gt;import CoreLocation&lt;/p&gt;

&lt;p&gt;class LocationManager: NSObject, CLLocationManagerDelegate {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private let locationManager = CLLocationManager()

func startTrackingLocation() {
    let locationData = getLocationData() ?? [:]
    let onDuty = locationData["on_duty"] as? Bool ?? false

    guard onDuty else { return }

    locationManager.delegate = self

    locationManager.requestAlwaysAuthorization()
    locationManager.pausesLocationUpdatesAutomatically = false
    locationManager.allowsBackgroundLocationUpdates = true

    locationManager.startMonitoringSignificantLocationChanges()
    locationManager.startUpdatingLocation()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;There are two location mechanisms here:&lt;/p&gt;

&lt;p&gt;locationManager.startUpdatingLocation()&lt;/p&gt;

&lt;p&gt;provides standard location updates, including while the application is backgrounded when correctly configured.&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;locationManager.startMonitoringSignificantLocationChanges()&lt;/p&gt;

&lt;p&gt;asks iOS to monitor significant changes in location.&lt;/p&gt;

&lt;p&gt;This second mechanism is particularly interesting because iOS can relaunch an authorized application in the background when a significant-location event occurs.&lt;/p&gt;

&lt;p&gt;It doesn't mean that arbitrary application code runs continuously after termination. The location lifecycle is still controlled by iOS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the Ionic configuration from Swift&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The native implementation can access the configuration previously stored by Capacitor:&lt;/p&gt;

&lt;p&gt;func getLocationData() -&amp;gt; [String: Any]? {&lt;br&gt;
    let defaults = UserDefaults.standard&lt;br&gt;
    let key = "CapacitorStorage.app|location-data"&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;guard let jsonString = defaults.string(forKey: key),
      let data = jsonString.data(using: .utf8) else {
    return nil
}

do {
    return try JSONSerialization.jsonObject(
        with: data
    ) as? [String: Any]
} catch {
    print("Unable to decode location configuration: \(error)")
    return nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;This gives native Swift access to values such as:&lt;/p&gt;

&lt;p&gt;userId&lt;br&gt;
apiUrl&lt;br&gt;
on_duty&lt;br&gt;
identifier&lt;br&gt;
platform&lt;/p&gt;

&lt;p&gt;without depending on Angular being active.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive location updates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implement CLLocationManagerDelegate:&lt;/p&gt;

&lt;p&gt;func locationManager(&lt;br&gt;
    _ manager: CLLocationManager,&lt;br&gt;
    didUpdateLocations locations: [CLLocation]&lt;br&gt;
) {&lt;br&gt;
    guard let location = locations.last else {&lt;br&gt;
        return&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude

let locationData = getLocationData() ?? [:]

guard let userId = locationData["userId"] as? Int,
      let apiUrl = locationData["apiUrl"] as? String else {
    return
}

postLocation(
    lat: latitude,
    long: longitude,
    apiUrl: apiUrl,
    userId: userId
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;At this point the WebView isn't involved:&lt;/p&gt;

&lt;p&gt;Core Location&lt;br&gt;
      ↓&lt;br&gt;
LocationManager.swift&lt;br&gt;
      ↓&lt;br&gt;
Native HTTP request&lt;br&gt;
      ↓&lt;br&gt;
Backend&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the location directly from Swift&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;func postLocation(&lt;br&gt;
    lat: Double,&lt;br&gt;
    long: Double,&lt;br&gt;
    apiUrl: String,&lt;br&gt;
    userId: Int&lt;br&gt;
) {&lt;br&gt;
    guard let url = URL(string: apiUrl + "/locations") else {&lt;br&gt;
        return&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var request = URLRequest(url: url)
request.httpMethod = "POST"

request.addValue(
    "application/json",
    forHTTPHeaderField: "Content-Type"
)

let body: [String: Any] = [
    "latitude": lat,
    "longitude": long,
    "userId": userId
]

request.httpBody = try? JSONSerialization.data(
    withJSONObject: body
)

URLSession.shared
    .dataTask(with: request)
    .resume()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;For a real application, authentication, offline retries, secure storage, and error handling should obviously be added.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start tracking from AppDelegate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, initialize the native manager from the iOS application:&lt;/p&gt;

&lt;p&gt;@UIApplicationMain&lt;br&gt;
class AppDelegate: UIResponder, UIApplicationDelegate {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var window: UIWindow?

let locationManager = LocationManager()

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions:
        [UIApplication.LaunchOptionsKey: Any]?
) -&amp;gt; Bool {

    locationManager.startTrackingLocation()

    return true
}

func applicationDidEnterBackground(
    _ application: UIApplication
) {
    locationManager.startTrackingLocation()
}

func applicationWillEnterForeground(
    _ application: UIApplication
) {
    locationManager.startTrackingLocation()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The important part isn't repeatedly calling the method from every lifecycle callback.&lt;/p&gt;

&lt;p&gt;The important part is that Core Location is configured at the native level and doesn't depend on Angular's execution lifecycle.&lt;/p&gt;

&lt;p&gt;What does "closed" actually mean?&lt;/p&gt;

&lt;p&gt;One final warning: don't treat "background", "locked", "terminated by iOS", and "force-closed by the user" as the same state.&lt;/p&gt;

&lt;p&gt;Background location is controlled by iOS, and different Core Location services have different lifecycle behavior.&lt;/p&gt;

&lt;p&gt;In particular, startMonitoringSignificantLocationChanges() can allow iOS to relaunch the application for a qualifying location event, but this isn't the same as keeping your application continuously alive.&lt;/p&gt;

&lt;p&gt;That's it&lt;/p&gt;

&lt;p&gt;We still have an Ionic application:&lt;/p&gt;

&lt;p&gt;Ionic / Angular&lt;br&gt;
      ↓&lt;br&gt;
Capacitor Storage&lt;br&gt;
      ↓&lt;br&gt;
Swift&lt;br&gt;
      ↓&lt;br&gt;
CLLocationManager&lt;br&gt;
      ↓&lt;br&gt;
URLSession&lt;br&gt;
      ↓&lt;br&gt;
Backend&lt;/p&gt;

&lt;p&gt;We simply moved the responsibility that needs native lifecycle support into the native layer.&lt;/p&gt;

&lt;p&gt;And this pattern isn't limited to Ionic.&lt;/p&gt;

&lt;p&gt;The same principle can be used with Flutter, React Native, or another cross-platform framework: keep the shared application shared, and go native for the small pieces that genuinely need native platform capabilities.&lt;br&gt;
Background Location in Hybrid Apps: Going Native on Android&lt;/p&gt;

&lt;p&gt;In [Part 1], we implemented background location tracking on iOS by moving the responsibility from Ionic/Angular into native Swift and Core Location.&lt;/p&gt;

&lt;p&gt;Now let's do the same thing on Android.&lt;/p&gt;

&lt;p&gt;The goal stays the same:&lt;/p&gt;

&lt;p&gt;Ionic/Angular manages the application and configuration&lt;br&gt;
native Android code manages background location&lt;br&gt;
location updates don't depend on the Angular WebView being active&lt;/p&gt;

&lt;p&gt;On Android, however, the implementation is different.&lt;/p&gt;

&lt;p&gt;We'll use a Foreground Service + Fused Location Provider:&lt;/p&gt;

&lt;p&gt;Ionic / Angular&lt;br&gt;
      │&lt;br&gt;
      │ configuration&lt;br&gt;
      ▼&lt;br&gt;
Capacitor Storage&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Native Android&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Foreground Service&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Fused Location Provider&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Backend API&lt;/p&gt;

&lt;p&gt;Let's implement it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the Android permissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, add the required permissions to AndroidManifest.xml:&lt;/p&gt;

&lt;p&gt;
    android:name="android.permission.ACCESS_FINE_LOCATION" /&amp;gt;&lt;/p&gt;

&lt;p&gt;
    android:name="android.permission.ACCESS_COARSE_LOCATION" /&amp;gt;&lt;/p&gt;

&lt;p&gt;
    android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /&amp;gt;&lt;/p&gt;

&lt;p&gt;
    android:name="android.permission.FOREGROUND_SERVICE" /&amp;gt;&lt;/p&gt;

&lt;p&gt;
    android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /&amp;gt;&lt;/p&gt;

&lt;p&gt;ACCESS_BACKGROUND_LOCATION is required for background location access on Android versions where that permission model applies.&lt;/p&gt;

&lt;p&gt;Modern Android versions also require the appropriate foreground-service permission/type for location services.&lt;/p&gt;

&lt;p&gt;Remember that declaring permissions in the manifest is only one part of the process. Location permissions still need to be requested from the user at runtime.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register the native LocationService&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inside :&lt;/p&gt;

&lt;p&gt;
    android:name=".LocationService"&lt;br&gt;
    android:enabled="true"&lt;br&gt;
    android:exported="false"&lt;br&gt;
    android:foregroundServiceType="location"&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;The important part is:&lt;/p&gt;

&lt;p&gt;android:foregroundServiceType="location"&lt;/p&gt;

&lt;p&gt;We're telling Android that this foreground service performs location-related work.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Fused Location Provider&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add Google Play Services Location to build.gradle:&lt;/p&gt;

&lt;p&gt;implementation "com.google.android.gms:play-services-location:$playServicesLocation"&lt;/p&gt;

&lt;p&gt;I'm also using Retrofit to communicate with the backend:&lt;/p&gt;

&lt;p&gt;implementation 'com.squareup.retrofit2:retrofit:2.9.0'&lt;br&gt;
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'&lt;/p&gt;

&lt;p&gt;The core dependency for location tracking is Google Play Services Location.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the native service from MainActivity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Ionic application stores its configuration in Capacitor storage.&lt;/p&gt;

&lt;p&gt;Native Android can read that configuration and decide whether location tracking should run.&lt;/p&gt;

&lt;p&gt;In MainActivity:&lt;/p&gt;

&lt;p&gt;private void manageBackgroundLocationService() {&lt;br&gt;
    boolean onDuty =&lt;br&gt;
        StorageHelper.isOnDuty(getApplicationContext());&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent serviceIntent =
    new Intent(this, LocationService.class);

if (onDuty &amp;amp;&amp;amp; hasLocationPermissions()) {
    startForegroundService(serviceIntent);
} else {
    stopService(serviceIntent);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;This method is called when the application starts:&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
protected void onCreate(Bundle savedInstanceState) {&lt;br&gt;
    super.onCreate(savedInstanceState);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;manageBackgroundLocationService();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;It can also react when the configuration stored by Ionic changes. In my implementation, MainActivity listens to the underlying SharedPreferences used by Capacitor.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;Angular&lt;br&gt;
   │&lt;br&gt;
   │ on_duty = true&lt;br&gt;
   ▼&lt;br&gt;
Capacitor Storage&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
SharedPreferences&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
MainActivity&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
startForegroundService()&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Foreground Service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now comes the important part.&lt;/p&gt;

&lt;p&gt;Create LocationService.java:&lt;/p&gt;

&lt;p&gt;public class LocationService extends Service {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static final String CHANNEL_ID = "LocationChannel";

private FusedLocationProviderClient fusedLocationClient;

@Override
public void onCreate() {
    super.onCreate();

    fusedLocationClient =
        LocationServices.getFusedLocationProviderClient(this);

    startForegroundService();

    if (StorageHelper.isOnDuty(getApplicationContext())) {
        requestLocationUpdates();
    } else {
        stopSelf();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The foreground service now owns the location lifecycle instead of Angular. Your actual implementation follows this pattern and also listens for on_duty changes so the service can stop itself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Foreground services require a notification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A foreground service must be visible to the user.&lt;/p&gt;

&lt;p&gt;Create a notification channel and notification:&lt;/p&gt;

&lt;p&gt;private void startForegroundService() {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ensureNotificationChannel();

Notification notification =
    new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("Location Tracking")
        .setContentText(
            "Tracking location in the background"
        )
        .setSmallIcon(
            android.R.drawable.ic_menu_mylocation
        )
        .setPriority(NotificationCompat.PRIORITY_LOW)
        .build();

startForeground(1, notification);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Your service does exactly this before beginning its long-running work.&lt;/p&gt;

&lt;p&gt;This is an important difference from iOS.&lt;/p&gt;

&lt;p&gt;Android makes long-running foreground work explicitly visible:&lt;/p&gt;

&lt;p&gt;LocationService&lt;br&gt;
      │&lt;br&gt;
      ├── persistent notification&lt;br&gt;
      │&lt;br&gt;
      └── location updates&lt;/p&gt;

&lt;p&gt;Trying to hide that notification isn't the solution—the foreground-service model intentionally makes this activity visible to the user.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request native location updates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now configure FusedLocationProviderClient:&lt;/p&gt;

&lt;p&gt;private static final long INTERVAL_MS =&lt;br&gt;
    60 * 60 * 1000; // 1 hour&lt;/p&gt;

&lt;p&gt;private void requestLocationUpdates() {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LocationRequest request =
    LocationRequest.create();

request.setInterval(INTERVAL_MS);
request.setFastestInterval(INTERVAL_MS);
request.setPriority(
    LocationRequest.PRIORITY_HIGH_ACCURACY
);

fusedLocationClient.requestLocationUpdates(
    request,
    locationCallback,
    null
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The real implementation also verifies both fine and background location permissions before requesting updates.&lt;/p&gt;

&lt;p&gt;Then receive the locations:&lt;/p&gt;

&lt;p&gt;private final LocationCallback locationCallback =&lt;br&gt;
    new LocationCallback() {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @Override
    public void onLocationResult(
        LocationResult locationResult
    ) {
        if (locationResult == null) {
            return;
        }

        for (Location location :
                locationResult.getLocations()) {

            saveLocation(location);
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Again, Angular isn't involved:&lt;/p&gt;

&lt;p&gt;Android Location Services&lt;br&gt;
        ↓&lt;br&gt;
FusedLocationProviderClient&lt;br&gt;
        ↓&lt;br&gt;
LocationService&lt;br&gt;
        ↓&lt;br&gt;
Backend&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the Ionic configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Just like on iOS, the native layer can access data persisted by the Ionic application.&lt;/p&gt;

&lt;p&gt;A small helper makes that easier:&lt;/p&gt;

&lt;p&gt;public static JSONObject getLocationData(&lt;br&gt;
    Context context&lt;br&gt;
) {&lt;br&gt;
    SharedPreferences preferences =&lt;br&gt;
        context.getSharedPreferences(&lt;br&gt;
            "CapacitorStorage",&lt;br&gt;
            Context.MODE_PRIVATE&lt;br&gt;
        );&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String data = preferences.getString(
    "app|location-data",
    null
);

if (data == null) {
    return new JSONObject();
}

try {
    return new JSONObject(data);
} catch (JSONException e) {
    return new JSONObject();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Your implementation uses the same mechanism to extract on_duty from the shared configuration.&lt;/p&gt;

&lt;p&gt;This gives the native service access to:&lt;/p&gt;

&lt;p&gt;apiUrl&lt;br&gt;
userId&lt;br&gt;
identifier&lt;br&gt;
platform&lt;br&gt;
on_duty&lt;/p&gt;

&lt;p&gt;without needing Angular to be running.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the location directly from native code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the Android implementation, I'm using Retrofit.&lt;/p&gt;

&lt;p&gt;Define the endpoint:&lt;/p&gt;

&lt;p&gt;public interface ApiService {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@POST("locations")
Call&amp;lt;Void&amp;gt; createLocation(
    @Body UserLocation location
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;This corresponds directly to the API interface used in the implementation.&lt;/p&gt;

&lt;p&gt;Then when a location arrives:&lt;/p&gt;

&lt;p&gt;private void saveLocation(&lt;br&gt;
    Location location,&lt;br&gt;
    JSONObject userData&lt;br&gt;
) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserLocation userLocation =
    new UserLocation(
        location.getLatitude(),
        location.getLongitude(),
        userData.optString("userId"),
        userData.optString("identifier"),
        userData.optString("platform"),
        location.getAccuracy(),
        location.getSpeed(),
        location.getAltitude()
    );

apiService
    .createLocation(userLocation)
    .enqueue(new Callback&amp;lt;Void&amp;gt;() {

        @Override
        public void onResponse(
            Call&amp;lt;Void&amp;gt; call,
            Response&amp;lt;Void&amp;gt; response
        ) {
            // Location sent
        }

        @Override
        public void onFailure(
            Call&amp;lt;Void&amp;gt; call,
            Throwable throwable
        ) {
            // Retry/persist according to your needs
        }
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The actual location payload includes latitude, longitude, user/device information, accuracy, speed and altitude.&lt;/p&gt;

&lt;p&gt;So, once again:&lt;/p&gt;

&lt;p&gt;Location event&lt;br&gt;
      ↓&lt;br&gt;
Java LocationService&lt;br&gt;
      ↓&lt;br&gt;
Retrofit&lt;br&gt;
      ↓&lt;br&gt;
Backend&lt;/p&gt;

&lt;p&gt;No WebView is required to process the event.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What happens when the app is removed?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where Android gets interesting.&lt;/p&gt;

&lt;p&gt;The service can return:&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
public int onStartCommand(&lt;br&gt;
    Intent intent,&lt;br&gt;
    int flags,&lt;br&gt;
    int startId&lt;br&gt;
) {&lt;br&gt;
    if (StorageHelper.isOnDuty(&lt;br&gt;
        getApplicationContext()&lt;br&gt;
    )) {&lt;br&gt;
        requestLocationUpdates();&lt;br&gt;
    } else {&lt;br&gt;
        stopSelf();&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return START_STICKY;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Your implementation uses START_STICKY and re-checks on_duty whenever Android recreates the service.&lt;/p&gt;

&lt;p&gt;START_STICKY tells Android that if the service process is killed, the system should attempt to recreate the service later.&lt;/p&gt;

&lt;p&gt;But don't interpret this as:&lt;/p&gt;

&lt;p&gt;"Android guarantees my application will always run after being closed."&lt;/p&gt;

&lt;p&gt;There are important differences between:&lt;/p&gt;

&lt;p&gt;App in background&lt;br&gt;
Screen locked&lt;br&gt;
Activity removed from Recents&lt;br&gt;
Process killed by Android&lt;br&gt;
User explicitly force-stops app&lt;/p&gt;

&lt;p&gt;These states don't have identical behavior.&lt;/p&gt;

&lt;p&gt;A foreground service makes background location significantly more reliable, but it doesn't give an application permission to bypass Android's lifecycle and user controls.&lt;/p&gt;

&lt;p&gt;The final architecture&lt;/p&gt;

&lt;p&gt;That's basically it.&lt;/p&gt;

&lt;p&gt;The complete Android architecture becomes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Ionic / Angular
               │
        Capacitor Storage
               │
               ▼
          MainActivity
               │
               ▼
        LocationService
               │
    ┌──────────┴──────────┐
    │                     │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Foreground notification   Fused Location&lt;br&gt;
                              │&lt;br&gt;
                              ▼&lt;br&gt;
                           Retrofit&lt;br&gt;
                              │&lt;br&gt;
                              ▼&lt;br&gt;
                           Backend&lt;/p&gt;

&lt;p&gt;Compare that with the iOS implementation from Part 1:&lt;/p&gt;

&lt;p&gt;iOS                         Android&lt;/p&gt;

&lt;p&gt;Angular                     Angular&lt;br&gt;
   ↓                           ↓&lt;br&gt;
Capacitor Storage           Capacitor Storage&lt;br&gt;
   ↓                           ↓&lt;br&gt;
Swift                       Java/Kotlin&lt;br&gt;
   ↓                           ↓&lt;br&gt;
Core Location               Foreground Service&lt;br&gt;
                               ↓&lt;br&gt;
                            Fused Location&lt;br&gt;
   ↓                           ↓&lt;br&gt;
Backend                     Backend&lt;/p&gt;

&lt;p&gt;The native implementations are completely different, but the architectural principle is the same:&lt;/p&gt;

&lt;p&gt;Keep the shared application in your hybrid framework, but move platform-specific background responsibilities into the native layer.&lt;/p&gt;

&lt;p&gt;And this approach isn't limited to Ionic.&lt;/p&gt;

&lt;p&gt;The same idea can be applied to Flutter, React Native, or other cross-platform frameworks when you reach a feature that needs deeper integration with the native operating system.&lt;/p&gt;

</description>
      <category>android</category>
      <category>architecture</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
