visual_reward 1.2.0
visual_reward: ^1.2.0 copied to clipboard
Tenqube Visual Reward SDK for Flutter. A reward-ad webview SDK built on flutter_inappwebview for Android and iOS.
Flutter #
Flutter InAppWebView 기반 리워드 광고 SDK입니다.
요구사항 #
- Flutter 3.38.1+
- Dart 3.10.0+
- iOS 14.0+
- Android minSdk 24
설치 #
pub.dev #
- https://pub.dev/packages/visual_reward
- visual_reward SDK 버전 정보는 위 페이지를 확인해주세요.
pubspec.yaml에 아래 의존성을 추가합니다.
dependencies:
# visual_reward SDK
visual_reward: ^1.2.0
# ⚠️ google_mobile_ads 는 사용하지 마세요. GMA 는 플랫폼별로 직접 링크합니다.
# 아래 「GMA SDK」 절을 참고하세요.
# Native Ads SDK 어댑터 (Rewarded/Interstitial). 코어와 같은 버전을 사용합니다.
visual_reward_ezoic: ^1.2.0
# Native Ads SDK 초기화용 공식 패키지 (git 으로만 배포됩니다).
ezoic_flutter_sdk:
git:
url: https://github.com/ezoic/flutter-sdk.git
ref: v1.5.0 # 반드시 고정하세요 (아래 참고)
ref를 생략하면flutter pub get시점의 최신 커밋을 받아 Native Ads SDK 버전이 앱마다 달라집니다. 어댑터는 1.5.0 에 맞춰 컴파일돼 있으므로ref: v1.5.0을 사용하고, 상위 버전이 필요하면 요청해주세요.
-
GMA SDK 는 플랫폼별로 직접 링크합니다. 아래 「GMA SDK」 절을 참고하세요.
-
iOS
Podfile에 정적 링크를 지정합니다. Native Ads SDK 가 의존하는 Google Mobile Ads / UMP가 정적 라이브러리라, 없으면pod install이transitive dependencies that include statically linked binaries오류로 실패합니다.# ios/Podfile platform :ios, '14.0' target 'Runner' do use_frameworks! :linkage => :static # 기존 use_frameworks! 를 이 줄로 교체 use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end
GMA SDK #
-
GMA 는 웹뷰 내부 광고(AdSense/GPT/IMA HTML5)의 수익화·측정 신호용입니다. 링크돼 있으면 SDK가 리워드 웹뷰를
registerWebView에 자동 등록합니다. -
google_mobile_ads플러그인은 사용할 수 없습니다. Native Ads SDK 를 함께 쓸 때 Android·iOS 양쪽 제약을 동시에 만족하는 버전이 없습니다.google_mobile_ads Android play-services-adsiOS Google-Mobile-Ads-SDK~ 5.3.1 23.6.0 ✅ ~ 11.13 ❌ 6.0.0 ~ 24.1.0 ~ ❌ 12.2 ~ ✅ - Android: GMA 24.0.0 에서
AdManagerAdRequest.Builder.addCustomTargeting(String, String)의 covariant 오버라이드가 제거돼, 이를 호출하는 Native Ads SDK 1.5.0 이 광고 로드 시NoSuchMethodError로 크래시합니다. → 23.6.0 이하 필요 - iOS: Native Ads SDK 가
Google-Mobile-Ads-SDK ~> 12.0을 요구해 11.x 로는pod install이 실패합니다. → 12.x 필요
- Android: GMA 24.0.0 에서
-
따라서 플랫폼별로 직접 링크합니다.
// android/app/build.gradle.kts dependencies { // ⚠️ 24.x 이상으로 올리지 마세요 (위 사유) implementation("com.google.android.gms:play-services-ads:23.6.0") }iOS 는 Native Ads SDK 가 GMA 12.x 를 가져오므로 추가 선언이 필요 없습니다.
-
Native Ads SDK(
visual_reward_ezoic)를 쓰지 않는다면 위 Android 제약은 적용되지 않습니다.
App ID #
- 호스트 앱에 AdMob/Ad Manager App ID가 필요합니다. ID가 없는 경우 요청주시기 바랍니다.
- App ID가 누락되면 앱 시작 시 크래시가 발생합니다.
-
AndroidManifest.xml
<application> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" /> <!-- 웹뷰 내부 광고만 서빙할 때 추가. 자체 광고(Rewarded/Interstitial)도 서빙하면 이 메타데이터는 넣지 않습니다 --> <meta-data android:name="com.google.android.gms.ads.INTEGRATION_MANAGER" android:value="webview" /> </application> -
Info.plist
<key>GADApplicationIdentifier</key> <string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</string> <!-- 웹뷰 내부 광고만 서빙할 때 추가. 자체 광고(Rewarded/Interstitial)도 서빙하면 이 키는 넣지 않습니다 --> <key>GADIntegrationManager</key> <string>webview</string> <!-- IDFA/ATT 사용 목적 문자열 --> <key>NSUserTrackingUsageDescription</key> <string>맞춤형 광고 제공을 위해 기기 식별자를 사용합니다.</string> <!-- SKAdNetwork (Google 제공 식별자) --> <key>SKAdNetworkItems</key> <array><!-- ... --></array>ATT 권한 요청은 SDK가 웹뷰 로드 전 네이티브
ATTrackingManager로 자동 처리합니다. 이미 권한이 결정된 경우 팝업 없이 즉시 진행하며,NSUserTrackingUsageDescription이 없으면 요청을 스킵하고 바로 로드합니다.
app-ads.txt #
- 앱 스토어 등록정보의 개발자 웹사이트 도메인 루트 app-ads.txt에 전달받은 목록을 그대로 복사하여 게시해주세요.
시퀀스 다이어그램 #
sequenceDiagram
title interaction VisualReward SDK
participant A as Host App
participant B as VisualReward SDK
participant C as VisualReward 서버
participant D as WebView 화면
A->>B: initialize(appKey, ...)
B->>C: config 요청
C-->>B: config 응답
B-->>A: onSuccess()
A->>B: setUserId(userId)
A->>B: open(serviceName, ...)
B->>D: 웹뷰 열기
D->>B: 웹뷰 닫힘
B-->>A: onClose
사용법 #
디버그 설정 #
-
initialize()전에 호출합니다.import 'package:visual_reward/visual_reward.dart'; VisualReward.setDebugEnabled(kDebugMode);
초기화 #
-
main()또는 앱 시작 시 한 번만 호출합니다. -
어댑터 등록 코드는 없습니다 —
visual_reward_ezoic을 의존성에 추가하면 SDK가 자동으로 인식합니다.import 'package:ezoic_flutter_sdk/ezoic_flutter_sdk.dart'; import 'package:flutter/widgets.dart'; import 'package:visual_reward/visual_reward.dart'; // visual_reward_ezoic 은 import 하지 않습니다 — 의존성에 추가하기만 하면 자동 등록됩니다. // 준비 상태 플래그 (예: 앱 전역에서 보관) var adsReady = false; var rewardReady = false; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // GMA 는 명시적 초기화가 필요 없습니다. // - Android: MobileAdsInitProvider 가 앱 시작 시 자동 초기화합니다 // (App ID 는 AndroidManifest 메타데이터로 지정) // - 양 플랫폼 모두 Native Ads SDK 가 initialize 중 GMA 를 초기화합니다 // 1) Native Ads SDK 초기화 // ⚠️ 이 SDK는 초기화 중 ATT 프롬프트를 기본으로 표시합니다(requestATTBeforeAds 기본 true). // 앱이 자체 ATT 플로우로 시점을 통제하려면 requestATTBeforeAds: false 를 주세요. try { await EzoicAds.initialize(const EzoicConfiguration( domain: 'example.com', // 발급된 domain debugEnabled: false, testMode: false, )); adsReady = true; } catch (_) { /* Native Ads SDK 초기화 실패 */ } // 2) VisualReward SDK 초기화 VisualReward.initialize( appKey: 'tq_live_xxx', // 발급된 app_key timeout: const Duration(seconds: 5), // 기본값 onSuccess: () => rewardReady = true, onFailure: (e) { /* 초기화 실패 */ }, ); runApp(const MyApp()); } -
VisualReward.initialize파라미터파라미터 타입 필수 설명 기본값 appKeyString✅ stage별 app_key 발급 요청이 필요합니다. timeoutDuration초기화 완료 대기 시간. 초과 시 VisualRewardInitializeTimeoutError발생Duration(seconds: 5)onSuccessVoidCallback?초기화 성공 콜백 nullonFailurevoid Function(VisualRewardError)?초기화 실패 콜백 null -
EzoicConfiguration파라미터파라미터 타입 필수 설명 기본값 domainString✅ 도메인 값 발급 요청 필요합니다. autoReadConsentbool설치된 CMP의 동의 값 자동 사용 truesubjectToCOPPAboolCOPPA 대상 여부 falserequestATTBeforeAdsbool초기화 중 ATT 프롬프트 자동 표시 (iOS 전용) truedebugEnabledbool네이티브 광고 디버그 활성화 falsetestModebool테스트 광고 노출 여부. 실제 광고가 아닌 테스트용 광고 사용됩니다. falseiOS ATT 프롬프트가 두 곳에서 요청됩니다. Native Ads SDK는
initialize중(requestATTBeforeAds기본true), Visual Reward SDK는 웹뷰 로드 직전에 각각 요청합니다. 먼저 실행된 쪽이 팝업을 띄우고 나머지는 이미 결정된 상태를 보고 통과하므로 팝업이 두 번 뜨지는 않습니다.Rewarded / Interstitial 광고는 웹 페이지가 요청합니다. 호스트가 광고를 직접 로드·표시하는 코드는 필요하지 않습니다.
사용자 설정 #
-
로그인 후 또는 사용자가 변경될 때 호출합니다.
open()전에 반드시 호출해야 합니다.VisualReward.setUserId(user.id);
웹뷰 열기 #
-
initialize()후에 호출합니다.import 'package:visual_reward/visual_reward.dart'; VisualReward.open( context: context, serviceName: 'reward_home', locale: 'ko', extra: null, onClose: () { /* 웹뷰 닫힘 */ }, onError: (e) { /* 열기 실패 */ }, ); -
open 파라미터
파라미터 타입 필수 설명 기본값 contextBuildContext✅ 화면을 열 BuildContext serviceNameString✅ 사용 서비스별 serviceName 전달 예정입니다. localeString?BCP 47 (RFC 5646) 언어 서브태그(ISO 639-1, 2자리 소문자)를 사용 "ko"extraString?포스트백 추가 데이터 (JSON serialize된 문자열) nullonCloseVoidCallback?웹뷰가 닫힐 때 호출되는 콜백 nullonErrorvoid Function(VisualRewardError)?에러 발생 시 호출되는 콜백 null
AdPopcorn 브릿지 등록 #
-
AdPopcorn SDK(
adpopcornssp_flutter)를 사용하는 경우open호출 전에 등록합니다.import 'package:adpopcornssp_flutter/adpopcornssp_flutter.dart'; VisualReward.setAdPopcornBridgeHandler(AdPopcornInAppWebViewBridgeHandler());
에러 처리 #
에러 타입 #
| 타입 | 설명 |
|---|---|
VisualRewardNetworkError |
네트워크 연결 실패 |
VisualRewardInitializeTimeoutError |
initialize() 호출 후 완료까지 타임아웃 |
VisualRewardInvalidAppKeyError |
유효하지 않은 앱 키 (404) |
VisualRewardServerError(statusCode) |
서버 오류 (5xx) |
VisualRewardParseError |
응답 파싱 실패 |
VisualRewardNotInitializedError |
initialize() 호출 없이 open() 호출 |
VisualRewardUserIdNotSetError |
setUserId() 호출 없이 open() 호출 |
initialize 에러 처리 #
-
onFailure콜백에서 에러 타입을 분기합니다.VisualReward.initialize( appKey: 'tq_live_xxx', timeout: const Duration(seconds: 5), onSuccess: () { /* SDK 준비 완료 */ }, onFailure: (e) { if (e is VisualRewardNetworkError) { // 네트워크 연결 실패 } else if (e is VisualRewardInitializeTimeoutError) { // 초기화 타임아웃 } else if (e is VisualRewardInvalidAppKeyError) { // 존재하지 않는 앱 키 } else if (e is VisualRewardServerError) { // 서버 오류: e.statusCode } else if (e is VisualRewardParseError) { // 응답 파싱 실패 } }, );
open 에러 처리 #
-
open()은 예외를 throw하지 않고onError콜백으로 에러를 전달합니다. 콜백에서 에러 타입을 분기합니다.VisualReward.open( context: context, serviceName: 'reward_home', locale: 'ko', extra: null, onError: (e) { if (e is VisualRewardNotInitializedError) { // initialize() 미호출 } else if (e is VisualRewardUserIdNotSetError) { // setUserId() 미호출 } else if (e is VisualRewardNetworkError) { // 네트워크 연결 실패 } else if (e is VisualRewardServerError) { // 서버 오류: e.statusCode } }, );