visual_reward 1.1.2 copy "visual_reward: ^1.1.2" to clipboard
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 스택에 추가 (기본값)
  }),
);