Build a Trip Experience
This section describes how to build a trip experience for the user, and how to receive the user's driving behavior from Zendrive SDK. You must cache the data locally, as described in the TripRepository documentation.
This section includes the following topics:
1.0. Fetch User Trips
This section describes how to use the driverTripsAPI to fetch user trip statistics.
1.1. Integrate tripList
API To Fetch User Trips
tripList
API To Fetch User TripsCreate a FetchIQLTripsJob
class as described in the following code snippet. Parse the data received and cache it locally. Refer to the driverTripsAPI endpoint for more information.
class FetchIQLTripsJob: Job {
func run() {
DDLogDebug("Start: Fetch iql trips")
let core = IQLRef.sharedInstance
guard let driverId = core.loginRepository.currentUser?.driverId else {
fatalError("User id doesn't exist")
}
core.api.getTrips(driverId: driverId, cursor: nil,
onSuccess: { data in
for tripData in data.trips! {
let trip = core.tripRepository.tripDetails(tripInfo: tripData)
core.tripRepository.insert(trip: trip)
}
ResponseMetaUtils.sharedInstance.setResponseMeta(data: data.responseMetadata)
}, onFailure: { failure in
DDLogDebug("Failure - \(failure)")
DDLogDebug("End: trip list fetch failed")
}) { error in
DDLogDebug("Error - \(error)")
DDLogDebug("End: trip fetch Error")
}
}
}
class API {
...
...
func getTrips(driverId: String,
cursor: String?,
onSuccess: ((_ trips: TripListResponse) -> Void)?,
onFailure: ((_ failureResponse: FailureResponse) -> Void)?,
onError: ((_ error: APIError) -> Void)? ) {
if let cursor = cursor {
executeGetRequest(
endpoint: .getTrips,
pathParams: ["driver_id": driverId],
queryParams: ["cursor": cursor],
onSuccess: onSuccess, onFailure: onFailure, onError: onError)
} else {
executeGetRequest(
endpoint: .getTrips,
pathParams: ["driver_id": driverId],
onSuccess: onSuccess, onFailure: onFailure, onError: onError)
}
}
...
...
}
1.2. Parse and Cache Trip Data
This section explains how to parse the trip data and cache it locally.
class TripRepository {
func tripDetails(tripInfo: TripData) -> Trip {
let events = tripInfo.drivingBehavior.eventRating
let driveCategory = TripDriveCategory.carDriver
let tripScore = TripScore.init(zendriveScore: Int32(tripInfo.drivingBehavior.score.zendriveScore))
var driveRatings = TripEventRatings.init(aggressiveAccelerationRating: .notAvailable , hardBrakeRating: .notAvailable , speedingRating: .notAvailable, hardTurnRating: .notAvailable, phoneHandlingRating: .notAvailable)
if let rapidAcceleration = TripStarRating.init(rawValue: "\(events.rapidAcceleration)") {
driveRatings.aggressiveAccelerationRating = rapidAcceleration
}
if let hardBrake = TripStarRating.init(rawValue: "\(events.hardBrake)") {
driveRatings.hardBrakeRating = hardBrake
}
if let overSpeeding = TripStarRating.init(rawValue: "\(events.overSpeeding)") {
driveRatings.speedingRating = overSpeeding
}
if let hardTurn = TripStarRating.init(rawValue: "\(events.hardTurn)") {
driveRatings.hardTurnRating = hardTurn
}
if let phoneUse = TripStarRating.init(rawValue:"\(events.phoneUse)") {
driveRatings.phoneHandlingRating = phoneUse
}
let tripStartLocation = TripLocationPointWithTimestamp.init(timestampMillis: tripInfo.tripId, location: tripInfo.startLocation)
let tripEndLocation = TripLocationPointWithTimestamp.init(timestampMillis: tripInfo.tripId, location: tripInfo.endLocation)
let waypoint = [tripStartLocation, tripEndLocation]
return Trip(driveCategory: driveCategory,
meta: nil,
createdTimeMillis: tripInfo.tripId,
updatedTimeMillis: 0,
timezone: "",
driveId: String(tripInfo.tripId),
startTimeMillis: tripInfo.tripId,
endTimeMillis: DateUtils.sharedInstance.getTimeStamp(inputDate: tripInfo.endTime),
driveType: .driving,
userMode: nil,
distanceMeters: tripInfo.distance,
averageSpeed: 0.0,
maxSpeed: 0.0,
phonePosition: .unknown,
waypoints: waypoint,
events: nil,
score: tripScore,
eventRatings: driveRatings,
warnings: nil,
tags: [],
isDriveCategoryManuallyUpdated: false,
vehicleId: nil,
isMockTrip: false)
}
}
class TripRepository {
...
...
func insert(trip: Trip) {
let trips = allTrips()
if !trips.contains(where: { $0.driveId == trip.driveId }) {
self.tripDao.insert(record: trip)
self.inserTripsMeta(trip: trip)
if !loginRepo.isMockMode {
updateProgramStatus()
}
}
}
func inserTripsMeta(trip: Trip) {
IQLRef.sharedInstance.jobManager.enqueue(job: GenerateTripMapJob(trip: trip))
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
self.updateTripMetaData(trip: trip)
}
}
//Reverse geocode start and end coordinates to fetch the trip start and end addresses
func updateTripMetaData(trip: Trip) {
let geocoder = Geocoder.sharedInstance
var tripMeta = TripMeta.init(image: trip.meta?.image, startAddress: trip.meta?.startAddress, endAddress: trip.meta?.endAddress)
if tripMeta.startAddress == nil || tripMeta.endAddress == nil {
DispatchQueue.global().async {
if let firstPoint = trip.waypoints.first, let lastPoint = trip.waypoints.last {
if let startAddressRes = try? awaitPromise(geocoder.reverseGeocode(lat: firstPoint.location.latitude, lng: firstPoint.location.longitude)) {
tripMeta.startAddress = startAddressRes.address
if let endAddressRes = try? awaitPromise(geocoder.reverseGeocode(lat: lastPoint.location.latitude, lng: lastPoint.location.longitude)) {
tripMeta.endAddress = endAddressRes.address
self.updateTripMeta(driveId: String(trip.driveId), meta: tripMeta)
}
}
}
}
}
}
...
...
}
1.3. Implement Pagination To Fetch Additional Trips
Fetching all user trips at once is not considered best practice. We therefore recommend that you fetch 30 trips via each API call and implement accurate pagination in the trip list. To do so, use the key nextCursor
which allows you to fetch the next set of trips by passing it via an API call.
extension TripsViewController {
…
…
…
func getTripsWithTimeStamp(timeStamp: String) {
guard let driverId = self.core.loginRepository.currentUser?.driverId else {
fatalError("User id doesn't exist")
}
core.api.getTrips(driverId: driverId, cursor: timeStamp,
onSuccess: { data in
ResponseMetaUtils.sharedInstance.resetCursor()
for tripData in data.trips! {
self.activityIndicator.stopAnimating()
let trip = self.core.tripRepository.tripDetails(tripInfo: tripData)
self.core.tripRepository.insert(trip: trip)
}
ResponseMetaUtils.sharedInstance.setResponseMeta(data: data.responseMetadata)
self.onTripAnalyzed()
}, onFailure: { failure in
self.activityIndicator.stopAnimating()
DDLogDebug("Failure - \(failure)")
DDLogDebug("End: trip list pagination failed")
}) { error in
self.activityIndicator.stopAnimating()
DDLogDebug("Error - \(error)")
DDLogDebug("End: trip list pagination Error")
}
…
…
…
}
}
class API {
...
...
func getTrips(driverId: String,
cursor: String?,
onSuccess: ((_ trips: TripListResponse) -> Void)?,
onFailure: ((_ failureResponse: FailureResponse) -> Void)?,
onError: ((_ error: APIError) -> Void)? ) {
if let cursor = cursor {
executeGetRequest(
endpoint: .getTrips,
pathParams: ["driver_id": driverId],
queryParams: ["cursor": cursor],
onSuccess: onSuccess, onFailure: onFailure, onError: onError)
} else {
executeGetRequest(
endpoint: .getTrips,
pathParams: ["driver_id": driverId],
onSuccess: onSuccess, onFailure: onFailure, onError: onError)
}
}
...
...
}w
2.0. Build Trip List Experience
Use the directions provided in the reference code implementation link below to build the trip list experience and the user experience (UX) for obtaining trip feedback from users.

