> ## Documentation Index
> Fetch the complete documentation index at: https://radarlabs-update-tracking-options.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a delivery tracking app

In this tutorial, we will build a delivery tracking iOS application which uses [Trips](/geofencing/trips) to monitor deliveries with live location tracking, progress notifications, and ETAs. The full source code for the project is ready to clone and run in the section below. This tutorial will walk step-by-step through setting up and using Radar's location building blocks to rebuild this sample app, which allows the user to dispatch upcoming deliveries and monitor their progress from start to completion.

## Source code

[GitHub Repo](https://github.com/radarlabs/deliverytracker)

## Languages used

* Swift

## Features used

* [Trip tracking](/geofencing/trips)
* [Geofences](/geofencing/geofences)

## Steps

<Steps>
  <Step title="Set up your Radar account">
    You will need a Radar account to get started with the location building blocks used in this application. [Log in](https://radar.com/login) to your account, or [Sign up](https://radar.com/signup) for a free account if you don't have one yet.

    Find your API keys on the [Get Started](https://dashboard.radar.com) page. We will be using your Test publishable (client) key in the iOS app.

    [**Get API keys**](https://radar.com/signup)

    Finally, let's create your first geofences if you haven't done so already. We will use them as trip destinations during app development and testing. To create a geofence via the [dashboard](https://dashboard.radar.com), go to the [Geofences page](https://dashboard.radar.com/geofencing/geofences) and click the *New* button. Search for an address or a place, then enter a `description`, `tag`, `external ID`, and optional `metadata`.
  </Step>

  <Step title="Install the Radar iOS SDK">
    Create an Xcode project with a SwiftUI interface, named `DeliveryTracker`.

    The recommended method of installing the iOS SDK is with Cocoapods. See the [iOS SDK](/sdk/ios) docs for alternatives.

    Install [CocoaPods](https://cocoapods.org/). If you don't have an existing `Podfile`, run `pod init` in your project directory. Add the following to your `Podfile`:

    ```swift theme={null}
    pod 'RadarSDK', '~> 3.5.0'
    ```

    Then, run `pod install`. You may also need to run `pod repo update`.

    <Info>
      After installing, close your project, and open the .xcodeworkspace instead. in Xcode instead of the `.xcproject` file.
    </Info>
  </Step>

  <Step title="Initialize the SDK">
    Create a new Swift file named `LocationManager.swift`, where we define a `LocationManager` class to handle location-related logic, and create a shared instance to be used across the app. Initialize the SDK in this class with your publishable [API key](https://dashboard.radar.com), and set this class to be the `CLLocationManager` delegate.

    ```swift theme={null}
    import Foundation
    import RadarSDK

    class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
        let locationManager = CLLocationManager()
        static let shared = LocationManager()
        
        override init() {

            super.init()
            Radar.initialize(publishableKey: "prj_test_pk_...")

            self.locationManager.delegate = self
        }
    }
    ```

    Finally, add an @ObservedObject property to the `DeliveryTrackerApp` class in `DeliveryTrackerApp.swift` as shown below.

    ```
    @ObservedObject var locationManager = LocationManager.shared
    ```
  </Step>

  <Step title="Request location permissions">
    Before requesting permissions, you must add location usage strings to the custom iOS Target Properties found in the Info tab of the app settings. To request foreground permissions in the app, add a new property with the key `NSLocationWhenInUseUsageDescription` (*Privacy - Location When In Use Usage Description*). The value is displayed in the location permission prompts. Enter a message such as `Your location will be used to share ETA and arrival notifications to customers`.

    <img src="https://mintcdn.com/radarlabs-update-tracking-options/jB_icMaBGrY_ccLn/images/request-locations-step-4.gif?s=56a7aa61a1cc44242ac1cc185b579bcd" alt="Request Locations Step 4 Gi" width="2392" height="1600" data-path="images/request-locations-step-4.gif" />

    Then, request these permissions in the app by adding the following into the `LocationManager` class:

    ```swift theme={null}
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            self.requestLocationPermissions()
    }

    func requestLocationPermissions() {
        let status = CLLocationManager.authorizationStatus()

        if status == .notDetermined {
            self.locationManager.requestWhenInUseAuthorization()
        }

        if status == .authorizedAlways || status == .authorizedWhenInUse {
            print("Location permissions granted")
        }
    }
    ```
  </Step>

  <Step title="Checkpoint: Launch app and grant location permissions!" icon="check" iconType="regular">
    At this point, you should be able to launch the app and see a prompt asking for location permissions. Accept these permissions!

    If you are following the step-by-step app implementation in this tutorial, let's set up the sample app skeleton at this point. Copy these SwiftUI view files from the [source code](https://github.com/radarlabs/deliverytracker) or this [zip file](https://assets.ctfassets.net/8lppgnrkmboj/2K0sMrIXqlLpMaf5fEZoTL/883324a82709279dbb966180344135f8/tutorial-files.zip) into your project: `JobListView.swift`, `DispatchJobView.swift`, `AcceptedJobDetailView.swift`, `JobCard.swift`, `JobDetailView.swift`. Copy over `Job.swift` as well, which defines the Job struct and contains sample jobs. Then in `DeliveryTrackerApp.swift`, set `JobListView` to be the scene in the App body:

    ```swift theme={null}
    var body: some Scene {
          WindowGroup {
              JobListView()
          }
    }
    ```

    In the next few steps, we will set up trip tracking capabilities to power these skeleton components.
  </Step>

  <Step title="Add trip start logic" stepNumber={5}>
    When a delivery starts, we create a trip to the destination geofence and start tracking the live location for the user. From the source code, `DispatchJobDetailView.swift` contains the button to start a trip for a given job. We will now define its action `startOrStopTrip` in the `LocationManager` class.

    The button action contains trip start logic (and trip cancel logic for in progress trips) as shown below. We use a uuid for the trip `externalId`, although existing delivery ids should be used here when applicable. Set your destination geofence from [Step 1](#step-1).

    ```swift theme={null}
    @Published var onTrip = false
    var activeTrip = ""

    func startOrStopTrip(job: Job) {
        if !onTrip {
            let uuid = UUID().uuidString
            let tripOptions = RadarTripOptions(
                externalId: String(uuid),
                // TODO: Fill in geofence from Step 1
                destinationGeofenceTag:"delivery_location",
                destinationGeofenceExternalId: "123"
            )
            tripOptions.mode = .car
            tripOptions.metadata = [
                "Pickup Title": job.title,
                "Vehicle": "Green Ford pickup truck"
            ]

            Radar.startTrip(options: tripOptions)
            print("Trip started", uuid)
            
            // TODO: Replace with your test origin and destination locations
            Radar.mockTracking(
              origin: CLLocation(latitude: 37.769722, longitude: -122.476944),
              destination: CLLocation(latitude: 37.7897442, longitude: -122.3972337),
              mode: .car,
              steps: 10,
              interval: 2) { (status, location, events, user) in
                  print("mock track", status, location)
            }
            
            activeTrip = uuid
            onTrip = true

        } else {
            Radar.cancelTrip()
            Radar.stopTracking()
            print("Trip cancelled", activeTrip)
            onTrip = false
        }
    ```

    Notice that we use `Radar.mockTracking()` instead of [track](https://docs.radar.com/api#track) for now, which helps simulate a sequence of location updates from an origin to a destination quickly for testing. In this case, we simulate a sequence of 10 location updates, each 2 seconds apart, by car from the `origin` to the `destination`.
  </Step>

  <Step title="Add trip completion logic" stepNumber={6}>
    We will use arrival at the trip destination as a signal that the delivery is complete. To do this, we need to listen for the corresponding event, then complete the trip and stop tracking when it is detected. Update the `LocationManager` to be our `RadarDelegate`:

    ```swift theme={null}
    class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate, RadarDelegate
    ```

    Then, add a `didReceiveEvents` method with trip completion logic when it is called with the `userArrivedAtTripDestination` event type, and add in the other RadarDelegate methods:

    ```swift theme={null}
    // RadarDelegate methods
    func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) {
        for event in events {            
            if event.type == RadarEventType.userArrivedAtTripDestination {
                Radar.completeTrip()
                Radar.stopTracking()
                print("Trip completed", activeTrip)
            }
        }
    }

    func didUpdateLocation(_ location: CLLocation, user: RadarUser) {
        return
    }
        
    func didUpdateClientLocation(_ location: CLLocation, stopped: Bool, source: RadarLocationSource) {
        return
    }

    func didFail(status: RadarStatus) {
        return
    }

    func didLog(message: String) {
        return
    }
    ```
  </Step>

  <Step title="Checkpoint: Create a trip!" icon="check">
    With the steps we've covered so far, you are ready to create your first trip in the app! Run the app, and click the start button in the DispatchJobView. Now head to the [Trips](https://dashboard.radar.com/geofencing/trips) page where you will find all current and past trips. Ensure that a new trip appears in the started state at the top of your dashboard. With mock tracking automatically making progress on the trip in the background, the trip status should change to completed within the next minute.

    <img src="https://mintcdn.com/radarlabs-update-tracking-options/raXxpkaZHCzKUoPc/images/trips-dashboard.png?fit=max&auto=format&n=raXxpkaZHCzKUoPc&q=85&s=004c4c90428229ccc34cad052da9c181" alt="Trips Dashboard Pn" width="3016" height="1729" data-path="images/trips-dashboard.png" />

    In the final few steps, we will surface the progress of the trip in app by sending notifications on status updates and adding a live map view with an ETA.
  </Step>

  <Step title="Send trip update notifications" iconType="regular" stepNumber={7}>
    Next, we will set up notifications for key trip updates. First, in a new file titled `NotificationManager.swift`, define a `LocalNotificationManager` as shown below:

    ```swift theme={null}
    import Foundation
    import RadarSDK
    import MapKit

    class LocalNotificationManager: NSObject, ObservableObject, UNUserNotificationCenterDelegate {
        
        var notifications = [Notification]()
        
        override init() {
            super.init()
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                if granted == true && error == nil {
                    print("Notifications permitted")
                } else {
                    print("Notifications not permitted")
                }
            }
            UNUserNotificationCenter.current().delegate = self
        }
        
        func sendNotification(title: String, subtitle: String?, body: String, launchIn: Double) {
            let content = UNMutableNotificationContent()
            content.title = title
            if let subtitle = subtitle {
                content.subtitle = subtitle
            }
            content.body = body
        
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: launchIn, repeats: false)
            let request = UNNotificationRequest(identifier: "demoNotification", content: content, trigger: trigger)
        
            UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
        }
        
        func userNotificationCenter(
          _ center: UNUserNotificationCenter,
          willPresent notification: UNNotification,
          withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void
        ) {
            completionHandler(.banner)
        }
    }
    ```

    Then initialize a `LocalNotificationManager` in the `LocationManager` class.

    ```swift theme={null}
    var notificationManager = LocalNotificationManager()
    ```

    Finally, we need to update the listener logic from Step 6 in `LocationManager` to the following in order to send notifications:

    ```swift theme={null}
    func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) {
        for event in events {
            if event.type == RadarEventType.userStartedTrip {
                self.notificationManager.sendNotification(title: "Your mover is on the way!", subtitle: nil, body: "Your mover is headed toward the pickup location. We will notify you when they are close!", launchIn: 0.1)
            }
            if event.type == RadarEventType.userApproachingTripDestination {
                var eta = 5 // Temporarily placeholder, we will update this in Step 8
                self.notificationManager.sendNotification(title: "Your mover is approaching!", subtitle: nil, body: "Your mover is " + String(eta) + " minutes away. Get ready to meet them at the pickup location.", launchIn: 0.1)
            }
            
            if event.type == RadarEventType.userArrivedAtTripDestination {
                self.notificationManager.sendNotification(title: "Your mover is here!", subtitle: nil, body: "Meet them and get going with your pick up!", launchIn: 0.1)
                
                Radar.completeTrip()
                Radar.stopTracking()
            }
        }
    }
    ```

    You should now see notifications as a trip progresses!

    <img src="https://mintcdn.com/radarlabs-update-tracking-options/raXxpkaZHCzKUoPc/images/trip-notification.png?fit=max&auto=format&n=raXxpkaZHCzKUoPc&q=85&s=7371372ce0af4c80d9616bbba808fac3" alt="Trip Notification Pn" width="450" height="376" data-path="images/trip-notification.png" />
  </Step>

  <Step title="Display live map location and ETA" stepNumber={8}>
    Lastly, we will create a map view with live location tracking for the ongoing trip, complete with a live ETA.

    Create this map view in a new view file named `TrackMapView.swift` with the following content:

    ```swift theme={null}
    import SwiftUI
    import MapKit

    struct TrackMapView: View {
        @ObservedObject var locationManager = LocationManager.shared
        var body: some View {
            NavigationView {
                VStack {
                    Map(coordinateRegion: $locationManager.region, showsUserLocation: true, annotationItems: locationManager.currentLocation == nil ? [] :
                            [Marker(location: MapMarker(coordinate: locationManager.currentLocation!.coordinate, tint: .red))])
                    {
                        marker in marker.location
                    }.ignoresSafeArea()
                    .accentColor(Color(.systemBlue))
                        
                    Text("ETA: \(locationManager.eta) minutes")
                        .font(.headline)
                        .foregroundColor(.accentColor)
                        
                }
            }
        }
    }
    ```

    This view uses the shared instance of the location manager as an observed variable to refresh the map markers, region, and ETA when there are location updates. Now, update the `LocationManager` with definitions for the published variables that will hold this information, and a create a new method named `updateCurrentLocation` to handle location updates as shown below.

    ```swift theme={null}
    @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(
        latitude: 37.7897, longitude: -122.3972
    ), span: MKCoordinateSpan(
        latitudeDelta: 0.1, longitudeDelta: 0.1
    ))
    @Published var eta = 0
    @Published var currentLocation: CLLocation?

    func updateCurrentLocation(event: RadarEvent) {
        currentLocation = event.location
        region = MKCoordinateRegion(center: event.location.coordinate, span: MKCoordinateSpan(
            latitudeDelta: 0.1, longitudeDelta: 0.1))
        if event.trip != nil {
            eta = Int(event.trip!.etaDuration)
        }
    }
    ```

    Finally, call the `updateCurrentLocation` method in `didReceiveEvents` for trip events.

    ```swift theme={null}
    func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) {
        for event in events {
            
            if event.type == RadarEventType.userStartedTrip {
                updateCurrentLocation(event: event)
                
                self.notificationManager.sendNotification(title: "Your mover is on the way!", subtitle: nil, body: "Your mover is headed toward the pickup location. We will notify you when they are close!", launchIn: 0.1)
            }
            if event.type == RadarEventType.userApproachingTripDestination {
                updateCurrentLocation(event: event)
                
                self.notificationManager.sendNotification(title: "Your mover is approaching!", subtitle: nil, body: "Your mover is " + String(eta) + " minutes away. Get ready to meet them at the pickup location.", launchIn: 0.1)
            }
            
            if event.type == RadarEventType.userArrivedAtTripDestination {
                updateCurrentLocation(event: event)
                
                self.notificationManager.sendNotification(title: "Your mover is here!", subtitle: nil, body: "Meet them and get going with your pick up!", launchIn: 0.1)
                
                Radar.completeTrip()
                Radar.stopTracking()
                print("Trip completed", activeTrip)
            }
            
            if event.type == RadarEventType.userUpdatedTrip {
                updateCurrentLocation(event: event)
            }
        }
    }
    ```

    The `currentLocation`, `region`, and `eta` variables update with events in the listener logic, and the map view refreshes as they change. You should now see the live map view and ETAs for your trips in the app!
  </Step>

  <Step title="Next steps" stepNumber={9}>
    Congratulations on finishing the tutorial! This is an illustrative example to get started with [trip tracking](/geofencing/trips). Here are some next steps and considerations before using these features in a production setting:

    1. On trip start, replace the mock tracking call with `Radar.startTracking` in the application that delivery drivers to be tracked will be using.
    2. Set trip destinations to geofences with delivery locations instead of the test geofence. Geofences can also be created programmatically via the [geofence upsert API](/api#upsert-a-geofence).
    3. Use [Braze](https://docs.radar.com/tutorials/integrations/braze) to send push instead of local notifications to other users with different devices.

    Explore resources on the [Trip Tracking page](/geofencing/trips), [API documentation](/api), and [SDK guides](/sdk/sdk) for more information.
  </Step>
</Steps>

### Support

Have questions? Contact us at [radar.com/support](https://radar.com/support).
