Skip to content

Commit 1d3d8ac

Browse files
authored
[MBL-3143] Statsig Plumbing (#2796)
* create statsig client * add client to AppEnv and initialize StatsigClient on app launch * add a new video feed StatsigFeature * link Statsig to Library and Kickstarter-Framework * setup KeyValueStore for caching statsig feature flags * add tests * format * cleanup unused method * add in missing logic and PR nits * PR feedback cleanup
1 parent 15c6f4f commit 1d3d8ac

16 files changed

Lines changed: 187 additions & 6 deletions

File tree

Kickstarter-Framework/Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ let package = Package(
2828
.package(url: "https://github.com/onevcat/Kingfisher", from: "8.5.0"),
2929
.package(url: "https://github.com/stripe/stripe-ios-spm", from: "23.32.0"),
3030
.package(url: "https://github.com/yeatse/KingfisherWebP.git", from: "1.6.0"),
31-
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.6")
31+
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.6"),
32+
.package(url: "https://github.com/statsig-io/statsig-kit.git", from: "1.61.0")
3233
],
3334
targets: [
3435
.target(
@@ -49,7 +50,8 @@ let package = Package(
4950
.product(name: "Stripe", package: "stripe-ios-spm"),
5051
.product(name: "StripePaymentSheet", package: "stripe-ios-spm"),
5152
.product(name: "KingfisherWebP", package: "KingfisherWebP"),
52-
.product(name: "SegmentBrazeUI", package: "braze-segment-swift")
53+
.product(name: "SegmentBrazeUI", package: "braze-segment-swift"),
54+
.product(name: "Statsig", package: "statsig-kit")
5355
],
5456
resources: [
5557
.process("Resources")

Kickstarter-Framework/Sources/Kickstarter-Framework/Kickstarter-iOS/AppDelegateViewModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ public protocol AppDelegateViewModelOutputs {
100100
/// Emits when the application should configure Segment with an instance of Braze.
101101
var configureSegmentWithBraze: Signal<String, Never> { get }
102102

103+
/// Emits when the application should configure Statsig.
104+
var configureStatsig: Signal<(), Never> { get }
105+
103106
/// Return this value in the delegate method.
104107
var continueUserActivityReturnValue: MutableProperty<Bool> { get }
105108

@@ -717,6 +720,8 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi
717720

718721
self.configureFirebase = self.applicationLaunchOptionsProperty.signal.ignoreValues()
719722

723+
self.configureStatsig = self.applicationLaunchOptionsProperty.signal.ignoreValues()
724+
720725
self.setApplicationShortcutItems = currentUserEvent
721726
.values()
722727
.switchMap(shortcutItems(forUser:))
@@ -922,6 +927,7 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi
922927
public let applicationIconBadgeNumber: Signal<Int, Never>
923928
public let configureFirebase: Signal<(), Never>
924929
public let configureSegmentWithBraze: Signal<String, Never>
930+
public let configureStatsig: Signal<(), Never>
925931
public let continueUserActivityReturnValue = MutableProperty(false)
926932
public let emailVerificationCompleted: Signal<(String, Bool), Never>
927933
public let findRedirectUrl: Signal<URL, Never>

Kickstarter-iOS/AppDelegate.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import ReactiveSwift
1616
import SafariServices
1717
import Segment
1818
import SegmentBrazeUI
19+
import Statsig
1920
import SwiftUI
2021
import UIKit
2122
import UserNotifications
@@ -219,6 +220,14 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate {
219220
}
220221
#endif
221222

223+
#if INTERNAL_BUILD
224+
self.viewModel.outputs.configureStatsig
225+
.observeForUI()
226+
.observeValues { [weak self] in
227+
self?.configureStatsig()
228+
}
229+
#endif
230+
222231
self.disposables.append(
223232
self.viewModel.outputs.trackingAuthorizationStatus
224233
.observeForUI()
@@ -458,6 +467,13 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate {
458467
}
459468
}
460469

470+
private func configureStatsig() {
471+
let client = StatsigClient(sdkKey: Secrets.Statsig.staging)
472+
AppEnvironment.updateStatsigClient(client)
473+
474+
client.initialize(userID: AppEnvironment.current.currentUser?.id.toString())
475+
}
476+
461477
private func fetchAndActivateRemoteConfig() {
462478
AppEnvironment.current.remoteConfigClient?.fetchAndActivate { _, error in
463479
guard let remoteConfigActivationError = error else {

Kickstarter.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,10 @@
731731
E1D529FC2ED4D21A00923AA9 /* ShippableLocationsForProjectQuery.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = ShippableLocationsForProjectQuery.graphql; sourceTree = "<group>"; };
732732
/* End PBXFileReference section */
733733

734+
/* Begin PBXFileSystemSynchronizedRootGroup section */
735+
6008DF842F6353BF00A393E6 /* Statsig */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Statsig; sourceTree = "<group>"; };
736+
/* End PBXFileSystemSynchronizedRootGroup section */
737+
734738
/* Begin PBXFrameworksBuildPhase section */
735739
A7D1F9421C850B7C000D41D5 /* Frameworks */ = {
736740
isa = PBXFrameworksBuildPhase;
@@ -1134,6 +1138,7 @@
11341138
AAF547E22F2445C0001DB5AC /* Extensions */,
11351139
AAF547F12F2445C0001DB5AC /* PagedContainer */,
11361140
AAF547F32F2445C0001DB5AC /* RemoteConfig */,
1141+
6008DF842F6353BF00A393E6 /* Statsig */,
11371142
AAF547F82F2445C0001DB5AC /* Tracking */,
11381143
AAF548022F2445C0001DB5AC /* UseCases */,
11391144
AAF5489B2F2445C0001DB5AC /* ViewModels */,
@@ -1396,6 +1401,9 @@
13961401
dependencies = (
13971402
AAF3BD5C2F19876800DAE921 /* PBXTargetDependency */,
13981403
);
1404+
fileSystemSynchronizedGroups = (
1405+
6008DF842F6353BF00A393E6 /* Statsig */,
1406+
);
13991407
name = LibraryTests;
14001408
packageProductDependencies = (
14011409
AAFCECF22F20353A00EAC2FE /* FirebaseCore */,

Kickstarter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Library/Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ let package = Package(
3636
.package(url: "https://github.com/onevcat/Kingfisher", from: "8.5.0"),
3737
.package(url: "https://github.com/stripe/stripe-ios-spm", from: "23.32.0"),
3838
.package(url: "https://github.com/yeatse/KingfisherWebP.git", from: "1.6.0"),
39-
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.6")
39+
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.6"),
40+
.package(url: "https://github.com/statsig-io/statsig-kit.git", from: "1.61.0")
4041

4142
],
4243
targets: [
@@ -59,7 +60,8 @@ let package = Package(
5960
.product(name: "Prelude", package: "Kickstarter-Prelude"),
6061
.product(name: "KingfisherWebP", package: "KingfisherWebP"),
6162
.product(name: "Lottie", package: "lottie-ios"),
62-
.product(name: "SegmentBrazeUI", package: "braze-segment-swift")
63+
.product(name: "SegmentBrazeUI", package: "braze-segment-swift"),
64+
.product(name: "Statsig", package: "statsig-kit")
6365
],
6466
path: "Sources/Library",
6567
resources: [

Library/Sources/Library/Library/AppEnvironment.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ public struct AppEnvironment: AppEnvironmentType {
8888
)
8989
}
9090

91+
// MARK: - Statsig
92+
93+
public static func updateStatsigClient(_ statsigClient: StatsigClientType?) {
94+
self.replaceCurrentEnvironment(
95+
statsigClient: statsigClient
96+
)
97+
}
98+
9199
public static func updateConfig(_ config: Config) {
92100
let debugConfigOrConfig = self.current.debugData?.config ?? config
93101

@@ -216,6 +224,7 @@ public struct AppEnvironment: AppEnvironmentType {
216224
reachability: SignalProducer<Reachability, Never> = AppEnvironment.current.reachability,
217225
remoteConfigClient: RemoteConfigClientType? = AppEnvironment.current.remoteConfigClient,
218226
scheduler: DateScheduler = AppEnvironment.current.scheduler,
227+
statsigClient: StatsigClientType? = AppEnvironment.current.statsigClient,
219228
ubiquitousStore: KeyValueStoreType = AppEnvironment.current.ubiquitousStore,
220229
userDefaults: KeyValueStoreType = AppEnvironment.current.userDefaults,
221230
uuidType: UUIDType.Type = AppEnvironment.current.uuidType
@@ -250,6 +259,7 @@ public struct AppEnvironment: AppEnvironmentType {
250259
reachability: reachability,
251260
remoteConfigClient: remoteConfigClient,
252261
scheduler: scheduler,
262+
statsigClient: statsigClient,
253263
ubiquitousStore: ubiquitousStore,
254264
userDefaults: userDefaults,
255265
uuidType: uuidType
@@ -291,6 +301,7 @@ public struct AppEnvironment: AppEnvironmentType {
291301
reachability: SignalProducer<Reachability, Never> = AppEnvironment.current.reachability,
292302
remoteConfigClient: RemoteConfigClientType? = AppEnvironment.current.remoteConfigClient,
293303
scheduler: DateScheduler = AppEnvironment.current.scheduler,
304+
statsigClient: StatsigClientType? = AppEnvironment.current.statsigClient,
294305
ubiquitousStore: KeyValueStoreType = AppEnvironment.current.ubiquitousStore,
295306
userDefaults: KeyValueStoreType = AppEnvironment.current.userDefaults,
296307
uuidType: UUIDType.Type = AppEnvironment.current.uuidType
@@ -328,6 +339,7 @@ public struct AppEnvironment: AppEnvironmentType {
328339
reachability: reachability,
329340
remoteConfigClient: remoteConfigClient,
330341
scheduler: scheduler,
342+
statsigClient: statsigClient,
331343
ubiquitousStore: ubiquitousStore,
332344
userDefaults: userDefaults,
333345
uuidType: uuidType

Library/Sources/Library/Library/Environment.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public struct Environment {
112112
/// `QueueScheduler.mainQueueScheduler`.
113113
public let scheduler: DateScheduler
114114

115+
public let statsigClient: StatsigClientType?
116+
115117
/// A ubiquitous key-value store. Default value is `NSUbiquitousKeyValueStore.default`.
116118
public let ubiquitousStore: KeyValueStoreType
117119

@@ -154,6 +156,7 @@ public struct Environment {
154156
reachability: SignalProducer<Reachability, Never> = Reachability.signalProducer,
155157
remoteConfigClient: RemoteConfigClientType? = nil,
156158
scheduler: DateScheduler = QueueScheduler.main,
159+
statsigClient: StatsigClientType? = nil,
157160
ubiquitousStore: KeyValueStoreType = NSUbiquitousKeyValueStore.default,
158161
userDefaults: KeyValueStoreType = UserDefaults.standard,
159162
uuidType: UUIDType.Type = UUID.self
@@ -190,6 +193,7 @@ public struct Environment {
190193
self.reachability = reachability
191194
self.remoteConfigClient = remoteConfigClient
192195
self.scheduler = scheduler
196+
self.statsigClient = statsigClient
193197
self.ubiquitousStore = ubiquitousStore
194198
self.userDefaults = userDefaults
195199
self.uuidType = uuidType

Library/Sources/Library/Library/KeyValueStoreType.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public enum AppKeys: String {
1616
case remoteConfigFeatureFlags = "com.kickstarter.KeyValueStoreType.remoteConfigFeatureFlags"
1717
case seenAppRating = "com.kickstarter.KeyValueStoreType.hasSeenAppRating"
1818
case seenGamesNewsletter = "com.kickstarter.KeyValueStoreType.hasSeenGamesNewsletter"
19+
case statsigFeatureFlags = "com.kickstarter.KeyValueStoreType.statsigFeatureFlags"
1920
// swiftformat:enable wrap
2021
}
2122

@@ -47,6 +48,7 @@ public protocol KeyValueStoreType: AnyObject {
4748
var lastSeenActivitySampleId: Int { get set }
4849
var onboardingCategories: Data? { get set }
4950
var remoteConfigFeatureFlags: [String: Bool] { get set }
51+
var statsigFeatureFlags: [String: Bool] { get set }
5052
}
5153

5254
extension KeyValueStoreType {
@@ -177,6 +179,16 @@ extension KeyValueStoreType {
177179
self.set(newValue, forKey: AppKeys.remoteConfigFeatureFlags.rawValue)
178180
}
179181
}
182+
183+
public var statsigFeatureFlags: [String: Bool] {
184+
get {
185+
return self
186+
.object(forKey: AppKeys.statsigFeatureFlags.rawValue) as? [String: Bool] ?? [:]
187+
}
188+
set {
189+
self.set(newValue, forKey: AppKeys.statsigFeatureFlags.rawValue)
190+
}
191+
}
180192
}
181193

182194
extension UserDefaults: KeyValueStoreType {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
public final class MockStatsigClient: StatsigClientType {
4+
public var features: [String: Bool] = [:]
5+
6+
public init() {}
7+
8+
public func initialize(userID _: String?) {}
9+
10+
public func checkGate(for feature: StatsigFeature) -> Bool {
11+
self.features[feature.rawValue] ?? false
12+
}
13+
}

0 commit comments

Comments
 (0)