3.0. Build Trip Detail Experience
Use the directions provided in the developer instructions below to build the user flow for obtaining trip details from IQL program users.
3.1. Fetch Data Using tripDetails
API
tripDetails
APIFetch trip details using the tripDetails
API for individual trips before navigating to the Trip Details screen. Refer to tripDetails
API for more information. Refer to the driverTrips API endpoint for more information.
extension TripsViewController:UICollectionViewDataSource {
…
…
…
func collectionView(_ collectionView: UICollectionView, cell I’ve ForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let tripIndex = indexPath.row
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! TripViewCell
let trip = self.trips[tripIndex]
cell.configure(trip: trip) {
self.onTapTrip(trip: trip)
}
}
…
…
…
func onTapTrip(trip: Trip) {
let updatedTrip = IQLRef.sharedInstance.tripRepository.findTrip(driveId: trip.driveId)
if updatedTrip!.isTripUpdated { //Check if trip data is already updated
let controller = getAppNavController()?.navigateTo(nav: .tripDetail ) as? TripDetailViewController
controller?.trip = updatedTrip
} else {
self.fetchTripDetail(trip: trip)
}
}
…
…
…
func fetchTripDetail(trip: Trip) {
self.activityIndicator.startAnimating()
DDLogDebug("Start: Fetch trip detail")
let core = IQLRef.sharedInstance
guard let driverId = core.loginRepository.currentUser?.driverId else {
fatalError("User id doesn't exist")
}
core.api.getTripDetails(driverId: driverId, tripId: trip.driveId,
onSuccess: { data in
if data.simplePath != nil {
self.core.tripRepository.updateTripInfoData(driveId: trip.driveId, data: data)
let updatedTrip = IQLRef.sharedInstance.tripRepository.findTrip(driveId: trip.driveId)
let controller = self.getAppNavController()?.navigateTo(nav: .tripDetail ) as? TripDetailViewController
controller?.trip = updatedTrip
self.activityIndicator.stopAnimating()
}
}, onFailure: { failure in
self.activityIndicator.stopAnimating()
DDLogDebug("Failure - \(failure)")
DDLogDebug("End: fetch trip detail Failed")
}) { error in
self.activityIndicator.stopAnimating()
DDLogDebug("Error - \(error)")
DDLogDebug("End: fetch trip detail Error")
}
}
…
…
…
}
class API {
...
...
func getTripDetails(driverId: String, tripId: String, onSuccess: ((_ trips: TripDetail) -> Void)?, onFailure: ((_ failureResponse: FailureResponse) -> Void)?, onError: ((_ error: APIError) -> Void)? ) {
executeGetRequest(
endpoint: .getTripDetail,
pathParams: ["driver_id": driverId, "trip_id": tripId],
onSuccess: onSuccess, onFailure: onFailure, onError: onError)
}
...
...
}
3.2. Parse and Cache Trip Details Data
Use the developer instructions below to parse and cache trip details data retrieved by the tripDetails
API.
class TripRepository {
…
…
…
func updateTripInfoData(driveId: String, data: TripDetail) {
var waypointArr: [TripLocationPointWithTimestamp] = []
var eventData: [TripEvent] = []
if let waypoint = data.simplePath {
for waypoint in waypoint {
let location = TripLocationPoint.init(latitude: waypoint.latitude, longitude: waypoint.longitude)
let wayPoint = TripLocationPointWithTimestamp.init(timestampMillis: Int64(waypoint.time_millis), location: location)
waypointArr.append(wayPoint)
}
if let tripInfo = data.tripInfo {
let usermode = TripUserMode.init(rawValue: tripInfo.userMode.lowercased())
let vehicleType = TripDriveCategory.init(rawValue: tripInfo.vehicleType!.lowercased())
let driveType = TripDriveType.init(rawValue: tripInfo.driveType.lowercased())
tripDao.updateTripDetails(driveId: driveId, waypoints: waypointArr, userMode: usermode!, driveCategory: vehicleType!, driveType: driveType!)
}
}
if let eventArray = data.events {
for event in eventArray {
let eventType = TripEventType.init(rawValue: event.eventTypeName.lowercased())!
let startTimeStamp = DateUtils.sharedInstance.getTimeStamp(inputDate: event.startTime)
let endTimeStamp = DateUtils.sharedInstance.getTimeStamp(inputDate: event.endTime)
let startLocation = TripLocationPoint.init(latitude: event.latitudeStart, longitude: event.longitudeStart)
let endLocation = TripLocationPoint.init(latitude: event.latitudeEnd, longitude: event.longitudeEnd)
let speedingInfo = TripSpeedingInfo.init(speedLimit: event.postedSpeedLimitKmph, maxSpeed: event.maxDriverSpeedKmph, avgSpeed: event.averageDriverSpeedKmph)
let tripEvent = TripEvent.init(eventType: eventType, startTimestampMillis: startTimeStamp, endTimestampMillis: endTimeStamp, startLocation: startLocation, endLocation: endLocation, severity: nil, turnDirection: nil, speedingInfo: speedingInfo)
eventData.append(tripEvent)
}
if !eventData.isEmpty {
tripDao.updateTripEvents(driveId: driveId, events: eventData)
}
}
}
…
…
…
}
3.3. Build a Trip Detail Page
Use the code provided in the reference implementation link below to build a trip detail page.
4.0. Implement Trip Deletion
Use this section to enable users to delete trip and trip information.
4.1. Enable Users To Delete Invalid and Non-driving Trips
Enable users to delete a trip in case the trip turns out to be invalid or a non-driving trip, using the instructions provided in this section. Refer to the tripFeedback EndpointAPI endpoint for more information.

