설치
새 Flutter 앱에 SDK를 붙이는 전체 과정 — Git 의존성, Android Gradle과 iOS CocoaPods·SwiftPM 연결, 네이티브 시작 호출, Dart 시작 순서를 단계별로 설명합니다.
설치는 세 부분으로 나뉩니다. Dart 패키지, 플랫폼별 네이티브 SDK, 그리고 각각의 시작 호출입니다. 네이티브 쪽은 생략할 수 없습니다. 수집기 주소와 서비스 키가 네이티브 설정에 있고, iOS에서는 Flutter 플러그인이 SDK를 스스로 시작하지 못하기 때문입니다. 이 페이지는 flutter create로 만든 앱을 기준으로 처음부터 끝까지 따라갑니다.
빠른 시작
아래에서 수집기 URL, 프로젝트, 앱, 앱 키를 입력하고 탭(Android · iOS)을 선택하면 그 값으로 네이티브 설정 파일이 생성됩니다. Flutter SDK의 Dart 시작 호출은 인자를 받지 않으므로, 수집기 주소와 앱 키는 Dart 코드가 아니라 이 네이티브 파일에 들어갑니다. 고급 옵션은 "고급 옵션" 토글 뒤에 있습니다.
{
"sdk_config": {
"app_framework": "flutter",
"ingest": {
"collector_url": "https://in.sophonz.ai",
"service_key": "sk_tYtJUa4WweXXedj6lxjSqoPW5O0DnbXz",
"service_namespace": "sophonz"
}
}
}요구 사항
| 항목 | 요구 사항 |
|---|---|
| Flutter | 3.22 이상 |
| Dart | 3.4 이상. 이전 버전은 Git 의존성 안의 상대 path: 의존성을 해석하지 못합니다 |
| Android | 앱 모듈에 Sophonz Gradle 플러그인 1.0.0 적용, minSdk 21 이상(26 미만이면 core library desugaring 필요) |
| iOS | 13.0 이상, Sophonz Apple SDK(SophonzIO) 1.0.0 |
| 접근 권한 | sophonz-labs/sophonz-flutter-sdk와 sophonz-labs/sophonz-apple-sdk 읽기 권한, Android 아티팩트용 read:packages 권한이 있는 GitHub 토큰 |
Flutter SDK v0.1.0은 Sophonz Android SDK 1.0.0과 SophonzIO 1.0.0에 맞춰 빌드되었습니다. 아래에서 지정하는 네이티브 버전도 이와 같아야 합니다.
1. Dart 패키지 추가
패키지는 pub.dev에 없습니다. 릴리스마다 SDK 저장소에 Git 태그와 GitHub Release가 만들어지며, 앱은 이 태그에 의존합니다.
dependencies:
flutter:
sdk: flutter
sophonz:
git:
url: git@github.com:sophonz-labs/sophonz-flutter-sdk.git
path: sophonz
ref: v0.1.0flutter pub getsophonz는 같은 태그에서 sophonz_android, sophonz_ios, sophonz_platform_interface를 함께 가져옵니다. 패키지끼리 상대 경로로 의존하고, pub이 이 경로를 같은 체크아웃 안에서 해석하기 때문입니다. 태그가 가리킨 커밋은 pubspec.lock에 기록됩니다.
저장소 접근 권한
비공개 저장소이므로 flutter pub get을 실행하는 모든 곳에 읽기 권한이 필요합니다. 개발자 PC와 CI 모두 해당합니다.
- SSH — 위 URL은 SSH 키로 인증합니다.
ssh -T git@github.com으로 확인하세요. - HTTPS —
https://github.com/sophonz-labs/sophonz-flutter-sdk.git으로 적고 Git 자격 증명 도우미를 설정합니다(예:gh auth setup-git).
CI에서는 flutter pub get 전에 GitHub URL을 토큰 URL로 바꿔 둡니다. pubspec.yaml의 SSH URL과, 뒤에서 CocoaPods가 쓰는 HTTPS URL이 모두 해결됩니다.
- name: Git access to private SDK repositories
env:
GH_TOKEN: ${{ secrets.SOPHONZ_SDK_READ_TOKEN }}
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "git@github.com:"
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/" --add
- run: flutter pub get토큰에는 sophonz-flutter-sdk와 sophonz-apple-sdk 읽기 권한이 있어야 합니다.
sophonz_dio, sophonz_go_router 추가
같은 url과 ref에 각자의 path를 지정해 추가하고, sophonz 항목을 dependency_overrides에도 똑같이 적습니다.
dependencies:
sophonz:
git:
url: git@github.com:sophonz-labs/sophonz-flutter-sdk.git
path: sophonz
ref: v0.1.0
sophonz_dio:
git:
url: git@github.com:sophonz-labs/sophonz-flutter-sdk.git
path: sophonz_dio
ref: v0.1.0
sophonz_go_router:
git:
url: git@github.com:sophonz-labs/sophonz-flutter-sdk.git
path: sophonz_go_router
ref: v0.1.0
dependency_overrides:
sophonz:
git:
url: git@github.com:sophonz-labs/sophonz-flutter-sdk.git
path: sophonz
ref: v0.1.0NOTE — 오버라이드가 필요한 이유
sophonz_dio는 체크아웃 안의 상대 경로로 sophonz에 의존하고, pub은 이 의존성을 v0.1.0이라는 이름이 아니라 태그가 가리킨 커밋으로 고정합니다. 앱에 직접 적은 sophonz는 태그 이름을 쓰므로, pub은 한 패키지에 대한 서로 다른 Git 설명 두 개를 보고 sophonz_dio from git is forbidden으로 멈춥니다. 오버라이드는 둘 다 같은 설명을 쓰도록 정해 줍니다.
ErrorCode나 LastRunEndState를 쓰기 위해 sophonz_platform_interface를 직접 import한다면(API 레퍼런스 참고) path: sophonz_platform_interface로 dependencies와 dependency_overrides 양쪽에 같은 방식으로 추가합니다.
2. Android
GitHub Packages 토큰
Sophonz Android SDK와 Gradle 플러그인은 GitHub Packages로 배포되며, 읽기만 해도 토큰이 필요합니다. read:packages 권한이 있는 classic 개인 액세스 토큰을 사용하세요. fine-grained 토큰은 GitHub Packages에서 동작하지 않습니다.
gpr.user=your-github-username
gpr.key=ghp_...커밋되는 프로젝트의 android/gradle.properties에는 토큰을 넣지 마세요. CI에서는 빌드 전 단계에서 이 두 줄을 ~/.gradle/gradle.properties에 써 넣습니다.
CAUTION — 403과 404가 같은 오류로 보입니다
토큰이 없거나 read:packages 권한이 없으면 Gradle은 존재하지 않는 버전일 때와 똑같이 "Could not resolve io.sophonz:…"를 출력합니다. 버전을 바꾸기 전에 토큰부터 확인하세요.
저장소와 Gradle 플러그인
최근 Flutter 버전은 Android 프로젝트를 Kotlin DSL(settings.gradle.kts, build.gradle.kts, app/build.gradle.kts)로 생성합니다. 이전 버전으로 만든 프로젝트는 Groovy(.gradle)입니다. 파일에 맞는 탭을 따르세요.
저장소는 두 곳에 추가해야 합니다. 플러그인은 pluginManagement에서 찾고, sophonz_android 플러그인 모듈은 allprojects의 프로젝트 저장소에서 SDK를 찾습니다.
pluginManagement {
// ...flutter create가 생성한 flutterSdkPath와 includeBuild...
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven {
url = uri("https://maven.pkg.github.com/sophonz-labs/sophonz-android-sdk")
credentials {
username = providers.gradleProperty("gpr.user").orNull
password = providers.gradleProperty("gpr.key").orNull
}
}
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
// ...flutter create가 생성한 com.android.application, Kotlin 플러그인...
id("io.sophonz.gradle") version "1.0.0" apply false
}allprojects {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/sophonz-labs/sophonz-android-sdk")
credentials {
username = providers.gradleProperty("gpr.user").orNull
password = providers.gradleProperty("gpr.key").orNull
}
}
}
}plugins {
// ...flutter create가 생성한 com.android.application, Kotlin, dev.flutter.flutter-gradle-plugin...
id("io.sophonz.gradle")
}Gradle 플러그인은 빌드 시점에 sophonz-config.json을 읽어 SDK에 넣고, 앱에 SDK를 추가합니다. Flutter 플러그인은 SDK에 맞춰 컴파일만 할 뿐 SDK를 포함하지 않으므로, Gradle 플러그인이 없으면 런타임에 SDK가 없습니다.
CAUTION — Gradle 플러그인 버전을 Flutter 플러그인의 SDK 버전과 맞추세요
Gradle 플러그인은 자기 버전의 SDK를 앱에 넣습니다. v0.1.0에서는 1.0.0입니다. 버전이 다르면 Dart에서 호출한 메서드가 런타임 SDK에 없을 수 있습니다.
minSdk가 26 미만일 때
생성된 app/build.gradle.kts의 flutter.minSdkVersion은 26보다 낮을 수 있습니다. 이 경우 Android SDK는 core library desugaring, Android Gradle Plugin 8.3.0 이상, gradle.properties 플래그를 요구합니다. 빠진 것은 Gradle 플러그인이 구성 단계에서 알려 줍니다.
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}android.useFullClasspathForDexingTransform=truedefaultConfig에서 minSdk = 26으로 지정하면 세 가지 모두 필요 없습니다. Android 요구 사항 전체는 Android 설치 가이드에 있습니다.
sophonz-config.json
앱 모듈에 파일을 만듭니다. Gradle 플러그인이 빌드 중에 읽으므로, 내용을 바꾸면 다시 빌드해야 합니다.
{
"sdk_config": {
"app_framework": "flutter",
"ingest": {
"collector_url": "https://in.sophonz.ai",
"service_key": "sk_replace_me",
"service_namespace": "my-project",
"deployment_environment": "production"
}
}
}app_framework: flutter는 앱을 Flutter 앱으로 표시합니다. 모든 키는 설정에서 설명합니다.
Application.onCreate에서 SDK 시작
템플릿에는 Application 서브클래스가 없으므로 하나 추가합니다. 패키지는 app/build.gradle.kts의 namespace를 따릅니다.
package com.example.my_app
import android.app.Application
import io.sophonz.android.sophonzsdk.Sophonz
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
Sophonz.start(this)
}
}매니페스트에 등록합니다. 생성된 android:name="${applicationName}"을 바꿉니다.
<application
android:name=".MainApplication"
android:label="my_app"
android:icon="@mipmap/ic_launcher">이 단계를 건너뛰면 Dart에서 Sophonz.instance.start()를 호출할 때 Flutter 플러그인이 SDK를 시작합니다. 동작은 하지만 Flutter 엔진이 뜬 뒤라서 앱 시작 시간은 측정되지 않습니다.
3. iOS
Apple SDK 추가
Flutter는 기본적으로 CocoaPods를 씁니다. sophonz_ios가 의존하는 SophonzIO pod는 CocoaPods trunk에 없으므로 Runner 타깃에 Git 소스를 지정합니다.
platform :ios, '13.0'
target 'Runner' do
pod 'SophonzIO', :git => 'https://github.com/sophonz-labs/sophonz-apple-sdk.git', :tag => 'v1.0.0'
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end생성된 Podfile의 나머지는 그대로 둡니다. 이어서 실행합니다.
cd ios && pod installsophonz-apple-sdk도 비공개 저장소이므로 CocoaPods와 Xcode에 GitHub 인증이 필요합니다. 키체인에 HTTPS 자격 증명을 저장하거나, SSH 키를 쓰면서 Podfile 소스를 git@github.com:sophonz-labs/sophonz-apple-sdk.git으로 적습니다. CI는 위의 URL 치환으로 둘 다 해결됩니다.
Sophonz-Info.plist
ios/Sophonz-Info.plist를 만들고 Xcode에서 Runner 타깃에 추가합니다(File > Add Files to "Runner", Runner 타깃 체크). 파일이 앱 번들에 들어가야 하며, 폴더에만 있는 파일은 읽히지 않습니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CollectorURL</key>
<string>https://in.sophonz.ai</string>
<key>AppKey</key>
<string>sk_replace_me</string>
<key>Project</key>
<string>my-project</string>
<key>DeploymentEnvironment</key>
<string>production</string>
</dict>
</plist>값은 그대로 적습니다. 리소스로 복사되는 plist에서는 Xcode가 $(...) 빌드 설정을 치환하지 않고, SDK는 치환되지 않은 값을 없는 것으로 봅니다. 모든 키는 설정에 있습니다.
AppDelegate에서 SDK 시작
Apple SDK는 네이티브 코드의 메인 스레드에서, Flutter 엔진이 실행되기 전에 시작해야 합니다. Flutter 플러그인은 이미 실행 중인 SDK에 붙기만 하므로, 아무도 시작하지 않으면 iOS에서의 Dart 호출은 전부 조용히 무시됩니다.
생성된 AppDelegate에 init()을 추가합니다. 나머지는 생성된 그대로 둡니다. 새 템플릿은 didInitializeImplicitFlutterEngine에서, 이전 템플릿은 application(_:didFinishLaunchingWithOptions:)에서 플러그인을 등록합니다.
import Flutter
import UIKit
import SophonzIO
@main
@objc class AppDelegate: FlutterAppDelegate {
override init() {
super.init()
// Sophonz-Info.plist를 읽습니다. CollectorURL이나 AppKey가 없거나 비었거나 $(...) 그대로면 nil입니다.
guard let options = Sophonz.Options.fromPlist(platform: .flutter) else {
print("[sophonz] Sophonz-Info.plist is not configured; SDK not started")
return
}
do {
try Sophonz.start(options: options)
} catch {
// 시작에 실패해도 앱 실행을 막으면 안 됩니다.
print("[sophonz] failed to start: \(error)")
}
}
// ...flutter create가 생성한 플러그인 등록 코드...
}didFinishLaunching이 아니라 init()에서 시작해야 SDK가 앱 시작 과정을 관찰할 수 있습니다. platform: .flutter는 앱을 Flutter 앱으로 표시합니다. 푸시 알림 수집 등 다른 시작 옵션은 설정에 있습니다.
4. Dart에서 SDK 시작
main의 가장 앞에서 start를 호출하고, 앱을 action으로 넘겨 처리되지 않은 오류가 수집되게 합니다.
import 'package:flutter/material.dart';
import 'package:sophonz/sophonz.dart';
Future<void> main() async {
await Sophonz.instance.start(action: () => runApp(const MyApp()));
}start는 다음 순서로 동작합니다.
WidgetsFlutterBinding.ensureInitialized()를 호출하고 Dart 시작 시각을 기록합니다.addSpanExporter,addLogRecordExporter로 추가한 익스포터를 전달합니다(Android 전용).- 네이티브 SDK에 연결합니다. Android에서는
Application.onCreate()가 시작하지 않았다면 여기서 시작합니다. - 프레임, UI 아이솔레이트 멈춤, 수명 주기 모니터링을 시작합니다.
action이 있으면 오류 핸들러를 설치하고action을 실행한 뒤 첫 프레임까지의 시간을 기록합니다.
설정 파일 읽기처럼 runApp 전에 해야 하는 일은 action 안에 넣습니다.
Future<void> main() async {
await Sophonz.instance.start(action: () async {
final settings = await AppSettings.load();
runApp(MyApp(settings: settings));
});
}start가 연결을 마치기 전에 Sophonz.instance로 한 호출은 쌓아 두지 않고 버려집니다.
라우트와 HTTP
내비게이션 옵저버를 추가하면 라우트가 뷰와 화면 로드 스팬으로 기록됩니다.
MaterialApp(
navigatorObservers: [SophonzNavigationObserver()],
home: const HomePage(),
);HTTP는 http.Client 대신 SophonzHttpClient를 쓰거나, Dio 인스턴스에 sophonz_dio의 SophonzInterceptor()를 추가합니다.
final client = SophonzHttpClient();
final response = await client.get(Uri.parse('https://api.example.com/items'));5. 확인
- 디버그 모드로 실행하고 콘솔에서
Sophonz Flutter SDK attached to host SDK successfully.를 찾습니다. - 대신
The Sophonz SDK was not started in native code가 보이면 네이티브 시작 호출이 빠진 것입니다. Android는 플러그인이 SDK를 시작하지만 앱 시작 시간은 측정되지 않습니다. iOS는 AppDelegate에Sophonz.start(options:)를 넣기 전까지 아무것도 기록되지 않습니다. - iOS에서는 앱을 백그라운드로 보냅니다. Apple SDK는 타이머가 아니라 세션 구간이 끝날 때 업로드하므로, 포그라운드에 있는 동안에는 아직 아무것도 보내지 않은 상태입니다.
- Sophonz에서 세션을 찾습니다. 나타나지 않으면 먼저 서비스 키를 확인하세요. 키가 없거나 등록되지 않았으면 수집기는 오류 없이 텔레메트리를 버립니다.
다음 단계
TIP — 이제 시작입니다
이제 앱을 배포하세요. Sophonz가 실사용자가 겪는 문제를 포착하고, 당신의 앱은 스스로 진화합니다.