visual_reward 1.1.2
visual_reward: ^1.1.2 copied to clipboard
Tenqube Visual Reward SDK for Flutter — flutter_inappwebview 기반 리워드 광고 웹뷰 SDK.
Flutter #
Flutter InAppWebView 기반 리워드 광고 SDK입니다.
요구사항 #
- Flutter 3.16.0+
- Dart 3.0.0+
- iOS 12.0+
- Android minSdk 21
설치 #
전달받은 visual_reward 폴더를 프로젝트 내 원하는 위치에 복사한 뒤, pubspec.yaml에 아래 의존성을 추가합니다.
dependencies:
visual_reward:
path: ./visual_reward # 복사한 폴더 경로
flutter_inappwebview: ^6.1.0
url_launcher: ^6.2.0
device_info_plus: ^10.0.0
connectivity_plus: ^6.0.0
package_info_plus: ^8.0.0
advertising_id: ^2.1.0
app_tracking_transparency: ^2.0.4
app_settings: ^5.1.1
path_provider: ^2.1.0
visual_reward를 path 의존성으로 추가하면 위 패키지들은 전이 의존성으로 자동 해석되므로, 호스트pubspec.yaml에 별도로 나열하지 않아도 됩니다. (버전 참고용)
R8 / ProGuard #
Android 빌드에서 minifyEnabled = true를 사용하는 경우, android/app/proguard-rules.pro에 아래 규칙을 추가합니다.
# flutter_inappwebview
-keep class com.pichillilorenzo.flutter_inappwebview.** { *; }
# WebView JavascriptInterface
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
권한 #
Android #
AndroidManifest.xml에 광고 ID 접근 권한을 추가합니다.
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
iOS #
IDFA 조회(getIDFA)를 사용하려면 Info.plist에 ATT 사용 목적 문자열을 추가해야 합니다.
<key>NSUserTrackingUsageDescription</key>
<string>맞춤형 광고 제공을 위해 기기 식별자를 사용합니다.</string>
ATT 권한 요청(
AppTrackingTransparency.requestTrackingAuthorization)은 SDK가 웹뷰 로드 전 자동으로 처리합니다. 이미 권한이 결정된 경우 팝업 없이 즉시 진행됩니다.NSUserTrackingUsageDescription이 없으면 요청을 스킵하고 바로 로드합니다.
사용법 #
디버그 설정 #
initialize() 전에 호출합니다.
import 'package:visual_reward/visual_reward.dart';
VisualReward.setDebugEnabled(kDebugMode);
초기화 #
main() 또는 앱 시작 시 한 번만 호출합니다.
import 'package:visual_reward/visual_reward.dart';
VisualReward.initialize(
appKey: 'tq_live_xxx',
timeout: const Duration(seconds: 5), // 기본값
onSuccess: () { /* SDK 준비 완료 */ },
onFailure: (e) { /* 초기화 실패 */ },
);
initialize 파라미터 #
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
appKey |
String |
✅ | 앱 식별 키 | |
timeout |
Duration |
Duration(seconds: 5) |
초기화 완료 대기 시간. 초과 시 VisualRewardInitializeTimeoutError 발생 |
|
onSuccess |
VoidCallback? |
null |
초기화 성공 콜백 (선택) | |
onFailure |
void Function(VisualRewardError)? |
null |
초기화 실패 콜백 (선택) |
사용자 설정 #
로그인 후 또는 사용자가 변경될 때 호출합니다. open() 전에 반드시 호출해야 합니다.
VisualReward.setUserId(user.id);
웹뷰 열기 #
import 'package:visual_reward/visual_reward.dart';
VisualReward.open(
context: context,
serviceName: 'reward_home',
locale: 'ko',
extra: null,
onClose: () { /* 웹뷰 닫힘 */ },
onError: (e) { /* 열기 실패 */ },
);
open 파라미터 #
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
context |
BuildContext |
✅ | 화면을 열 BuildContext | |
serviceName |
String |
✅ | 서비스 이름 | |
locale |
String? |
'ko' |
언어 코드 | |
extra |
String? |
null |
추가 query parameter (선택) | |
onClose |
VoidCallback? |
null |
웹뷰가 닫힐 때 호출되는 콜백 (선택) | |
onError |
void Function(VisualRewardError)? |
null |
웹뷰를 열지 못했을 때 호출되는 콜백 (선택) |
AdPopcorn 브릿지 등록 (선택) #
AdPopcorn SDK(adpopcornssp_flutter: ^1.0.17)가 있는 경우, open 호출 전에 등록합니다.
import 'package:adpopcornssp_flutter/adpopcornssp_flutter.dart';
VisualReward.setAdPopcornBridgeHandler(AdPopcornInAppWebViewBridgeHandler());
VisualReward.open(
context: context,
serviceName: 'reward_home',
locale: 'ko',
extra: null,
);
AdPopcorn SDK가 없으면 호출하지 않아도 됩니다.
AdMob WebView API (선택, best-effort) #
호스트 앱에 Google Mobile Ads SDK가 링크돼 있으면, SDK가 리워드 웹뷰를 자동으로 registerWebView에 등록해 WebView 내부 광고(AdSense/GPT/IMA HTML5)의 수익화·측정 신호를 개선합니다. GMA SDK가 없으면 조용히 무시되며, 광고 표시·보상 로직에는 영향이 없습니다.
- Android:
com.google.android.gms:play-services-ads또는 Next-Gen SDK - iOS:
Google-Mobile-Ads-SDK(9.6.0+) — 호스트가 GMA 심볼을 참조하도록 링크
⚠️ Flutter는
flutter_inappwebview가 내부 WebView 인스턴스를 노출하지 않아, 네이티브 뷰 계층에서 WebView를 탐색해 등록하는 best-effort 방식입니다. 등록 여부는 네이티브 로그(AdMob registerWebView registered/skipped/not linked)로 확인하세요. 플러그인 버전에 따라 탐색이 실패할 수 있으므로, 실제 기기에서 검증이 필요합니다.
에러 처리 #
에러 타입 #
| 에러 타입 | 설명 |
|---|---|
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
}
},
);
SDK 동작 #
아래 동작은 모두 VisualReward.open으로 열린 웹뷰 화면에만 적용됩니다. 호스트 앱의 다른 화면이나 웹뷰에는 영향을 주지 않습니다.
가로 모드 차단 #
웹뷰 화면은 항상 세로 모드로 고정됩니다. 화면을 닫으면 기존 방향 설정이 자동으로 복원됩니다.
영상 광고 자동재생 #
사용자 제스처 없이 미디어(영상 광고)가 자동으로 인라인 재생됩니다.
javaScriptEnabled: true— JavaScript 활성화domStorageEnabled: true— DOM Storage 활성화mediaPlaybackRequiresUserGesture: false— 제스처 없이 자동재생 허용allowsInlineMediaPlayback: true(iOS) — 전체화면 강제 전환 없이 인라인 재생allowsPictureInPictureMediaPlayback: false(iOS) — PIP 비활성화
ATT 권한 요청 (iOS) #
웹뷰 로드 전 AppTrackingTransparency.requestTrackingAuthorization을 자동으로 요청합니다. 이미 권한이 결정된 경우 팝업 없이 즉시 진행됩니다. NSUserTrackingUsageDescription이 없으면 요청을 스킵하고 바로 로드합니다.
스와이프 백 (iOS) #
웹뷰 내 이전 페이지로 돌아가는 스와이프 백 제스처가 기본으로 활성화되어 있습니다. swipeBackEnabled JS 핸들러로 웹에서 활성화 여부를 제어할 수 있습니다.
Mixed Content 허용 (Android) #
HTTPS 페이지 내 HTTP 리소스 로드를 허용합니다. Android 5.0 이상에서 기본 차단되는 mixed content를 광고 크리에이티브가 정상적으로 로드될 수 있도록 허용합니다.
스크롤 바운스 비활성화 (iOS) #
iOS 웹뷰 스크롤이 콘텐츠 끝에 도달해도 바운스 효과가 발생하지 않습니다(disallowOverScroll: true). Android는 해당 없습니다.
백그라운드 미디어 중단 #
앱이 백그라운드로 진입하면 미디어 재생이 중단됩니다.
- Android: 이 웹뷰만
pause()(per-instance)로 렌더링·JS를 일시정지, 포그라운드 복귀 시resume()으로 재개. 전역pauseTimers()는 호출하지 않아 같은 앱의 전면 광고 화면이 덮을 때 광고 웹뷰가 멈추지 않습니다. - iOS:
setAllMediaPlaybackSuspended(true)로 미디어 재생 명시적 중단
URL 처리 #
| 케이스 | 동작 |
|---|---|
| 허용 도메인 링크 | 내부 웹뷰에서 로드 |
| 비허용 도메인 링크 | 외부 브라우저로 오픈 |
window.open / target="_blank" |
도메인 정책에 따라 내부/외부 분기 |
intent:// 스킴 (Android) |
앱 실행, 미설치 시 browser_fallback_url 또는 Play Store로 이동 |
mailto:, tel:, itms-apps: 등 비 http(s) 스킴 |
외부로 전달 |
허용 도메인은
initialize()시 서버에서 수신한allowedDomains설정이 적용됩니다.
JS Interface #
웹에서 네이티브 기능을 호출하는 인터페이스입니다.
호출 형식 #
// 파라미터 없음
window.flutter_inappwebview.callHandler("fncName");
// 파라미터 있음
window.flutter_inappwebview.callHandler("fncName", JSON.stringify({ ... }));
콜백은 window.nativeCallback 객체로 수신합니다.
window.nativeCallback.fncName = (result) => { ... };
getAppVersion() #
앱 버전을 가져옵니다.
window.flutter_inappwebview.callHandler("getAppVersion");
window.nativeCallback.getAppVersion = (version) => {
console.log(version); // "1.0.0"
};
getADID() — Android Only #
디바이스 광고 식별자(Advertising ID)를 가져옵니다. iOS에서는 동작하지 않습니다(콜백 없음).
window.flutter_inappwebview.callHandler("getADID");
window.nativeCallback.getADID = (adId) => {
console.log(adId); // "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
};
getIDFA() — iOS Only #
디바이스 광고 식별자(IDFA)를 가져옵니다. ATT 권한 허용 시 IDFA 반환, 미허용 시 빈 문자열을 반환합니다. Android에서는 동작하지 않습니다(콜백 없음).
window.flutter_inappwebview.callHandler("getIDFA");
window.nativeCallback.getIDFA = (idfa) => {
console.log(idfa); // "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
};
getIDFV() — iOS Only #
디바이스 벤더 식별자(IDFV)를 가져옵니다. Android에서는 동작하지 않습니다(콜백 없음).
window.flutter_inappwebview.callHandler("getIDFV");
window.nativeCallback.getIDFV = (idfv) => {
console.log(idfv); // "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
};
getDeviceInfo() #
기기 정보를 JSON 문자열로 가져옵니다.
window.flutter_inappwebview.callHandler("getDeviceInfo");
window.nativeCallback.getDeviceInfo = (deviceInfo) => {
const info = JSON.parse(deviceInfo);
// Android: { network: "wifi", model: "SM-S911N", manufacturer: "Samsung", osVersion: "14" }
// iOS: { network: "wifi", model: "iPhone16,1", manufacturer: "Apple", osVersion: "17.2" }
};
| 필드 | 타입 | 값 |
|---|---|---|
network |
string |
"wifi" | "mobile". 연결 없으면 키 생략 |
model |
string |
기기 모델명 |
manufacturer |
string |
제조사 |
osVersion |
string |
OS 버전 |
registerBackKeyListener() — Android Only #
디바이스 Back Key 이벤트 권한을 얻습니다. 등록 후 Back Key가 눌리면 onBackKeyPress가 호출됩니다. iOS는 하드웨어 백키가 없어 항상 "0"(실패)을 즉시 반환합니다.
window.flutter_inappwebview.callHandler("registerBackKeyListener");
window.nativeCallback.registerBackKeyListener = (success) => {
// "1": 권한 획득 성공 (Android)
// "0": 권한 획득 실패 (iOS)
};
window.nativeCallback.onBackKeyPress = () => {
// Back Key 눌렸을 때 처리
};
unregisterBackKeyListener() — Android Only #
Back Key 이벤트 권한을 해제합니다. 호출 후 Back Key는 웹뷰 히스토리 뒤로가기 또는 화면 종료로 동작합니다. iOS는 항상 no-op입니다.
window.flutter_inappwebview.callHandler("unregisterBackKeyListener");
swipeBackEnabled() — iOS Only #
iOS 스와이프 백 기능 활성화 여부를 설정합니다.
window.flutter_inappwebview.callHandler(
"swipeBackEnabled",
JSON.stringify({ enabled: false }),
);
finish() #
현재 웹뷰를 종료합니다.
window.flutter_inappwebview.callHandler("finish");
openTrackingSettings() #
앱 설정 화면을 엽니다. 광고 추적 권한을 변경할 수 있도록 안내할 때 사용합니다.
window.flutter_inappwebview.callHandler("openTrackingSettings");
openExternalBrowser() #
외부 브라우저를 엽니다.
window.flutter_inappwebview.callHandler(
"openExternalBrowser",
JSON.stringify({ url: "https://example.com" }),
);
pushUrl() #
현재 웹뷰에서 URL을 변경합니다.
window.flutter_inappwebview.callHandler(
"pushUrl",
JSON.stringify({
url: "https://example.com",
replace: false, // true: history 교체, false: history 스택에 추가 (기본값)
}),
);