본문 바로가기
문제해결

Flutter 앱에 TNK 팩토리 SDK 통합하기

by 골목코딩 2025. 9. 21.

# 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 패키지는....적용 잘 안되었음.

댓글