-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Demo App emulates login scenarios
- Loading branch information
1 parent
2b4dc68
commit 64c049f
Showing
8 changed files
with
253 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 75 additions & 35 deletions
110
ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,90 @@ | ||
import Confidence | ||
import SwiftUI | ||
|
||
class Status: ObservableObject { | ||
enum State { | ||
case unknown | ||
case ready | ||
case error(Error?) | ||
} | ||
@main | ||
struct ConfidenceDemoApp: App { | ||
@AppStorage("loggedUser") | ||
private var loggedUser: String? | ||
@AppStorage("appVersion") | ||
private var appVersion = 0 | ||
|
||
@Published var state: State = .unknown | ||
} | ||
private let confidence: Confidence | ||
private let flaggingState = ExperimentationFlags() | ||
private let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "<Empty Secret>" | ||
|
||
init() { | ||
@AppStorage("appVersion") var appVersion = 0 | ||
@AppStorage("loggedUser") var loggedUser: String? | ||
appVersion += 1 // Simulate update of the app on every new run | ||
var context = ["app_version": ConfidenceValue.init(integer: appVersion)] | ||
if let user = loggedUser { | ||
context["user_id"] = ConfidenceValue.init(string: user) | ||
} | ||
|
||
@main | ||
struct ConfidenceDemoApp: App { | ||
@StateObject private var lifecycleObserver = ConfidenceAppLifecycleProducer() | ||
confidence = Confidence | ||
.Builder(clientSecret: secret, loggerLevel: .TRACE) | ||
.withContext(initialContext: context) | ||
.build() | ||
do { | ||
// NOTE: here we are activating all the flag values from storage, regardless of how `context` looks now | ||
try confidence.activate() | ||
} catch { | ||
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription)) | ||
} | ||
// flaggingState.color is set here at startup and remains immutable until a user logs out | ||
flaggingState.color = ContentView.getColor( | ||
color: confidence.getValue( | ||
key: "swift-demoapp.color", | ||
defaultValue: "Gray")) | ||
|
||
self.appVersion = appVersion | ||
self.loggedUser = loggedUser | ||
updateConfidence() | ||
} | ||
|
||
var body: some Scene { | ||
WindowGroup { | ||
let secret = ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "" | ||
let confidence = Confidence.Builder(clientSecret: secret, loggerLevel: .TRACE) | ||
.withContext(initialContext: [ | ||
"targeting_key": ConfidenceValue(string: UUID.init().uuidString), | ||
"user_id": .init(string: "user2") | ||
]) | ||
.build() | ||
|
||
let status = Status() | ||
|
||
ContentView(confidence: confidence, status: status) | ||
.task { | ||
do { | ||
confidence.track(producer: lifecycleObserver) | ||
try await self.setup(confidence: confidence) | ||
status.state = .ready | ||
} catch { | ||
status.state = .error(error) | ||
print(error.localizedDescription) | ||
} | ||
} | ||
Text("Client secret: \(secret)") | ||
.font(.caption) | ||
if loggedUser == nil { | ||
LoginView(confidence: confidence) | ||
.environmentObject(flaggingState) | ||
} else { | ||
ContentView(confidence: confidence) | ||
.environmentObject(flaggingState) | ||
} | ||
} | ||
} | ||
|
||
private func updateConfidence() { | ||
Task { | ||
do { | ||
flaggingState.state = .loading | ||
try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // simulating slow network | ||
// The flags in storage are refreshed for the current `context`, and activated | ||
// After this line, fresh (and potentially new) flags values can be accessed | ||
try await confidence.fetchAndActivate() | ||
flaggingState.state = .ready | ||
} catch { | ||
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension ConfidenceDemoApp { | ||
func setup(confidence: Confidence) async throws { | ||
try await confidence.fetchAndActivate() | ||
class ExperimentationFlags: ObservableObject { | ||
var color: Color = .red // This is set on applicaaton start, and reset on user logout | ||
@Published var state: State = .notReady | ||
|
||
enum State: Equatable { | ||
case unknown | ||
case notReady | ||
case loading | ||
case ready | ||
case error(CustomError?) | ||
} | ||
|
||
public struct CustomError: Error, Equatable { | ||
let message: String | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import SwiftUI | ||
import Confidence | ||
|
||
struct LoginView: View { | ||
@EnvironmentObject | ||
var flaggingState: ExperimentationFlags | ||
@AppStorage("loggedUser") | ||
private var loggedUser: String? | ||
@State | ||
private var loginCompleted = false | ||
@State | ||
private var flagsLoaded = false | ||
@State | ||
private var loggingIn = false | ||
|
||
private let confidence: Confidence | ||
|
||
init(confidence: Confidence) { | ||
self.confidence = confidence | ||
} | ||
|
||
var body: some View { | ||
NavigationStack { | ||
VStack { | ||
Spacer() | ||
Button("Login as user1") { | ||
do { | ||
try confidence.activate() | ||
} catch { | ||
flaggingState.state = .error( | ||
ExperimentationFlags.CustomError(message: error.localizedDescription)) | ||
} | ||
|
||
flaggingState.color = ContentView.getColor( | ||
color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray") | ||
) | ||
|
||
// Simulating a module that handles feature flagging state during login | ||
Task { | ||
flaggingState.state = .loading | ||
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // simulating network delay | ||
// putContext adds the user_id field to the evaluation context and fetches values for it | ||
await confidence.putContext(context: ["user_id": .init(string: "user1")]) | ||
flaggingState.state = .ready | ||
} | ||
|
||
// Simulating a module that handles the actual login mechanism for a user | ||
Task { | ||
loggingIn = true | ||
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // simulating network delay | ||
loggedUser = "user1" | ||
loggingIn = false | ||
loginCompleted = true | ||
} | ||
} | ||
.navigationDestination(isPresented: $loginCompleted) { | ||
ContentView(confidence: confidence) | ||
} | ||
|
||
if loggingIn { | ||
ProgressView() | ||
} | ||
|
||
Spacer() | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.