Skip to content

Commit 74713d3

Browse files
authored
feat - amplitude 심기 (#94)
* feat(amplitude): 앰플리튜드 연동 * feat(amplitude): 앱 실행, 회원가입, 로그인, 온보딩 상태 스킵 상태, 온보드 완료 로그 추가 * feat(amplitude): 각 탭, 탐색, 킬링파트 자르기 로그 추가 * feat(1.2.6): 업데이트
1 parent 1cf20ac commit 74713d3

14 files changed

Lines changed: 503 additions & 11 deletions

File tree

KillingPart.xcodeproj/project.pbxproj

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
12F7A0012F3F010000A00001 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 12F7A0112F3F010000A00001 /* KakaoSDKCommon */; };
1717
12F7A0022F3F010000A00001 /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 12F7A0122F3F010000A00001 /* KakaoSDKAuth */; };
1818
12F7A0032F3F010000A00001 /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 12F7A0132F3F010000A00001 /* KakaoSDKUser */; };
19+
13A100032FF5A00000A1B001 /* AmplitudeUnified in Frameworks */ = {isa = PBXBuildFile; productRef = 13A100022FF5A00000A1B001 /* AmplitudeUnified */; };
1920
/* End PBXBuildFile section */
2021

2122
/* Begin PBXContainerItemProxy section */
@@ -81,6 +82,7 @@
8182
files = (
8283
12F7A0012F3F010000A00001 /* KakaoSDKCommon in Frameworks */,
8384
12F7A0022F3F010000A00001 /* KakaoSDKAuth in Frameworks */,
85+
13A100032FF5A00000A1B001 /* AmplitudeUnified in Frameworks */,
8486
1260BBC82FA88B900006BF01 /* GoogleSignInSwift in Frameworks */,
8587
1224698D2FA765FE00A6EF76 /* FirebaseMessaging in Frameworks */,
8688
1224698B2FA765FE00A6EF76 /* FirebaseCore in Frameworks */,
@@ -155,6 +157,7 @@
155157
1224698C2FA765FE00A6EF76 /* FirebaseMessaging */,
156158
1260BBC52FA88B900006BF01 /* GoogleSignIn */,
157159
1260BBC72FA88B900006BF01 /* GoogleSignInSwift */,
160+
13A100022FF5A00000A1B001 /* AmplitudeUnified */,
158161
);
159162
productName = KillingPart;
160163
productReference = 1231F11D2F372E5B00CFA51D /* KillingPart.app */;
@@ -242,6 +245,7 @@
242245
12F7A0212F3F010000A00001 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */,
243246
122469892FA765FE00A6EF76 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
244247
1260BBC42FA88B900006BF01 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */,
248+
13A100012FF5A00000A1B001 /* XCRemoteSwiftPackageReference "AmplitudeUnified-Swift" */,
245249
);
246250
preferredProjectObjectVersion = 77;
247251
productRefGroup = 1231F11E2F372E5B00CFA51D /* Products */;
@@ -448,7 +452,7 @@
448452
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
449453
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
450454
CODE_SIGN_STYLE = Automatic;
451-
CURRENT_PROJECT_VERSION = 38;
455+
CURRENT_PROJECT_VERSION = 41;
452456
DEAD_CODE_STRIPPING = YES;
453457
DEVELOPMENT_TEAM = GQ89YG5G9R;
454458
ENABLE_APP_SANDBOX = YES;
@@ -473,7 +477,7 @@
473477
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
474478
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
475479
MACOSX_DEPLOYMENT_TARGET = 14.0;
476-
MARKETING_VERSION = 1.2.3;
480+
MARKETING_VERSION = 1.2.6;
477481
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
478482
PRODUCT_NAME = "$(TARGET_NAME)";
479483
REGISTER_APP_GROUPS = YES;
@@ -493,7 +497,7 @@
493497
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
494498
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
495499
CODE_SIGN_STYLE = Automatic;
496-
CURRENT_PROJECT_VERSION = 38;
500+
CURRENT_PROJECT_VERSION = 41;
497501
DEAD_CODE_STRIPPING = YES;
498502
DEVELOPMENT_TEAM = GQ89YG5G9R;
499503
ENABLE_APP_SANDBOX = YES;
@@ -518,7 +522,7 @@
518522
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
519523
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
520524
MACOSX_DEPLOYMENT_TARGET = 14.0;
521-
MARKETING_VERSION = 1.2.3;
525+
MARKETING_VERSION = 1.2.6;
522526
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
523527
PRODUCT_NAME = "$(TARGET_NAME)";
524528
REGISTER_APP_GROUPS = YES;
@@ -691,6 +695,14 @@
691695
kind = branch;
692696
};
693697
};
698+
13A100012FF5A00000A1B001 /* XCRemoteSwiftPackageReference "AmplitudeUnified-Swift" */ = {
699+
isa = XCRemoteSwiftPackageReference;
700+
repositoryURL = "https://github.com/amplitude/AmplitudeUnified-Swift";
701+
requirement = {
702+
branch = main;
703+
kind = branch;
704+
};
705+
};
694706
/* End XCRemoteSwiftPackageReference section */
695707

696708
/* Begin XCSwiftPackageProductDependency section */
@@ -729,6 +741,11 @@
729741
package = 12F7A0212F3F010000A00001 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */;
730742
productName = KakaoSDKUser;
731743
};
744+
13A100022FF5A00000A1B001 /* AmplitudeUnified */ = {
745+
isa = XCSwiftPackageProductDependency;
746+
package = 13A100012FF5A00000A1B001 /* XCRemoteSwiftPackageReference "AmplitudeUnified-Swift" */;
747+
productName = AmplitudeUnified;
748+
};
732749
/* End XCSwiftPackageProductDependency section */
733750
};
734751
rootObject = 1231F1152F372E5B00CFA51D /* Project object */;

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

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

