# Flutter 앱에 TNK 팩토리 SDK 통합하기
## 개요
Flutter 앱에 TNK 팩토리 SDK를 통합하여 오퍼월 광고를 통해 수익화하는 방법을 정리한 실전 가이드입니다. 실제 프로젝트에서 겪은 문제들과 해결 방법을 포함하여 다른 개발자들이 참고할 수 있도록 작성했습니다.
## TNK 팩토리 SDK란?
TNK 팩토리 SDK는 사용자가 다양한 미션을 수행하고 보상을 받는 오퍼월 방식의 광고 플랫폼입니다. 전통적인 보상형 광고와 달리 사용자가 앱 설치, 회원가입, 설문조사 등의 미션을 완료하면 포인트를 지급받는 구조입니다.
## 1. Android 네이티브 설정
### 1.1 Gradle 의존성 추가
먼저 `android/settings.gradle.kts`에 TNK 저장소를 추가해야 합니다:
```kotlin
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url = uri("https://repository.tnkad.net:8443/repository/public/") }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
maven { url = uri("https://repository.tnkad.net:8443/repository/public/") }
// 기타 필요한 저장소들...
}
}
```
그리고 `android/app/build.gradle.kts`에 TNK SDK 의존성을 추가합니다:
```kotlin
dependencies {
implementation("com.tnkfactory:rwd:7.31.3")
}
```
### 1.2 AndroidManifest.xml 설정
필요한 권한과 메타데이터를 추가합니다:
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<application>
<!-- TNK 팩토리 앱 ID -->
<meta-data
android:name="tnkad_app_id"
android:value="YOUR_TNK_APP_ID"/>
<!-- TNK 팩토리 앱 키 -->
<meta-data
android:name="tnkad_app_key"
android:value="YOUR_TNK_APP_KEY"/>
<!-- TNK 팩토리 AdWallActivity -->
<activity
android:name="com.tnkfactory.ad.AdWallActivity"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
</application>
```
### 1.3 MainActivity.kt 구현
Method Channel을 통해 Flutter와 네이티브 코드를 연결합니다:
```kotlin
package com.yourpackage.yourapp
import android.os.Bundle
import android.util.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.tnkfactory.ad.TnkSession
class MainActivity : FlutterActivity() {
private val CHANNEL = "tnk_factory_channel"
private var methodChannel: MethodChannel? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
methodChannel?.setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> initializeTnkSdk(result)
"showRewardedAd" -> showRewardedAd(result)
"canShowAd" -> canShowAd(result)
else -> result.notImplemented()
}
}
}
private fun initializeTnkSdk(result: MethodChannel.Result) {
try {
Log.d("TNK_SDK", "TNK 팩토리 SDK 초기화 시작...")
// TnkSession 초기화
TnkSession.initInstance(this)
// 사용자 이름 설정
TnkSession.setUserName(this, "user_${System.currentTimeMillis()}")
Log.d("TNK_SDK", "TNK 팩토리 SDK 초기화 성공")
result.success(true)
} catch (e: Exception) {
Log.e("TNK_SDK", "TNK SDK 초기화 실패: ${e.message}")
result.error("INIT_ERROR", "TNK SDK 초기화 실패: ${e.message}", null)
}
}
private fun showRewardedAd(result: MethodChannel.Result) {
try {
Log.d("TNK_SDK", "TNK 팩토리 광고 표시 시작...")
// TNK 오퍼월 표시
TnkSession.showAdList(this)
Log.d("TNK_SDK", "TNK 팩토리 광고 표시 성공")
result.success(true)
} catch (e: Exception) {
Log.e("TNK_SDK", "TNK SDK 광고 표시 오류: ${e.message}")
result.error("SHOW_ERROR", "광고 표시 오류: ${e.message}", null)
}
}
private fun canShowAd(result: MethodChannel.Result) {
try {
// TNK SDK는 항상 광고를 표시할 수 있음 (오퍼월 방식)
result.success(true)
} catch (e: Exception) {
Log.e("TNK_SDK", "TNK 광고 표시 가능 여부 확인 오류: ${e.message}")
result.success(false)
}
}
}
```
## 2. Flutter 서비스 레이어
### 2.1 TnkFactoryService 구현
Flutter에서 TNK SDK를 사용하기 위한 서비스 클래스를 만듭니다:
```dart
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
class TnkFactoryService {
static const MethodChannel _channel = MethodChannel('tnk_factory_channel');
static final TnkFactoryService _instance = TnkFactoryService._internal();
factory TnkFactoryService() => _instance;
TnkFactoryService._internal();
bool _isInitialized = false;
/// TNK 팩토리 SDK 초기화
Future<bool> initialize() async {
try {
debugPrint('TNK 팩토리 SDK 초기화 시작...');
final result = await _channel.invokeMethod('initialize');
if (result == true) {
_isInitialized = true;
debugPrint('TNK 팩토리 SDK 초기화 성공');
return true;
} else {
debugPrint('TNK 팩토리 SDK 초기화 실패');
return false;
}
} catch (e) {
debugPrint('TNK 팩토리 SDK 초기화 오류: $e');
return false;
}
}
/// 보상형 광고 표시
Future<bool> showRewardedAd() async {
try {
debugPrint('TNK 팩토리 광고 표시 시작...');
final success = await _channel.invokeMethod('showRewardedAd');
if (success) {
debugPrint('TNK 팩토리 광고 표시 성공');
return true;
} else {
debugPrint('TNK 팩토리 광고 표시 실패');
return false;
}
} catch (e) {
debugPrint('TNK 팩토리 광고 표시 오류: $e');
return false;
}
}
/// 광고 표시 가능 여부 확인
Future<bool> canShowAd() async {
try {
final result = await _channel.invokeMethod('canShowAd');
return result == true;
} catch (e) {
debugPrint('TNK 광고 표시 가능 여부 확인 오류: $e');
return false;
}
}
}
```
### 2.2 UI에서 사용
UI에서 TNK 오퍼월을 호출하는 방법입니다:
```dart
Future<void> _showTnkOfferwall() async {
// 로딩 다이얼로그 표시
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('광고를 불러오는 중...'),
],
),
),
);
// TNK 팩토리 광고 표시
final success = await _tnkService.showRewardedAd();
// 로딩 다이얼로그 닫기
if (mounted) {
Navigator.pop(context);
}
```
## 3. Firebase Functions 서버사이드 처리
### 3.1 TNK 콜백 함수
TNK에서 광고 완료 시 호출되는 콜백 함수를 구현합니다:
```javascript
const { onRequest } = require("firebase-functions/v2/https");
const admin = require("firebase-admin");
admin.initializeApp();
// TNK 팩토리 콜백 함수
exports.tnkCallback = onRequest(async (req, res) => {
console.log("TNK 팩토리 콜백 수신됨");
console.log("요청 데이터:", req.body);
try {
// TNK에서 보낸 데이터 추출
const {
pay_pnt: payPnt, // 지급할 포인트
md_user_nm: mdUserName, // 사용자 식별자
seq_id: seqId, // 고유 거래 ID
md_chk: mdChk, // 보안 검증 코드
pay_dt: payDt, // 지급 시간
app_nm: appName, // 광고명
pay_amt: payAmt, // 정산 금액
actn_id: actionId, // 액션 타입
} = req.body;
// payPnt를 숫자로 변환
const pointsToAdd = parseInt(payPnt, 10);
// 필수 데이터 검증
if (!payPnt || !mdUserName || !seqId || !mdChk || isNaN(pointsToAdd)) {
console.error("TNK 콜백 필수 데이터 누락 또는 잘못된 포인트 값");
return res
.status(400)
.json({ error: "필수 데이터 누락 또는 잘못된 포인트 값" });
}
// TNK App Key (실제 값으로 변경 필요)
const TNK_APP_KEY = "YOUR_TNK_APP_KEY";
// 보안 검증 (mdChk = MD5(AppKey + mdUserName + seqId))
const crypto = require("crypto");
const expectedMdChk = crypto
.createHash("md5")
.update(TNK_APP_KEY + mdUserName + seqId)
.digest("hex");
if (mdChk !== expectedMdChk) {
console.error("TNK 콜백 보안 검증 실패");
return res.status(400).json({ error: "보안 검증 실패" });
}
// 중복 처리 방지 (seqId로 확인)
const db = admin.firestore();
const callbackRef = db.collection("tnk_callbacks").doc(seqId);
const callbackDoc = await callbackRef.get();
if (callbackDoc.exists) {
console.log("TNK 콜백 중복 처리 방지:", seqId);
return res.status(200).json({ message: "이미 처리된 요청" });
}
// 사용자 조회
const usersRef = db.collection("users");
const userQuery = await usersRef.where("uid", "==", mdUserName).get();
if (userQuery.empty) {
console.error("TNK 사용자 찾을 수 없음:", mdUserName);
return res.status(404).json({ error: "사용자를 찾을 수 없음" });
}
const userDoc = userQuery.docs[0];
const userId = userDoc.id;
const userData = userDoc.data();
const currentPoints = userData.points || 0;
## 4. 자주 발생하는 문제와 해결 방법
### 4.1 Gradle 저장소 충돌
**문제**: `Build was configured to prefer settings repositories over project repositories`
**해결**: 모든 저장소 설정을 `settings.gradle.kts`로 이동하고 `repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)` 설정
### 4.2 TNK SDK를 찾을 수 없음
**문제**: `Could not find com.tnkfactory:rwd:7.31.3`
**해결**: 올바른 Maven 저장소 URL 추가: `https://repository.tnkad.net:8443/repository/public/`
### 4.3 AdWallActivity를 찾을 수 없음
**문제**: `Unable to find explicit activity class {com.yourpackage.yourapp/com.tnkfactory.ad.AdWallActivity}`
**해결**: AndroidManifest.xml에 AdWallActivity 선언 추가
## 5. 주의사항
1. **실제 앱 ID/키 사용**: 테스트용 ID가 아닌 실제 TNK 앱 ID와 키 사용
2. **사용자 ID 매핑**: TNK 사용자 ID와 Firestore 사용자 UID 매핑 확인
3. **콜백 URL 설정**: TNK 콘솔에 올바른 콜백 URL 설정
---
**참고 자료**
- [TNK 팩토리 공식 문서](https://github.com/tnkfactory/tnk_sdk_rwd_br)
- [Firebase Functions 문서](https://firebase.google.com/docs/functions)
- [Flutter Method Channel 문서](https://docs.flutter.dev/development/platform-integration/platform-channels)
ps. pubdev에 있는 tnk_flutter_rwd 0.5.9 패키지는....적용 잘 안되었음.
'문제해결' 카테고리의 다른 글
| 구글 로그인 관련 앱 서명 오류(PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null) (0) | 2025.09.08 |
|---|---|
| clang: error: unsupported option '-G' for target 'arm64-apple-ios13-simulator'오류 (0) | 2025.06.30 |
| VS Code에서 Inlay Hints 끄는 방법 (0) | 2025.05.02 |
| jdk 경로 설정 이슈 (0) | 2025.03.15 |
| [Flutter 심화] 팀 프로젝트 - SNS 앱 TroubleShooting (0) | 2025.01.07 |
댓글