Giter Site home page Giter Site logo

composable-core-location's Introduction

Composable Core Location

CI

Composable Core Location is library that bridges the Composable Architecture and Core Location.

Example

Check out the LocationManager demo to see ComposableCoreLocation in practice.

Basic Usage

To use ComposableCoreLocation in your application, you can add an action to your domain that represents all of the actions the manager can emit via the CLLocationManagerDelegate methods:

import ComposableCoreLocation

enum AppAction {
  case locationManager(LocationManager.Action)

  // Your domain's other actions:
  ...
}

The LocationManager.Action enum holds a case for each delegate method of CLLocationManagerDelegate, such as didUpdateLocations, didEnterRegion, didUpdateHeading, and more.

Next we add a LocationManager, which is a wrapper around CLLocationManager that the library provides, to the application's environment of dependencies:

struct AppEnvironment {
  var locationManager: LocationManager

  // Your domain's other dependencies:
  ...
}

Then, we simultaneously subscribe to delegate actions and request authorization from our application's reducer by returning an effect from an action to kick things off. One good choice for such an action is the onAppear of your view.

let appReducer = Reducer<AppState, AppAction, AppEnvironment> {
  state, action, environment in

  switch action {
  case .onAppear:
    return .merge(
      environment.locationManager
        .delegate()
        .map(AppAction.locationManager),

      environment.locationManager
        .requestWhenInUseAuthorization()
        .fireAndForget()
    )

  ...
  }
}

With that initial setup we will now get all of CLLocationManagerDelegate's delegate methods delivered to our reducer via actions. To handle a particular delegate action we can destructure it inside the .locationManager case we added to our AppAction. For example, once we get location authorization from the user we could request their current location:

case .locationManager(.didChangeAuthorization(.authorizedAlways)),
     .locationManager(.didChangeAuthorization(.authorizedWhenInUse)):

  return environment.locationManager
    .requestLocation()
    .fireAndForget()

If the user denies location access we can show an alert telling them that we need access to be able to do anything in the app:

case .locationManager(.didChangeAuthorization(.denied)),
     .locationManager(.didChangeAuthorization(.restricted)):

  state.alert = """
    Please give location access so that we can show you some cool stuff.
    """
  return .none

Otherwise, we'll be notified of the user's location by handling the .didUpdateLocations action:

case let .locationManager(.didUpdateLocations(locations)):
  // Do something cool with user's current location.
  ...

Once you have handled all the CLLocationManagerDelegate actions you care about, you can ignore the rest:

case .locationManager:
  return .none

And finally, when creating the Store to power your application you will supply the "live" implementation of the LocationManager, which is an instance that holds onto a CLLocationManager on the inside and interacts with it directly:

let store = Store(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    locationManager: .live,
    // And your other dependencies...
  )
)

This is enough to implement a basic application that interacts with Core Location.

The true power of building your application and interfacing with Core Location in this way is the ability to test how your application interacts with Core Location. It starts by creating a TestStore whose environment contains a .failing version of the LocationManager. Then, you can selectively override whichever endpoints your feature needs to supply deterministic functionality.

For example, to test the flow of asking for location authorization, being denied, and showing an alert, we need to override the create and requestWhenInUseAuthorization endpoints. The create endpoint needs to return an effect that emits the delegate actions, which we can control via a publish subject. And the requestWhenInUseAuthorization endpoint is a fire-and-forget effect, but we can make assertions that it was called how we expect.

let store = TestStore(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    locationManager: .failing
  )
)

var didRequestInUseAuthorization = false
let locationManagerSubject = PassthroughSubject<LocationManager.Action, Never>()

store.environment.locationManager.create = { locationManagerSubject.eraseToEffect() }
store.environment.locationManager.requestWhenInUseAuthorization = {
  .fireAndForget { didRequestInUseAuthorization = true }
}

Then we can write an assertion that simulates a sequence of user steps and location manager delegate actions, and we can assert against how state mutates and how effects are received. For example, we can have the user come to the screen, deny the location authorization request, and then assert that an effect was received which caused the alert to show:

store.send(.onAppear)

// Simulate the user denying location access
locationManagerSubject.send(.didChangeAuthorization(.denied))

