RevenueCat / purchases-ios

In-app purchases and subscriptions made easy. Support for iOS, watchOS, tvOS, macOS, and visionOS.
https://www.revenuecat.com/
MIT License
2.37k stars 320 forks source link

Custom `UserDefaults` Compatibility #4427

Open lucamegh opened 1 month ago

lucamegh commented 1 month ago

Our app uses a custom UserDefaultsClient interface to manage UserDefaults, defined as follows:

@DependencyClient
public struct UserDefaultsClient: Sendable {
  public var bool: @Sendable (_ forKey: String) -> Bool = { _ in false }

  public var setBool: @Sendable (_ forKey: String) -> Void

  ...
}

The SDK currently lacks support for abstractions like these, causing a compile error when trying to use UserDefaultsClient instead of UserDefaults:

@Dependency(UserDefaultsClient.self) var userDefaults

Purchases.configure(
  with: Configuration
      .builder(withAPIKey: apiKey)
      .with(userDefaults: userDefaults) // Compiler Error!
)

To work around this limitation, I am forced to add a new dependency endpoint that provides direct access to the wrapped UserDefaults instance.

@DependencyClient
public struct UserDefaultsClient: Sendable {
  ...

  public var wrapped: @Sendable () -> UserDefaults
}

However, this workaround defeats the purpose of the abstraction, as it forces us back to using a concrete UserDefaults instance.

Solution

  1. Introduce UserDefaultsProtocol: Add a protocol to the SDK that mirrors the UserDefaults methods used within the SDK:
public protocol UserDefaultsProtcol {
  func bool(forKey defaultName: String) -> Bool

  func set(_ value: Bool, forKey defaultName: String)

  ...
}
  1. Extend Configuration.Builder: Add support for UserDefaultsProtocol in the Configuration.Builder:

    @objc public func with(userDefaults: UserDefaultsProtocol) -> Builder {
    self.userDefaults = userDefaults
    return self
    }
  2. Conform UserDefaults (and UserDefaultsClient) to UserDefaultsProtocol.

The solution enables custom UserDefaults implementations, such as UserDefaultsClient, to integrate seamlessly with the SDK, enhancing flexibility without introducing breaking changes to the public API.

RCGitBot commented 1 month ago

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

nyeu commented 1 month ago

Hi @lucamegh! Thank you for your suggestion - we will look into it and prioritize it accordingly.