visual_reward 1.3.0
visual_reward: ^1.3.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.24+
- Dart 3.5+
- iOS 13.0+
- Android minSdk 21 (Flutter 3.35+ 앱은 기본값이 24)
설치 #
pub.dev #
- https://pub.dev/packages/visual_reward
- visual_reward SDK 버전 정보는 위 페이지를 확인해주세요.
pubspec.yaml에 아래 의존성을 추가합니다.
dependencies:
# visual_reward SDK
visual_reward: ^1.2.0
# GMA SDK — 호스트가 지정한 버전을 그대로 사용합니다. SDK 는 버전을 강제하지 않습니다.
# 이 플러그인이 양 플랫폼에 GMA 를 링크하며, 별도 네이티브 설정(gradle·Podfile)은 필요 없습니다.
google_mobile_ads: ^9.0.0
광고 엔진(Rewarded/Interstitial)은 visual_reward 가 자체 보유하고 초기화까지 책임집니다. 호스트가 제공해야 하는 것은 위의 GMA SDK 와 AdMob App ID 뿐입니다 — 아래 「GMA SDK」 절을 참고하세요.
GMA 버전별 동작 #
| 호스트 GMA | 광고 기능 |
|---|---|
google_mobile_ads 5.3.x 이상 |
정상 동작 |
google_mobile_ads 5.3.x 미만 |
비활성 (로그로 안내, 크래시 없음) |
| 미링크 | 비활성 (로그로 안내, 크래시 없음) |
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" /> </application> -
Info.plist
<!-- App ID 누락 시 시작 크래시 (AdMob 또는 Ad Manager App ID) --> <key>GADApplicationIdentifier</key> <string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</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()또는 앱 시작 시 한 번만 호출합니다.import 'package:flutter/widgets.dart'; import 'package:visual_reward/visual_reward.dart'; var rewardReady = false; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // 광고 엔진(GMA 초기화 포함)까지 이 호출이 함께 수행합니다. // 호스트는 MobileAds 를 직접 초기화하지 않습니다 — SDK 가 대신 수행하고 완료를 기다립니다. // 필요한 것은 App ID 뿐입니다. VisualReward.initialize( appKey: 'tq_live_xxx', // 발급된 app_key adTestMode: false, // true 면 테스트 광고만 노출 timeout: const Duration(seconds: 5), // 기본값 onSuccess: () => rewardReady = true, onFailure: (e) { /* 초기화 실패 */ }, ); runApp(const MyApp()); } -
VisualReward.initialize파라미터파라미터 타입 필수 설명 기본값 appKeyString✅ stage별 app_key 발급 요청이 필요합니다. adTestModebool테스트 광고 노출 여부. 광고 엔진 첫 초기화 시점의 값만 적용됩니다 falsetimeoutDuration초기화 완료 대기 시간. 초과 시 VisualRewardInitializeTimeoutError발생Duration(seconds: 5)onSuccessVoidCallback?초기화 성공 콜백 nullonFailurevoid Function(VisualRewardError)?초기화 실패 콜백 nullonSuccess/onFailure는 리워드 설정 로드 결과에 대한 콜백입니다. 광고 엔진 초기화는 백그라운드에서 진행되며 실패해도 리워드 기능은 정상 동작합니다(광고만 비활성). 광고 엔진 상태는VisualReward로그 태그로 확인할 수 있습니다.광고용 도메인은 앱이 지정하지 않습니다.
appKey로 조회하는 서버 설정의ezoic.adDomain값을 사용하며, 설정에 값이 없으면 광고 기능만 비활성됩니다.동의 관리(TCF v2 / GPP / US Privacy)는 SDK 가
SharedPreferences(Android) ·UserDefaults(iOS) 에서 자동으로 읽습니다. 디버그 로그는setDebugEnabled()값이 광고 엔진에도 함께 적용됩니다.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());
호스트 WebView 브릿지 #
호스트 앱이 직접 소유한 WebView 의 웹 페이지에서 리워드 웹뷰를 열 수 있습니다. 리워드 웹뷰(SDK 소유)에는 필요 없습니다 — 이미 자체 브릿지를 갖고 있습니다.
1. 네이티브: 브릿지 부착 #
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:visual_reward/visual_reward.dart';
class HostWebViewPage extends StatefulWidget {
const HostWebViewPage({super.key});
@override
State<HostWebViewPage> createState() => _HostWebViewPageState();
}
class _HostWebViewPageState extends State<HostWebViewPage> {
InAppWebViewController? _controller;
@override
void dispose() {
final controller = _controller;
if (controller != null) VisualReward.detachWebBridge(controller);
super.dispose();
}
@override
Widget build(BuildContext context) {
return InAppWebView(
initialUrlRequest: URLRequest(url: WebUri('https://your-page.example.com')),
onWebViewCreated: (controller) {
_controller = controller;
// 페이지 로드 이전에 부착합니다.
VisualReward.attachWebBridge(controller: controller, context: context);
},
);
}
}
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
controller |
InAppWebViewController |
✅ | 브릿지를 붙일 호스트 WebView 컨트롤러 |
context |
BuildContext |
✅ | 리워드 웹뷰를 열 BuildContext |
attachWebBridge는 페이지 로드 이전에 호출해야 합니다.flutter_inappwebview의 핸들러는 등록 이후에 만들어지는 문서에만 주입되므로, 이미 로드된 페이지에는 다시 로드하기 전까지 나타나지 않습니다.
detachWebBridge를 잊지 마세요. 핸들러가 컨트롤러에 남아 있으면 사라진 화면의BuildContext로 리워드 웹뷰를 열려고 시도합니다.
initialize()·setUserId()를 앱에서 부르지 않아도 됩니다. 웹에서appKey·userId를 넘기면 브릿지가 초기화 → 사용자 설정 → 웹뷰 열기를 한 번에 처리합니다.
2. 웹: 핸들러 호출 #
window.flutter_inappwebview.callHandler('visualRewardBridge', JSON.stringify({
appKey: 'app_key', // 필수 — 발급된 app_key
userId: 'u-123', // 필수 — 리워드 귀속 사용자
serviceName: 'anyService', // 필수 — 실행 serviceName(전달 예정)
locale: 'ko', // 선택 — 기본 "ko"
extra: '', // 선택 — 포스트백 추가 데이터(JSON serialize된 문자열)
adTestMode: false, // 선택 — true 면 테스트 광고만 노출. 기본 false
timeoutMs: 5000, // 선택 — 설정 조회 대기 한도(ms). 기본 5000
}));
객체를 그대로 넘겨도 됩니다 — callHandler('visualRewardBridge', { appKey: 'app_key', ... }).
| 필드 | 타입 | 필수 | 설명 | 기본값 |
|---|---|---|---|---|
appKey |
string |
✅ | 발급된 app_key | |
userId |
string |
✅ | 리워드 귀속 사용자 | |
serviceName |
string |
✅ | 실행 serviceName(전달 예정) | |
locale |
string |
BCP 47 언어 서브태그 | "ko" |
|
extra |
string |
포스트백 추가 데이터 (JSON serialize된 문자열) | "" |
|
adTestMode |
boolean |
테스트 광고 노출 여부 | false |
|
timeoutMs |
number |
설정 조회 대기 한도(ms). 초과 시 INITIALIZE_TIMEOUT |
5000 |
adTestMode는 광고 엔진이 처음 초기화될 때의 값만 적용됩니다. 앱에서 이미initialize()로 초기화했거나 앞선 브릿지 호출로 초기화된 뒤라면 이후 값은 무시됩니다.
응답 콜백은 페이지에서 전역 객체로 등록합니다.
window.visualRewardCallback = {
onClose: function () {}, // 리워드 웹뷰 닫힘
onError: function (code, message) {},
};
onError 의 code 는 네이티브 에러 타입에 대응합니다.
| code | 의미 |
|---|---|
NETWORK_ERROR |
설정 조회 네트워크 오류 |
INITIALIZE_TIMEOUT |
초기화 완료 대기 시간 초과 |
INVALID_APP_KEY |
존재하지 않는 app_key (404) |
SERVER_ERROR |
설정 서버 오류 (200/404 외 응답) |
PARSE_ERROR |
설정 응답 파싱 실패 |
NOT_INITIALIZED |
초기화 전 열기 시도 |
USER_ID_NOT_SET |
userId 없이 열기 시도 |
INVALID_PAYLOAD |
브릿지 자체 — 필수 키 누락·JSON 파싱 실패 |
브릿지가 주입됐는지 확인하고 호출하세요.
if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
window.flutter_inappwebview.callHandler('visualRewardBridge', JSON.stringify({ /* ... */ }));
}
3. 호출 가능한 페이지 — 허용 오리진 #
appKey 로 조회한 설정의 allowedDomains 에 호출한 페이지의 오리진이 포함돼야 실행됩니다. 검증에 실패하면 아무 것도 하지 않고 로그만 남깁니다(어떤 app_key가 어떤 오리진을 허용하는지 탐색할 수 있는 신호를 웹에 주지 않기 위해 콜백도 호출하지 않습니다).
- 오리진은 최상위 페이지 URL 로 판정합니다. 같은 오리진 iframe 은 통과합니다
- 검증을 통과하기 전에는 SDK 상태(설정·사용자·광고 엔진)를 바꾸지 않습니다
file://로 로드한 페이지는 오리진 호스트가 비어 있어 항상 거부됩니다
에러 처리 #
에러 타입 #
| 타입 | 설명 |
|---|---|
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 } }, );