// We receive the authorization change delegate action from the effect
store.receive(.locationManager(.didChangeAuthorization(.denied))) {
  $0.alert = """
    Please give location access so that we can show you some cool stuff.
    """

// Store assertions require all effects to be completed, so we complete
// the subject manually.
locationManagerSubject.send(completion: .finished)

And this is only the tip of the iceberg. We can further test what happens when we are granted authorization by the user and the request for their location returns a specific location that we control, and even what happens when the request for their location fails. It is very easy to write these tests, and we can test deep, subtle properties of our application.

Installation

You can add ComposableCoreLocation to an Xcode project by adding it as a package dependency.

  1. From the File menu, select Swift Packages › Add Package Dependency…
  2. Enter "https://github.com/pointfreeco/composable-core-location" into the package repository URL text field

Documentation

The latest documentation for the Composable Core Location APIs is available here.

Help

If you want to discuss Composable Core Location and the Composable Architecture, or have a question about how to use them to solve a particular problem, ask around on its Swift forum.

License

This library is released under the MIT license. See LICENSE for details.

composable-core-location's People

Contributors

andreyz avatar ferologics avatar fire-at-will avatar hanneskaeufler avatar joeblau avatar klundberg avatar mbrandonw avatar rhysm94 avatar stephencelis avatar tomhut avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

composable-core-location's Issues

take too much time to update current location

Describe the bug
I don't know what is the problem I just know the issue and send a small video clip also click any point of interest button that does not work

I run your demo app and it takes too much time to my iPhone 6s
take too much time to update the current location

Expected behavior
it should take not more than 1/2s or less

Screenshots

RPReplay_Final1625561162.MP4

Environment

  • Swift [5.2.2]
  • OS (if applicable): [iOS 12, 13, 14]

Additional context
Add any more context about the problem here.

Support for more recent versions of TCA?

I am currently building an app in TCA to keep my chops fresh on the latest in Apple Platforms development. The app needs to hook into CoreLocation, and hence I found this repository. But just from searching, it appears that this library has not been updated to support newer versions of TCA (updated concurrency, reducer protocol, etc).

Are there plans to update this library? Or can anyone direct me to an example of implementing the delegation pattern of CoreLocation (and many other Apple APIs) within newer versions of TCA? The closest example I could find was in the audio recorder case study.

Time for new release?

Looks like there has been quite some progress since the 0.1.0 release to warrant a new release.

A possible memory leak when composable architecture is combined with the composable core location

Description
Hey guys,
first of all, I would like to thank you both for making such a great library. Working with it is a lot of fun! 🥇

I believe, however, that I've found a memory leak when composable architecture is combined with the composable core location dependency in a very specific way. I've prepared a mini demo for you and I would really appreciate it if you'll have time to check it out. It is available here.

To Reproduce

  1. Download the app
  2. Launch the app in the simulator
  3. Allow location updates
  4. Start simulating location updates, by enabling Simulator -> Features -> Location -> City Run
  5. Make sure that you are receiving locations (map is moving)
  6. Leave it running for a second or two, then take a look at the memory graph
  7. You'll see that some of the CLLocation objects are leaking (image attached)

After investigating, I've found out that if I don't combine reducerA into the "main" appReducer, location objects stop leaking. I.e. using just one pulled back child reducer with a core location dependency inside of .combined block works fine. But as soon as you add another "scoped" reducer, the issue reappears. You can reproduce this "fix" by commenting out the following code.

// MARK: - App Reducer

let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(

    /*
     * Comment out the following reducer
     */
    reducerA
        .pullback(
            state: \.stateA,
            action: /AppAction.actionA,
            environment: { $0 }
        ),

    reducerB
        .pullback(
            state: \.stateB,
            action: /AppAction.actionB,
            environment: { $0 }
        )
)

The other strange thing that it seems like it also helps is if you remove the map function from the didUpdateLocations method in the core location dependency and only use the last location.

Change from:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    subscriber.send(.didUpdateLocations(locations.map(Location.init(rawValue:))))
}

to:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    subscriber.send(.didUpdateLocations(Location.init(rawValue: locations.last!)))
 }

Expected behavior
CLLocation objects should not leak.

Screenshots
MemoryGraph

Environment

  • Xcode Version 12.2 (12B45b)
  • Apple Swift version 5.3.1 (swiftlang-1200.0.41 clang-1200.0.32.8)
  • macOS Big Sur Version 11.0.1

Thanks for your help in advance. I hope that I am not wasting your time by misusing the library 😄

Delegate must respond to locationManager:didUpdateLocations:

i dont understand why its always crashing i am using main branch
// And/or enter code that reproduces the behavior here.
https://gist.github.com/saroar/abdb5ec787246d150e66be1a4cb7d03b

Expected behavior
Give a clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Environment

  • Xcode 13
  • Swift 5.5
  • OS (15.0): [e.g. iOS 13]

Additional context
Add any more context about the problem here.

CCL dependency on TCA conflicts with dependency on TCA

Describe the bug
In projects that depend on both TCA and CCL Xcode raises the following warning during SPM package resolution:

'composable-core-location' dependency on 'https://github.com/pointfreeco/swift-composable-architecture' conflicts with dependency on 'https://github.com/pointfreeco/swift-composable-architecture' which has the same identity 'swift-composable-architecture'. this will be escalated to an error in future versions of SwiftPM.

To Reproduce

  1. Create a new project
  2. Add TCA and CCL as dependencies
  3. Reset package caches
  4. Observe warning

Expected behavior
No warning during SPM package resolution.

Environment

  • Xcode Version 13.3 (13E113)
  • Swift version 5.6
  • macOS 12.3 Beta (21E5196i)

Failed to resolve dependencies Dependencies could not be resolved because root depends on 'swift-composable-architecture' 1.2.0. 'swift-composable-architecture' 0.43.0..<1.0.0 is required because 'composable-core-location' 0.3.0 depends on 'swift-composable-architecture' 0.43.0..<1.0.0 and root depends on 'composable-core-location' 0.3.0.

Describe the bug
Failed to resolve dependencies Dependencies could not be resolved because root depends on 'swift-composable-architecture' 1.2.0.
'swift-composable-architecture' 0.43.0..<1.0.0 is required because 'composable-core-location' 0.3.0 depends on 'swift-composable-architecture' 0.43.0..<1.0.0 and root depends on 'composable-core-location' 0.3.0.

To Reproduce
install TCA version 1.2.0 and Composable core location 0.3.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.