visual_reward 1.3.3 copy "visual_reward: ^1.3.3" to clipboard
visual_reward: ^1.3.3 copied to clipboard

Tenqube Visual Reward SDK for Flutter. A content 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 #

dependencies:
  # visual_reward SDK
  visual_reward: ^1.3.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 미만 비활성 (로그로 안내, 크래시 없음)
미링크 비활성 (로그로 안내, 크래시 없음)

Android 는 GMA 두 세대를 모두 지원합니다. legacy(play-services-ads)와 Next-Gen(ads-mobile-sdk) 중 호스트가 링크한 쪽에 맞춰 SDK 가 자동으로 맞춥니다. 앱이 지정할 설정은 없습니다. 단, Next-Gen 을 쓰는 경우 앱이 minSdk 24 · compileSdk 35 이상이어야 합니다 (GMA Next-Gen 자체의 요구사항).

App ID #

  • 호스트 앱에 AdMob/Ad Manager App ID가 필요합니다. ID가 없는 경우 요청주시기 바랍니다.
  • App ID가 누락되면 앱 시작 시 크래시가 발생합니다.
  1. AndroidManifest.xml

    <application>
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" />
    </application>
    
  2. 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 ID 를 선언하세요. 이 SDK 는 두 광고를 인앱으로 서빙하므로 App ID 가 필요합니다. 웹뷰 전용 우회 키(Android com.google.android.gms.ads.INTEGRATION_MANAGER · iOS GADIntegrationManager = webview)는 GMA 의 App ID 검사를 건너뛰는 설정이라 이 경우에는 넣을 이유가 없습니다.

App ID 가 없는 구성(리워드 웹뷰 안의 광고만 쓰는 경우)이라면 이 우회 키가 필요합니다.

  • Android legacy: 넣지 않으면 앱 시작 즉시 크래시하고(MobileAdsInitProvider → Missing application ID), 넣으면 크래시 없이 registerWebView 까지 정상 동작하는 것이 실측됐습니다. Next-Gen 은 이 키를 읽지 않으며 추가 설정이 필요 없습니다.
  • iOS: 넣지 않으면 앱 시작 시 GADInvalidInitializationException 으로 크래시합니다.

다만 이 구성에서 리워드·전면 광고가 실제로 서빙되는지는 미검증입니다.

app-ads.txt #

  • 앱 스토어 등록정보의 개발자 웹사이트 도메인 루트 app-ads.txt 에 아래 목록을 그대로 게시해주세요. 게시할 전체 목록은 https://visual.reward.tenqube.com/docs/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: open(serviceName, userId, ...)
    B->>D: 웹뷰 열기

    D->>B: 웹뷰 닫힘
    B-->>A: onClose

사용법 #

디버그 설정 #

  • initialize() 전에 호출합니다.

    import 'package:flutter/foundation.dart';
    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 파라미터

    파라미터 타입 필수 설명 기본값
    appKey String ✅ stage별 app_key 발급 요청이 필요합니다.
    adTestMode bool 테스트 광고 노출 여부. 광고 엔진 첫 초기화 시점의 값만 적용됩니다 false
    timeout Duration 초기화 완료 대기 시간. 초과 시 VisualRewardInitializeTimeoutError 발생 Duration(seconds: 5)
    onSuccess VoidCallback? 초기화 성공 콜백 null
    onFailure void Function(VisualRewardError)? 초기화 실패 콜백 null

    onSuccess/onFailure 는 리워드 설정 로드 결과에 대한 콜백입니다. 광고 엔진 초기화는 백그라운드에서 진행되며 실패해도 리워드 기능은 정상 동작합니다(광고만 비활성). 광고 엔진 상태는 VisualReward 로그 태그로 확인할 수 있습니다.

    광고용 도메인은 앱이 지정하지 않습니다. appKey 로 조회하는 서버 설정의 ezoic.adDomain 값을 사용하며, 설정에 값이 없으면 광고 기능만 비활성됩니다.

    initialize() 재호출 시 리워드 설정은 새 appKey 로 교체됩니다(광고 도메인·adTestMode 는 첫 초기화 값이 유지됩니다). 교체 중이던 open() 호출은 실패하지 않고 새 설정을 이어서 기다립니다.

    동의 관리(TCF v2 / GPP / US Privacy)는 SDK 가 SharedPreferences(Android) · UserDefaults(iOS) 에서 자동으로 읽습니다. 디버그 로그는 setDebugEnabled() 값이 광고 엔진에도 함께 적용됩니다.

    Rewarded / Interstitial 광고는 웹 페이지가 요청합니다. 호스트가 광고를 직접 로드·표시하는 코드는 필요하지 않습니다.

