Skip to content

Commit

Permalink
feat: More DemoApp tests and cases
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziodemaria committed Dec 2, 2024
1 parent d62cb2b commit df7f196
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 40 deletions.
50 changes: 39 additions & 11 deletions ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
import Confidence
import SwiftUI

@main
struct ConfidenceDemoApp: App {
@AppStorage("loggedUser") private var loggedUser: String?
@AppStorage("appVersion") private var appVersion = 0

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 = 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)
}

confidence = Confidence
.Builder(clientSecret: ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "", loggerLevel: .TRACE)
.Builder(clientSecret: secret, loggerLevel: .TRACE)
.withContext(initialContext: context)
.build()

if let loggedUser = loggedUser {
confidence.putContextLocal(context: ["user_id": ConfidenceValue.init(string: loggedUser)], removeKeys: [])
do {
try confidence.activate()
flaggingState.state = .ready
} catch {
print("Error during activation of Confidence")
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
flaggingState.state = .loading
do {
// DANGER: here we are activating anything in storage, could be data of a different user
try confidence.activate() // Activating the existing cache, in case the new fetch fails i.e. offline mode
} catch {
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
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 {
Text("Client secret: \(secret)")
.font(.caption)
if loggedUser == nil {
LoginView(confidence: confidence)
.environmentObject(flaggingState)
Expand All @@ -35,9 +50,22 @@ struct ConfidenceDemoApp: App {
}
}
}

private func updateConfidence() {
Task {
do {
try await Task.sleep(nanoseconds: 2 * 1_000_000_000)
try await confidence.fetchAndActivate()
flaggingState.state = .ready
} catch {
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
}
}
}

class ExperimentationFlags: ObservableObject {
var color: Color = .red // This is set via Confidence ONLY on user login/logout
@Published var state: State = .notReady

enum State: Equatable {
Expand Down
70 changes: 56 additions & 14 deletions ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,90 @@ import Confidence
import Combine

struct ContentView: View {
@EnvironmentObject var status: ExperimentationFlags
@EnvironmentObject var flaggingState: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State var isLoggingOut: Bool = false
@State var loggedOut: Bool = false
@State var textColor: Color = .red

private let confidence: Confidence

init(confidence: Confidence) {
init(confidence: Confidence, color: Color? = nil) {
self.confidence = confidence
}

var body: some View {
NavigationStack {
if (status.state == .loading && !isLoggingOut) {
if (flaggingState.state == .loading && !isLoggingOut) {
VStack {
Spacer()
Text("Welcome \(loggedUser ?? "?")")
Text("Login successful: \(loggedUser ?? "?")")
Text("We are preparing your experience...")
ProgressView()
Spacer()
}
} else {
VStack {
Text("Hello World!")
.font(.largeTitle)
.foregroundStyle(ContentView.getColor(color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray")))
if (flaggingState.state == .ready) {
VStack {
Spacer()
if let user = loggedUser {
let eval = confidence.getEvaluation(key: "swift-demoapp.color", defaultValue: "Gray")
Text("Hello \(user)")
.font(.largeTitle)
// Any change of flagState triggers a re-read of the value
// The "state = ready" wrapper makes sure only the reconciled value is shown at any time
.foregroundStyle(ContentView.getColor(color: eval.value))
.padding()
}
}
}
NavigationLink(destination: AboutPage()) {
Text("Navigate")
}
.padding()
Button("Logout") {
isLoggingOut = true

loggedUser = nil
status.state = .loading
flaggingState.state = .loading
flaggingState.color = .red
Task {
await confidence.removeContext(key: "user_id")
status.state = .ready
flaggingState.state = .ready
}
loggedUser = nil
loggedOut = true
}
.navigationDestination(isPresented: $loggedOut) {
LoginView(confidence: confidence)
}
Spacer()
}
ZStack {
VStack {
Spacer()
Text("This text is set on onAppear, doesn't wait for reconciliation")
.font(.caption)
.foregroundStyle(textColor) // This is set on onAppear and not changing, although it might show different values within the session if the user exit and re-enter ContentView (try tapping on Navigate and go Back)
Text("This text dynamically changes on flags fetch")
.font(.caption)
// Any change of flagState triggers a re-read of the value
.foregroundStyle(ContentView.getColor(color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray")))
Text("This text is fixed from user session start, and before any flags fetch")
.font(.caption)
// Any change of flagState triggers a re-read of the value
.foregroundStyle(flaggingState.color)
}
}.onAppear {
let eval = confidence.getEvaluation(key: "swift-demoapp.color", defaultValue: "Gray")
/*
Due to syntetic delay, the new context hasn't entered the SDK yet se we are operating with
the last seen context before the view loads
*/
print(">> Evaluation reason: \(eval)")
textColor = ContentView.getColor(color: eval.value)
}
}
}

private static func getColor(color: String) -> Color {
public static func getColor(color: String) -> Color {
switch color {
case "Green":
.green
Expand All @@ -63,3 +99,9 @@ struct ContentView: View {
}
}
}

struct AboutPage: View {
var body: some View {
Text("About Page")
}
}
23 changes: 12 additions & 11 deletions ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SwiftUI
import Confidence

struct LoginView: View {
@EnvironmentObject var status: ExperimentationFlags
@EnvironmentObject var flaggingState: ExperimentationFlags
@AppStorage("loggedUser") private var loggedUser: String?
@State private var loginCompleted = false
@State private var flagsLoaded = false
Expand All @@ -19,17 +19,24 @@ struct LoginView: View {
VStack {
Spacer()
Button("Login as user1") {
status.state = .loading
flaggingState.state = .loading
do {
try confidence.activate() // Activating the existing cache, in case the new fetch fails i.e. offline mode
} catch {
flaggingState.state = .error(ExperimentationFlags.CustomError(message: error.localizedDescription))
}
flaggingState.color = ContentView.getColor(color: confidence.getValue(key: "swift-demoapp.color", defaultValue: "Gray"))
// Load flags
Task {
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) // Sleep for 3 seconds

try? await Task.sleep(nanoseconds: 5 * 1_000_000_000)
await confidence.putContext(context: ["user_id": .init(string: "user1")])
status.state = .ready
flaggingState.state = .ready
}
// Load everything else for this user
Task {
loggingIn = true
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // Sleep for 3 seconds
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000)
loggedUser = "user1"
loggingIn = false
loginCompleted = true
Expand All @@ -45,10 +52,4 @@ struct LoginView: View {
}
}
}
private var combinedBinding: Binding<Bool> {
Binding(
get: { loginCompleted && flagsLoaded },
set: { _ in } // No-op; navigation state is derived from other properties
)
}
}
4 changes: 0 additions & 4 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ public class Confidence: ConfidenceEventSender {
}
let savedFlags = try storage.load(defaultValue: FlagResolution.EMPTY)
cache = savedFlags
debugLogger?.logMessage(
message: "[Activating stored cache]",
isWarning: false
)
}
}

Expand Down

0 comments on commit df7f196

Please sign in to comment.