4.2. Allow Trip Deletion To Trips Less Than 7 Days Old
Use the following code snippet to enable users to delete trips that are less than 7 days old
class TripViewCell: UICollectionViewCell {
…
…
…
func configure(trip: Trip,
onTripTap: @escaping TapAction,
onTapDelete: @escaping TapAction,
onTapRetry: @escaping TapAction,
onTapCancel: @escaping TapAction) {
…
…
…
if DateUtils.sharedInstance.isTripOlderThanSevenDays(tripDate: startTimeDate) {
tripFeedbackView.isHidden = true
tripFeedbackView.height(10)
} else {
tripFeedbackView.height(48)
tripFeedbackView.isHidden = false
}
…
…
…
}
…
…
…
}
class DateUtils {
…
…
…
func isTripOlderThanSevenDays(tripDate: Date) -> Bool {
let currentDate = Date()
let timeDifference = Int(currentDate.timeIntervalSince1970 - tripDate.timeIntervalSince1970)
let hours = timeDifference / 3600
let days = hours/24
if days <= 7 {
return true
}
return false
}
…
…
…
}
4.3. Enable Trip Deletion Only for Non-qualified Users
Once a user qualifies for the IQL program, he or she should not be allowed to delete trips, as all trip data is required while preparing an offer. Use the following code snippet to disable trip deletion for qualified users:
func configure(trip: Trip,
onTripTap: @escaping TapAction,
onTapDelete: @escaping TapAction,
onTapRetry: @escaping TapAction,
onTapCancel: @escaping TapAction) {
…
…
…
if loginRepo.state == .Qualified {
tripFeedbackView.isHidden = true
tripFeedbackView.height(10)
} else {
tripFeedbackView.height(48)
tripFeedbackView.isHidden = false
}
…
…
…
}
…
…
…
}
5.0. IQL Reference Application UX Design
Here is the overall design implementation for the IQL Reference Application:
6.0. Publisher Integration Testing Checklist
Follow the testing checklist given below while testing the Build a Trip Experience process:
Trips Page: Ensure that on the Trips page, all the user's recent trips are displayed accurately.
Pagination: If the Trips page contains pagination, add additional trips accurately, as needed from time to time.
Trip Feedback: Ensure that the user can delete the trips where the user was not the driver. When a trip is deleted, it should be removed immediately from the Trips page. If trip deletion does not occur owing to internet issues, provide a retry option to the user.
Trip Deletion Logic: Ensure that the user can only delete trips that are less than 7 days old. Additionally, ensure that the delete option is unavailable once the user receives an offer.
Trip Detail: When the user taps on a particular trip, enable the Trips page to accurately display all the trip details.