KillingPart/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<dict>
55
<key>APP_STORE_URL</key>
66
<string>$(APP_STORE_URL)</string>
7+
<key>AMPLITUDE_API_KEY</key>
8+
<string>$(AMPLITUDE_API_KEY)</string>
79
<key>BASE_URL</key>
810
<string>$(BASE_URL)</string>
911
<key>CFBundleURLTypes</key>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import AmplitudeUnified
2+
import Foundation
3+
4+
final class AmplitudeClient {
5+
static let shared = AmplitudeClient()
6+
7+
private var amplitude: Amplitude?
8+
private var isConfigured = false
9+
10+
private init() {}
11+
12+
func configure(apiKey: String) {
13+
guard !isConfigured else { return }
14+
guard let normalizedKey = normalizedApiKey(from: apiKey) else { return }
15+
16+
amplitude = Amplitude(apiKey: normalizedKey)
17+
isConfigured = true
18+
}
19+
20+
func track(eventType: String, properties: [String: Any]? = nil) {
21+
guard let amplitude else { return }
22+
23+
if let properties, !properties.isEmpty {
24+
amplitude.track(eventType: eventType, eventProperties: properties)
25+
return
26+
}
27+
28+
amplitude.track(eventType: eventType)
29+
}
30+
31+
func setUserId(_ userId: String?) {
32+
guard
33+
let amplitude,
34+
let userId = userId?.trimmingCharacters(in: .whitespacesAndNewlines),
35+
!userId.isEmpty
36+
else {
37+
return
38+
}
39+
40+
amplitude.setUserId(userId: userId)
41+
}
42+
43+
private func normalizedApiKey(from rawValue: String) -> String? {
44+
let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
45+
46+
guard !trimmed.isEmpty else { return nil }
47+
guard !trimmed.hasPrefix("$(") else { return nil }
48+
guard !trimmed.hasPrefix("YOUR_") else { return nil }
49+
guard trimmed != "AMPLITUDE_API_KEY" else { return nil }
50+
guard trimmed != "<TEST_API_KEY>" else { return nil }
51+
52+
return trimmed
53+
}
54+
}

KillingPart/Services/AppDelegate.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
1515
if let remotePayload = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
1616
FCMManager.shared.handleLaunchRemoteNotification(remotePayload)
1717
}
18+
let amplitudeApiKey = (Bundle.main.object(forInfoDictionaryKey: "AMPLITUDE_API_KEY") as? String ?? "")
19+
.trimmingCharacters(in: .whitespacesAndNewlines)
20+
AmplitudeClient.shared.configure(apiKey: amplitudeApiKey)
21+
AmplitudeClient.shared.track(eventType: "app_opened")
1822
print("[FCM][1] Firebase 초기화 완료")
1923
return true
2024
}

KillingPart/ViewModels/InitialSetupFlowViewModel.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ final class InitialSetupFlowViewModel: ObservableObject {
3737
private let diaryService: DiaryServicing
3838
private let calendarService: CalendarServicing
3939
private let shouldSkipNameSetupForAppleLogin: Bool
40+
private var hasTrackedOnboardingTerminalEvent = false
4041

4142
var onComplete: (() -> Void)?
4243

@@ -153,6 +154,7 @@ final class InitialSetupFlowViewModel: ObservableObject {
153154
}
154155

155156
func skipAllTutorialAndFinish() {
157+
trackOnboardingSkippedIfNeeded()
156158
onComplete?()
157159
}
158160

@@ -229,6 +231,7 @@ final class InitialSetupFlowViewModel: ObservableObject {
229231
}
230232

231233
func finishTutorial() {
234+
trackOnboardingCompletedIfNeeded()
232235
onComplete?()
233236
}
234237

@@ -321,6 +324,42 @@ final class InitialSetupFlowViewModel: ObservableObject {
321324
}
322325
}
323326

327+
private func trackOnboardingSkippedIfNeeded() {
328+
guard !hasTrackedOnboardingTerminalEvent else { return }
329+
hasTrackedOnboardingTerminalEvent = true
330+
331+
let properties: [String: Any] = [
332+
"skip_step": skipStepName(for: step)
333+
]
334+
AmplitudeClient.shared.track(eventType: "onboard_skipped", properties: properties)
335+
}
336+
337+
private func trackOnboardingCompletedIfNeeded() {
338+
guard !hasTrackedOnboardingTerminalEvent else { return }
339+
hasTrackedOnboardingTerminalEvent = true
340+
341+
AmplitudeClient.shared.track(eventType: "onboard_completed")
342+
}
343+
344+
private func skipStepName(for step: Step) -> String {
345+
switch step {
346+
case .tutorialChoice:
347+
return "tutorial_choice"
348+
case .tutorialTrackSearch:
349+
return "tutorial_track_search"
350+
case .tutorialTrim:
351+
return "tutorial_trim"
352+
case .tutorialHome:
353+
return "tutorial_home"
354+
case .tutorialDiaryDetail:
355+
return "tutorial_diary_detail"
356+
case .tutorialNotification:
357+
return "tutorial_notification"
358+
case .policyAgreement, .nameSetup, .tagSetup, .tutorialFinal:
359+
return "unknown"
360+
}
361+
}
362+
324363
private func resolveErrorMessage(from error: Error) -> String {
325364
if let userError = error as? UserServiceError {
326365
return userError.errorDescription ?? "요청 처리에 실패했어요."

0 commit comments

Comments
 (0)