웹뷰 열기 #

  • initialize() 후에 호출합니다.

    import 'package:visual_reward/visual_reward.dart';
    
    VisualReward.open(
      context: context,
      serviceName: 'reward_home',
      userId: user.id,          // 리워드 귀속 사용자
      locale: 'ko',
      extra: null,
      onClose: () { /* 웹뷰 닫힘 */ },
      onError: (e) { /* 열기 실패 */ },
    );
    
  • open 파라미터

    파라미터 타입 필수 설명 기본값
    context BuildContext ✅ 화면을 열 BuildContext
    serviceName String ✅ 사용 서비스별 serviceName 전달 예정입니다.
    userId String ✅ 리워드 귀속 사용자 식별자. 비어 있으면 VisualRewardUserIdNotSetError
    locale String? BCP 47 (RFC 5646) 언어 서브태그(ISO 639-1, 2자리 소문자)를 사용 "ko"
    extra String? 포스트백 추가 데이터 (JSON serialize된 문자열) null
    onClose VoidCallback? 웹뷰가 닫힐 때 호출되는 콜백 null
    onError void Function(VisualRewardError)? 에러 발생 시 호출되는 콜백 null

    한 번의 open() 은 onClose 또는 onError 중 정확히 하나를 받습니다. 화면 트리에서 사라진 BuildContext 로 호출하면 VisualRewardOpenFailedError 로 알립니다(조용히 아무 콜백도 오지 않는 경우를 없앱니다).

AdPopcorn 브릿지 등록 #

  • AdPopcorn SDK(adpopcornssp_flutter)를 사용하는 경우 open 호출 전에 등록합니다.

    import 'package:adpopcornssp_flutter/adpopcornssp_flutter.dart';
    
    VisualReward.setAdPopcornBridgeHandler(AdPopcornInAppWebViewBridgeHandler());
    

호스트 WebView 브릿지 #

호스트 앱이 직접 소유한 WebView 의 웹 페이지에서 리워드 웹뷰를 열 수 있습니다. 리워드 웹뷰(SDK 소유)에는 필요 없습니다. 이미 자체 브릿지를 갖고 있습니다.

1. 네이티브: 브릿지 부착 #

import 'package:flutter/material.dart';
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 로 리워드 웹뷰를 열려고 시도합니다.

VisualReward.initialize() 가 선행돼야 합니다. 브릿지는 초기화를 대신하지 않습니다. 앱이 초기화한 appKey 설정(허용 오리진·광고 엔진)을 그대로 사용합니다. 초기화 전에 페이지가 호출하면 NOT_INITIALIZED 로 응답합니다.

2. 웹: 핸들러 호출 #

window.flutter_inappwebview.callHandler('visualRewardBridge', JSON.stringify({
  userId: 'u-123',            // 필수: 리워드 귀속 사용자
  serviceName: 'anyService',  // 필수: 실행 serviceName(전달 예정)
  locale: 'ko',               // 선택: 기본 "ko"
  extra: '',                  // 선택: 포스트백 추가 데이터(JSON serialize된 문자열)
}));
필드 타입 필수 설명 기본값
userId string ✅ 리워드 귀속 사용자
serviceName string ✅ 실행 serviceName(전달 예정)
locale string BCP 47 언어 서브태그 "ko"
extra string 포스트백 추가 데이터 (JSON serialize된 문자열) ""

appKey · adTestMode · 대기 한도는 웹이 지정하지 않습니다. 모두 네이티브 initialize() 가 소유합니다(timeout 이 설정 대기 한도로 쓰입니다).

응답 콜백은 페이지에서 전역 객체로 등록합니다.

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 없이 열기 시도
OPEN_FAILED 리워드 화면 진입 실패
INVALID_PAYLOAD 브릿지 자체: 필수 키 누락·JSON 파싱 실패

브릿지 호출 하나는 onClose 또는 onError 중 정확히 하나를 받습니다(오리진 검증 실패는 예외로, 의도적으로 아무 것도 알리지 않습니다).

설정 관련 코드(NETWORK_ERROR · INVALID_APP_KEY · SERVER_ERROR · PARSE_ERROR · INITIALIZE_TIMEOUT)는 브릿지가 따로 조회해서 내는 것이 아니라 네이티브 initialize() 의 실패·대기 결과가 그대로 전달되는 것입니다.

브릿지가 주입됐는지 확인하고 호출하세요.

if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
  window.flutter_inappwebview.callHandler('visualRewardBridge', JSON.stringify({ /* ... */ }));
}

3. 호출 가능한 페이지 (허용 오리진) #

앱이 initialize() 로 채택한 설정의 allowedDomains 에 호출한 페이지의 오리진이 포함돼야 실행됩니다. 검증에 실패하면 아무 것도 하지 않고 로그만 남깁니다(어떤 오리진이 허용되는지 탐색할 수 있는 신호를 웹에 주지 않기 위해 콜백도 호출하지 않습니다).

  • 오리진은 최상위 페이지 URL 로 판정합니다. 같은 오리진 iframe 은 통과합니다
  • 브릿지 호출은 SDK 상태(설정·광고 엔진)를 바꾸지 않습니다. 웹은 appKey 를 지정할 수 없으므로 검증 기준을 고를 수 없습니다
  • file:// 로 로드한 페이지는 오리진 호스트가 비어 있어 항상 거부됩니다

에러 처리 #

에러 타입 #

타입 설명
VisualRewardNetworkError 네트워크 연결 실패
VisualRewardInitializeTimeoutError initialize() 호출 후 완료까지 타임아웃
VisualRewardInvalidAppKeyError 유효하지 않은 앱 키 (404)
VisualRewardServerError(statusCode) 서버 오류 (5xx)
VisualRewardParseError 응답 파싱 실패
VisualRewardNotInitializedError initialize() 호출 없이 open() 호출
VisualRewardUserIdNotSetError open() 의 userId 가 빈 문자열
VisualRewardOpenFailedError 리워드 화면 진입 실패: 화면 트리에서 사라진 BuildContext 로 open() 호출 등

initialize 에러 처리 #

  • onFailure 콜백에서 에러 타입을 분기합니다.

    import 'package:visual_reward/visual_reward.dart';
    
    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 콜백으로 에러를 전달합니다. 콜백에서 에러 타입을 분기합니다.

    import 'package:visual_reward/visual_reward.dart';
    
    VisualReward.open(
      context: context,
      serviceName: 'reward_home',
      userId: user.id,
      locale: 'ko',
      extra: null,
      onError: (e) {
        if (e is VisualRewardNotInitializedError) {
          // initialize() 미호출
        } else if (e is VisualRewardUserIdNotSetError) {
          // userId 가 빈 문자열
        } else if (e is VisualRewardOpenFailedError) {
          // 화면 진입 실패
        } else if (e is VisualRewardNetworkError) {
          // 네트워크 연결 실패
        } else if (e is VisualRewardServerError) {
          // 서버 오류: e.statusCode
        }
      },
    );
    
0
likes
0
points
460
downloads

Publisher

verified publishertenqube.com

Weekly Downloads

Tenqube Visual Reward SDK for Flutter. A content webview SDK built on flutter_inappwebview for Android and iOS.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_inappwebview

More

Packages that depend on visual_reward

Packages that implement visual_reward