Skip to content

Commit c7330df

Browse files
feat: SSE / real time flags support #32 (#67)
* Update example app with the latest local SDK version * Checkpoint commit, SSE connection up and running, need to implement the decoding and logic * Attempt to decode the SSE data * Initial implementation of the logic from the Kotlin implementation * Add a SwiftUI view to the example app to show real-time updates to the flags * Fix an issue with taking too many event stream updated-at times * Swift lint and tidy up * Add some unit tests for the SSEManager class and also change the reconnection logic to use a backoff timer * Tidy and document SSEManager * A bit of a tidy and swift linting * Removed some commented-out code and put some back in * Run swift format and update the swift format package * Update FlagsmithClient/Tests/SSEManagerTests.swift Co-authored-by: Matthew Elwell <[email protected]> * Updated use of API Key -> env * Remove the unnecessary Podfile.lock from the example app --------- Co-authored-by: Matthew Elwell <[email protected]>
1 parent ba0fb2b commit c7330df

24 files changed

+948
-373
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ Carthage/Build
4848
# hence it is not needed unless you have added a package configuration file to your project
4949
.swiftpm
5050
.build
51+
52+
# This was causing confusion on PRs and is really just a record of the last time the example
53+
# pods were re-built from scratch. It's not useful to keep in the repo.
54+
Example/Podfile.lock

Example/FlagsmithClient.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
7F76D20A2C9852510028470B /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F76D2092C9852510028470B /* SwiftUIView.swift */; };
1011
C87CA15C7294632245730C64 /* Pods_FlagsmithClient_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9F7BEB767BE008D76723DD6 /* Pods_FlagsmithClient_Example.framework */; };
1112
DAED1E94268DBF9100F91DBC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED1E8C268DBF9100F91DBC /* ViewController.swift */; };
1213
DAED1E95268DBF9100F91DBC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = DAED1E8D268DBF9100F91DBC /* LaunchScreen.xib */; };
@@ -18,6 +19,7 @@
1819
/* Begin PBXFileReference section */
1920
5357A0EF6DA00FB7B1E84F8C /* Pods-FlagsmithClient_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlagsmithClient_Example.release.xcconfig"; path = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.release.xcconfig"; sourceTree = "<group>"; };
2021
607FACD01AFB9204008FA782 /* FlagsmithClient_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlagsmithClient_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
22+
7F76D2092C9852510028470B /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; };
2123
9DFD4B33E10902546A38D90F /* Pods-FlagsmithClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlagsmithClient_Example.debug.xcconfig"; path = "Target Support Files/Pods-FlagsmithClient_Example/Pods-FlagsmithClient_Example.debug.xcconfig"; sourceTree = "<group>"; };
2224
C4197E729B096AA93F72A537 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
2325
C9F7BEB767BE008D76723DD6 /* Pods_FlagsmithClient_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlagsmithClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -96,6 +98,7 @@
9698
DAED1E8F268DBF9100F91DBC /* Main.storyboard */,
9799
DAED1E91268DBF9100F91DBC /* Images.xcassets */,
98100
DAED1E92268DBF9100F91DBC /* AppDelegate.swift */,
101+
7F76D2092C9852510028470B /* SwiftUIView.swift */,
99102
);
100103
path = FlagsmithClient;
101104
sourceTree = "<group>";
@@ -220,6 +223,7 @@
220223
files = (
221224
DAED1E98268DBF9100F91DBC /* AppDelegate.swift in Sources */,
222225
DAED1E94268DBF9100F91DBC /* ViewController.swift in Sources */,
226+
7F76D20A2C9852510028470B /* SwiftUIView.swift in Sources */,
223227
);
224228
runOnlyForDeploymentPostprocessing = 0;
225229
};

Example/FlagsmithClient.xcodeproj/xcshareddata/xcschemes/FlagsmithClient_Example.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1320"
3+
LastUpgradeVersion = "1540"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

Example/FlagsmithClient/AppDelegate.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
import UIKit
1010
import FlagsmithClient
11+
#if canImport(SwiftUI)
12+
import SwiftUI
13+
#endif
1114

1215
func isSuccess<T, F>(_ result: Result<T, F>) -> Bool {
1316
if case .success = result { return true } else { return false }
@@ -43,6 +46,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4346

4447
// set analytics on or off
4548
Flagsmith.shared.enableAnalytics = true
49+
50+
// Enable real time updates
51+
Flagsmith.shared.enableRealtimeUpdates = true
4652

4753
// set the analytics flush period in seconds
4854
Flagsmith.shared.analyticsFlushPeriod = 10
@@ -65,6 +71,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
6571

6672
// Flagsmith.shared.setTrait(Trait(key: "<my_key>", value: "<my_value>"), forIdentity: "<my_identity>") { (result) in print(result) }
6773
// Flagsmith.shared.getIdentity("<my_key>") { (result) in print(result) }
74+
75+
// Launch SwiftUIView
76+
if #available(iOS 13.0, *) {
77+
let swiftUIView = SwiftUIView()
78+
window = UIWindow(frame: UIScreen.main.bounds)
79+
window?.rootViewController = UIHostingController(rootView: swiftUIView)
80+
window?.makeKeyAndVisible()
81+
}
82+
6883
return true
6984
}
7085

Example/FlagsmithClient/Base.lproj/LaunchScreen.xib

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@
2929
<constraints>
3030
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
3131
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
32-
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
33-
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
34-
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
3532
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
3633
</constraints>
3734
<nil key="simulatedStatusBarMetrics"/>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// SwiftUIView.swift
3+
// FlagsmithClient_Example
4+
//
5+
// Created by Gareth Reese on 16/09/2024.
6+
// Copyright © 2024 CocoaPods. All rights reserved.
7+
//
8+
9+
#if canImport(SwiftUI)
10+
import SwiftUI
11+
#endif
12+
import FlagsmithClient
13+
14+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *)
15+
struct SwiftUIView: View {
16+
@State private var flags: [Flag] = []
17+
@State private var isLoading = true
18+
19+
let flagsmith = Flagsmith.shared
20+
21+
var body: some View {
22+
VStack {
23+
if isLoading {
24+
ProgressView()
25+
} else {
26+
Text("Feature Flags")
27+
.font(.title)
28+
.padding()
29+
List(flags, id: \.feature.name) { flag in
30+
HStack {
31+
Text("\(flag.feature.name): \(flag.value)")
32+
Spacer()
33+
Text("\(flag.enabled ? "" : "")")
34+
}
35+
}
36+
}
37+
}
38+
.onAppear {
39+
initializeFlagsmith()
40+
subscribeToFlagUpdates()
41+
}
42+
}
43+
44+
func initializeFlagsmith() {
45+
// Fetch initial flags
46+
flagsmith.getFeatureFlags { result in
47+
DispatchQueue.main.async {
48+
switch result {
49+
case .success(let fetchedFlags):
50+
flags = fetchedFlags
51+
case .failure(let error):
52+
print("Error fetching flags: \(error)")
53+
}
54+
isLoading = false
55+
}
56+
}
57+
}
58+
59+
func subscribeToFlagUpdates() {
60+
Task {
61+
for await updatedFlags in flagsmith.flagStream {
62+
DispatchQueue.main.async {
63+
flags = updatedFlags
64+
}
65+
}
66+
}
67+
}
68+
}

Example/Podfile.lock

Lines changed: 0 additions & 16 deletions
This file was deleted.

Example/Pods/Local Podspecs/FlagsmithClient.podspec.json

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

Example/Pods/Manifest.lock

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

0 commit comments

Comments
 (0)