From 6212eba27e4edcd214e46a1088e121e0714e02e9 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Fri, 29 Nov 2024 18:34:44 +0100 Subject: [PATCH] feat: More DemoApp tests and cases --- .../ConfidenceDemoApp/ConfidenceDemoApp.swift | 51 +++++++++++---- .../ConfidenceDemoApp/ContentView.swift | 63 ++++++++++++++++--- .../ConfidenceDemoApp/LoginView.swift | 10 +-- Sources/Confidence/Confidence.swift | 4 -- 4 files changed, 95 insertions(+), 33 deletions(-) diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index b5426628..27601250 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -1,40 +1,69 @@ 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"] ?? "" + private var color: Color init() { + @AppStorage("appVersion") var appVersion = 0 + @AppStorage("loggedUser") var loggedUser: String? confidence = Confidence - .Builder(clientSecret: ProcessInfo.processInfo.environment["CLIENT_SECRET"] ?? "", loggerLevel: .TRACE) + .Builder(clientSecret: secret, loggerLevel: .TRACE) .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)) - } + 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.putContextLocal(context: context, removeKeys: []) + + print(">> \(appVersion)") + 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)) } + self.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) } else { - ContentView(confidence: confidence) + ContentView(confidence: confidence, color: color) .environmentObject(flaggingState) } } } + + 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 { diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift index 6aec74cd..535699bc 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ContentView.swift @@ -7,11 +7,14 @@ struct ContentView: View { @AppStorage("loggedUser") private var loggedUser: String? @State var isLoggingOut: Bool = false @State var loggedOut: Bool = false + @State var textColor: Color = .red + @State var staleColor: Color private let confidence: Confidence - init(confidence: Confidence) { + init(confidence: Confidence, color: Color? = nil) { self.confidence = confidence + self.staleColor = color ?? .red } var body: some View { @@ -19,21 +22,30 @@ struct ContentView: View { if (status.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 (status.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 Task { @@ -46,11 +58,36 @@ struct ContentView: View { .navigationDestination(isPresented: $loggedOut) { LoginView(confidence: confidence) } + Spacer() + } + ZStack { + VStack { + Spacer() + Text("This text doesn't wait for flags to be loaded") + .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 change on flag re-load") + .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 determine at startup and won't change") + .font(.caption) + // Any change of flagState triggers a re-read of the value + .foregroundStyle(staleColor) + } + }.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 @@ -63,3 +100,9 @@ struct ContentView: View { } } } + +struct AboutPage: View { + var body: some View { + Text("About Page") + } +} diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift b/ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift index f0469dd5..12e879da 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/LoginView.swift @@ -22,14 +22,14 @@ struct LoginView: View { status.state = .loading // 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 } // 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 @@ -45,10 +45,4 @@ struct LoginView: View { } } } - private var combinedBinding: Binding { - Binding( - get: { loginCompleted && flagsLoaded }, - set: { _ in } // No-op; navigation state is derived from other properties - ) - } } diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index 4b4f0127..d60b762c 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -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 - ) } }