Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor for testability #282

Merged
merged 2 commits into from
Jun 26, 2017
Merged

Refactor for testability #282

merged 2 commits into from
Jun 26, 2017

Conversation

frederoni
Copy link
Contributor

@frederoni frederoni commented Jun 14, 2017

This PR adds a few location managers that can be set on the RouteController

  • NavigationLocationManager: This is the base location manager that handles permission and background mode. All other location managers inherits from this location manager.
  • DefaultLocationManager: This location manager resides in the UI part of the framework because it needs access to UIKit to determine battery state and set the desired accuracy accordingly.
  • ReplayLocationManager: Replays a list of locations.
  • SimulatedLocationManager: Replaces SimulatedRoute.

RouteController now also supports simple dead reckoning using any of the location managers. However, isDeadReckoningEnabled defaults to false.

NavigationMapView now receives location updates from the location manager associated with the RouteController through the new RouteControllerDelegate.routeController(_:didUpdateLocations:).

  • Implement ReplayLocationManager
  • Replace SimulatedRoute with SimulatedLocationManager
  • Refactor monitorStepProgress
  • Write tests using ReplayLocationManager

The gist of how to use these location managers:

SimulatedLocationManager - Simulates location updates along a route

let locationManager = SimulatedLocationManager(route: route)
let navigationViewController = NavigationViewController(for: route, locationManager: locationManager)

ReplayLocationManager - Replays a list of locations previously recorded to a file.

let locationManager = ReplayLocationManager()
locationManager.locations = Array<CLLocation>.locations(from: filePath)
let navigationViewController = NavigationViewController(for: route, locationManager: locationManager)

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good so far.

@@ -52,6 +52,18 @@ class ViewController: UIViewController, MGLMapViewDelegate, NavigationViewContro
getRoute()
}

@IBAction func didTapReplay(_ sender: Any) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An action is usually named something more active, like replay(_:).

}

if Bundle.main.backgroundModeLocationSupported {
activityType = .automotiveNavigation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the activity type always be automotiveNavigation, even when the user is getting walking directions?

Copy link
Contributor Author

@frederoni frederoni Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extended RouteOptions to convert from MBDirectionsProfileIdentifier to CLActivityType.

also derive from regular location updates from a `CLLocationManager`.

- parameter routeController: The route controller that received the new locations.
- parameter locations: The locations that were received from on of the location managers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: /on/one/

public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}

delegate?.routeController!(self, didUpdateLocations: [location])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should routeController(_:didUpdateLocations:) be optional if we require its existence here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be optional

@frederoni frederoni force-pushed the refactor-testability branch 4 times, most recently from 4d6c2ae to 88b80ec Compare June 22, 2017 15:06
@frederoni frederoni changed the title [WIP] Refactor for testability Refactor for testability Jun 22, 2017
Copy link
Contributor

@ericrwolfe ericrwolfe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frederoni this looks awesome, the ReplayLocationManager is going to be especially useful.

navigationViewController.voiceController?.volume = 0.5
navigationViewController.navigationDelegate = self

if let locationManager = locationManager {
navigationViewController.routeController.locationManager = locationManager
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if locationManager was a nullable property and you could set this to nil for the default location manager.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if Swift have something like Obj-C’s null_resettable.

Copy link
Contributor

@1ec5 1ec5 Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: CLLocationManager! with a custom getter or setter that handles nil.

@@ -143,11 +156,15 @@ class ViewController: UIViewController, MGLMapViewDelegate, NavigationViewContro
// You can get a token here: http://docs.aws.amazon.com/mobile/sdkforios/developerguide/cognito-auth-aws-identity-for-ios.html
//navigationViewController.voiceController?.identityPoolId = "<#Your AWS IdentityPoolId. Remove Argument if you do not want to use AWS Polly#>"

navigationViewController.simulatesLocationUpdates = simulatesLocationUpdates
navigationViewController.routeController.snapsUserLocationAnnotationToRoute = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make snapsUserLocationAnnotationToRoute=true by default

@@ -165,15 +166,6 @@ public class NavigationViewController: NavigationPulleyViewController, RouteMapV
public var routeController: RouteController!

/**
`simulate` provides simulated location updates along the given route.
*/
public var simulatesLocationUpdates: Bool = false {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep this bool for simplicity in the Navigation UI and have the didSet function optionally set the SimulatedLocationManager?

Copy link
Contributor Author

@frederoni frederoni Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't do the same for ReplayLocationManager, so for consistency, I'd prefer not to expose this shortcut.

}

mapView.locationManager.stopUpdatingLocation()
mapView.locationManager.stopUpdatingHeading()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should move to viewWillAppear to fix animation issues. More here: #303

@frederoni frederoni force-pushed the refactor-testability branch 3 times, most recently from 8e8463b to 9eab5b7 Compare June 22, 2017 22:39
@@ -307,9 +368,6 @@ extension RouteController: CLLocationManagerDelegate {
strongSelf.routeProgress = RouteProgress(route: route)
strongSelf.routeProgress.currentLegProgress.stepIndex = 0
strongSelf.delegate?.routeController?(strongSelf, didRerouteAlong: route)
NotificationCenter.default.post(name: RouteControllerDidReroute, object: self, userInfo: [
MBRouteControllerNotificationRouteKey: location
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to routeProgress didSet and changed RouteKey to LocationKey

@frederoni frederoni force-pushed the refactor-testability branch 6 times, most recently from f7fcaa8 to 860d3bf Compare June 25, 2017 18:42
@frederoni
Copy link
Contributor Author

frederoni commented Jun 25, 2017

CLLocationManager crashes intermittently if it's deallocated in the same runloop it was initialized in, so I had to change the API a bit. RouteController and NavigationViewController is now initialized with an optional CLLocationManager.


self.expectation(forNotification: RouteControllerAlertLevelDidChange.rawValue, object: navigation) { (notification) -> Bool in
XCTAssertEqual(notification.userInfo?.count, 2)

let routeProgress = notification.userInfo![RouteControllerAlertLevelDidChangeNotificationRouteProgressKey] as? RouteProgress
let userDistance = notification.userInfo![RouteControllerAlertLevelDidChangeNotificationDistanceToEndOfManeuverKey] as! CLLocationDistance

return routeProgress?.currentLegProgress.alertUserLevel == .low && routeProgress?.currentLegProgress.stepIndex == 2 && round(userDistance) == 1758
return routeProgress?.currentLegProgress.alertUserLevel == .low && routeProgress?.currentLegProgress.stepIndex == 2 && round(userDistance) == 1786
Copy link
Contributor Author

@frederoni frederoni Jun 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 @bsudekum Any idea how userDistance could've changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. Do you think it could be a race condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting 1786 on master (2c6ec67) 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screenshot 2017-06-26 22 22 43
That test was disabled in the scheme. 😄

@frederoni frederoni merged commit 1244ad7 into master Jun 26, 2017
@frederoni frederoni deleted the refactor-testability branch March 19, 2018 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants