애드팝콘 SSP SDK 연동가이드
애드팝콘 홈페이지애드팝콘 SSP 콘솔 바로가기
  • 개요
  • Android
    • 기본 설정
    • 배너 광고
    • 전면 광고
    • 전면 비디오 광고
    • 리워드 비디오 광고
    • 보상형 광고 플러스 연동(beta)
    • 네이티브 광고
      • 네이티브 광고 레이아웃 구성 가이드
        • AdPopcornSSP NativeAdView
    • 스플래시 광고
    • 모달 광고
    • 팝콘텐츠 광고
    • 응답 에러 코드 및 샘플 프로젝트
    • 미디에이션(Android)
      • AdFit
      • Admob
      • AdForus
      • ADOP
      • GAM (Google Ad Manager)
      • Cauly
      • FAN(Facebook Audience Network)
      • MezzoMedia
      • Mintegral
      • Mobwith
      • UnityAds
      • Vungle(LiftOff)
      • Fyber
      • Pangle
      • AppLovin
      • AppLovinMax
      • NAM (Naver Ad Manager)
      • CoupangCPM
    • AppLovin Max Android Mediation 가이드
    • 릴리즈 노트
  • iOS
    • 기본 설정
      • CocoaPod 설치
      • SPM 설치
      • 수동 설치
    • 배너 광고
    • 전면 광고
    • 전면 비디오 광고
    • 리워드 비디오 광고
    • 보상형 광고 플러스 연동(beta)
    • 네이티브 광고
      • NativeAdView layout
        • 직접 설정
        • 템플릿 사용
    • 스플래시 광고
    • 모달 광고
    • 팝콘텐츠 광고
    • 응답 에러 코드 및 샘플 프로젝트
    • 미디에이션
      • NAM (Naver Ad Manager)
      • AdFit
      • AppLovin
      • AppLovinMAX
      • UnityAds
      • Vungle
      • FBAudienceNetwork (FaceBook)
      • AdMob
      • Google AdManager(GAM)
      • ADOP
      • Fyber
      • Mezzo
      • Mintegral
      • Pangle
      • Cauly
      • SDK, Mediation ver 호환성
    • AppLovin Max iOS Mediation 가이드
    • 릴리즈노트
  • Unity
    • Unity (Android)
      • 기본 설정
      • 배너 광고
      • 전면 광고
      • 리워드 비디오 광고
      • 네이티브 광고
      • 보상형 광고 플러스 연동(beta)
      • 미디에이션 Unity Android
    • Unity (iOS)
      • 기본 설정
      • 배너 광고
      • 전면 광고
      • 리워드 비디오 광고
      • 네이티브 광고
      • 보상형 광고 플러스 연동(beta)
      • 미디에이션 Unity iOS
    • 에러 코드
  • Flutter
    • 기본 설정
    • 배너 광고
    • 전면 광고
    • 전면 비디오 광고
    • 리워드 비디오 광고
    • 보상형 광고 플러스 연동(beta)
    • 네이티브 광고
    • 팝콘텐츠 광고
  • React Native(beta)
    • 기본 설정
    • 배너 광고
    • 전면 광고
    • 전면 비디오 광고
    • 리워드 비디오 광고
    • 네이티브 광고
  • Web SDK
  • 하이브리드 앱 - Web(APM + NAM)
    • 네이티브 Side 연동(Android)
    • 네이티브 Side 연동(iOS)
    • WebPage Side 연동
    • 광고 ID 수동 세팅
  • 하이브리드 앱- Web(Adfit)
    • Android
    • iOS
  • 테스트 코드
  • 리포트 API
    • 애드팝콘 SSP Report API
    • DSP Report API
    • Publisher API (Report)
    • Publisher API (Metadata)
    • Adserver Report API
    • Popcontent Report API
  • 추가 기능 연동가이드
    • Adfit Bizboard Android 연동 가이드
    • 웹 CS 페이지 연동
    • AppLovin Custom Network
      • Android
      • iOS
    • KT Library
    • 원스토어 연동가이드
      • 애드팝콘 SSP SDK for Onestore
      • 원스토어 Ads 래핑 가이드(내부용)
        • 배너 광고
        • 전면 광고
        • 전면 비디오 광고
        • 리워드 비디오 광고
        • 네이티브 광고
        • SSP 미디에이션
        • 에러 코드 및 외부 노출 함수
        • 테스트 지면 키
        • AAR 라이브러리 목록
    • 쿠팡 연동가이드
      • 쿠팡 포스트백 연동
      • 쿠팡 EP 리스트 연동
      • 쿠팡 리포트 API 연동
        • 쿠팡 일별 리포트 API
        • 쿠팡 API - Report
        • 쿠팡 API - Product list
    • Reward Banner 스크립트 연동가이드
      • Reward Banner 스크립트 Android
      • Reward Banner 스크립트 iOS
    • 차단관리 파일 가이드
    • 커스텀 타입 연동 가이드
      • 커스텀 타입(Android)
      • 커스텀 타입(iOS)
    • 쿠팡 클릭 이벤트 페이지 연동 가이드
  • 팝콘텐츠 연동가이드
    • WebView 직접 연동
    • 리워드 콜백
  • 웹 SSP
    • 쿠키 매칭
    • Web Header Bidding(WIP)
Powered by GitBook
On this page
  • 1. Landing URL 내 파라미터 설정
  • 2. 웹뷰 내 JavascriptInterface 설정
  • 2.1 공통 사항
  • 2.2 Interfaces
  • 2.3 광고 클릭에 대한 이벤트 처리 추가
  • 2.4 Sample Code

Was this helpful?

  1. 팝콘텐츠 연동가이드

WebView 직접 연동

애드팝콘 SSP SDK 연동 없이 URL만을 이용하여 연동하고자 할 때 사용되는 가이드입니다.

  • 변경 사항

    • 2025-04-18

      • flutter_webview 환경 내에서의 직접 연동 방식 가이드 추가

    • 2025-02-19

      • 2.2 Interfaces 내, 2.2.5 pushUrl api 설정 추가 및 샘플 코드 내, pushUrl 처리 로직 추가

1. Landing URL 내 파라미터 설정

애드팝콘 SSP 사업팀으로부터 전달 받은 URL에 포함된 매크로를 직접 치환한 뒤, 웹뷰로 URL을 호출해 주어야 합니다. 이벤트 페이지 주소는 팝콘텐츠에 따라 조금씩 다르지만, 파라미터 구조는 동일합니다.

https://reward-webview.adpopcornpopcontents.com/fortune/?userId={USN}&clientSessionId={CLIENT_SESSION_ID}&pageId=1
  • userId : 매체의 앱키와 이벤트 페이지에서 발생하는 리워드를 지급 받을 유저의 unique한 id를 조합하여 id가 생성됩니다. 앱키는 애드팝콘 SSP 사업실로부터 전달 받을 수 있습니다.

    • android, ios

      • {appKey}:{USN}

        • 800296516:test_user_id

  • clientSessionId

    • android

      • adid:{adid}

        • adid:08283f4e-2094-4e96-94e5-5b95b536827d

    • ios

      • idfa:{idfa}

        • idfa:99ED0000-75E0-43BD-BB25-8AE5A61B6437

위 조건에 따라 매크로 치환을 할 경우 아래와 같은 랜딩 URL이 나오게 됩니다.

https://reward-webview.adpopcornpopcontents.com/fortune/?userId=800296516:test_userId&clientSessionId=adid:08283f4e-2094-4e96-94e5-5b95b536827d&pageId=1

2. 웹뷰 내 JavascriptInterface 설정

이벤트 페이지의 원활한 동작을 위해 아래 가이드를 참고하여 JavascriptInterface 설정을 진행해 주어야 합니다.

2.1 공통 사항

  • 호출은 nativeBridge 객체로 호출합니다. 이에 아래와 같이 각 OS 환경에 맞게 nativeBridge를 등록해 주어야 합니다. 단, flutter_inappwebview 사용 시에는 nativeBridge가 아닌, 각 func 이름으로 각각 handlerName을 등록해야 합니다.

webView = new WebView(context.getApplicationContext());
webView.addJavascriptInterface(this, "nativeBridge");
wkContentController = [[WKUserContentController alloc]init];
[wkContentController addScriptMessageHandler:self name:@"nativeBridge"];

webViewConfiguration = [[WKWebViewConfiguration alloc]init];
webViewConfiguration.userContentController = wkContentController;

webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen     mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:webViewConfiguration];
InAppWebView(
        initialUrlRequest: URLRequest(url: WebUri(url)),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptEnabled: true,
            useShouldOverrideUrlLoading: true
          ),
        ),
        onWebViewCreated: (controller) {
          _webViewController = controller;
          _addJavaScriptHandler();
        })
...
void _addJavaScriptHandler() {
    _webViewController?.addJavaScriptHandler(
      handlerName: 'finish',
      callback: (args) {
        if (Platform.isIOS) {
          Future.delayed(const Duration(milliseconds: 100), () {
            if (mounted) Navigator.of(context).maybePop();
          });
        } else {
          if (mounted) Navigator.of(context).pop();
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'openExternalBrowser',
      callback: (args) {
        print('main.dart _addJavaScriptHandler (openExternalBrowser) callback: ${args.toString()}');
        if (args.isNotEmpty && args[0] is String) {
          try {
            final Map<String, dynamic> params = jsonDecode(args[0]);
            if (params['url'] != null && params['url'] is String) {
              _launchExternalBrowser(params['url']);
            }
          } catch (e) {
            print("❌ Error decoding JSON parameter (openExternalBrowser): $e");
          }
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'registerBackKeyListener',
      callback: (args) {
        if (Platform.isAndroid) {
          enableBackKeyCallback = true;
          _webViewController?.evaluateJavascript(source: registerBackKeyOnScript);
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'unregisterBackKeyListener',
      callback: (args) {
        if (Platform.isAndroid) {
          enableBackKeyCallback = false;
          _webViewController?.evaluateJavascript(source: registerBackKeyOffScript);
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'pushUrl',
      callback: (args) {
        print('main.dart _addJavaScriptHandler (pushUrl) callback: ${args.toString()}');
        if (args.isNotEmpty && args[0] is String) {
          try {
            final Map<String, dynamic> params = jsonDecode(args[0]);
            final String? url = params['url'];
            final bool replace = params['replace'] ?? false;
            if (url != null) {
              _pushUrl(url, replace);
            }
          } catch (e) {
            print("❌ Error decoding JSON parameter (pushUrl): $e");
          }
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'onPageLoaded',
      callback: (args) {
        enableOnPageCallback = true;
      },
    );
  }      
// setJavaScriptMode, addJavaScriptChannel 코드 블록을 참고하여 세팅해 줍니다.

void _setupWebViewController() {
  final PlatformWebViewControllerCreationParams params =
        const PlatformWebViewControllerCreationParams();
  
  _controller = WebViewController.fromPlatformCreationParams(params)
          ..setJavaScriptMode(JavaScriptMode.unrestricted)
          ..addJavaScriptChannel(
            'nativeBridge',
            onMessageReceived: (JavaScriptMessage message) {
              _handleJavascriptMessage(message.message);
            },
          )
        ..loadRequest(Uri.parse("LandingURL"));
}
  • 이벤트 페이지에서 기기별 호출 형식은 아래와 같습니다.

window.nativeBridge.fncName() // -> no params
window.nativeBridge.fncName(JSON.stringify({
    params: { ,,, } // -> params
}))
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
    fncName: "fncName", // -> no params
}))
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
    fncName: "fncName",
    params: { ,,, } // -> params
}))
// no param
window.flutter_inappwebview.callHandler('finish');

// params
const params = {
   url: 'https://www.google.com',
   replace: true
};
window.flutter_inappwebview.callHandler('pushUrl', JSON.stringify(params));
nativeBridge.postMessage(JSON.s
    fncName: "fncName", // -> no params
}))
nativeBridge.postMessage(JSON.s
    fncName: "fncName",
    params: { ,,, } // -> params
}))
  • 인터페이스에 대한 콜백은 nativeCallback 객체로 호출합니다.

window.nativeCallback.fncName()

2.2 Interfaces

2.2.1 onPageLoaded()

  • 설명 : 웹뷰가 정상적으로 로드 되었음을 확인합니다.

  • 콜백 : onPageLoaded()가 호출된 이후에 웹뷰 로드 성공 여부를 콜백으로 전달해 주어야 합니다.

// AOS
window.nativeBridge.onPageLoaded()

// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
    fncName: "onPageLoaded"
}))

/**
* 웹뷰 로드 성공 여부를 '0', '1'로 구분하여 SDK에서 onPageLoaded 콜백
* @Param {('0' | '1')} success 성공 여부
*/
window.nativeCallback.onPageLoaded = (success: '0' | '1') =>
// '0': 로드 실패
// '1': 로드 성공
}

2.2.2 finish()

  • 설명 : 현재 웹뷰를 종료합니다.

  • 콜백 : 콜백 없음

// AOS
window.nativeBridge.finish()

// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
    fncName: "finish"
}))

2.2.3 openExternalBrowser()

  • 설명 : 외부 브라우저를 엽니다.

  • 콜백 : 콜백 없음

// AOS
window.nativeBridge.openExternalBrowser("https://,,,")

// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.s
    fncName: "openExternalBrowser",
    params: {
        url: "https://,,,"
    }
}))

2.2.4 registerBackKeyListener() - AndroidOnly

  • 설명 : 디바이스 back key 동작 여부를 설정합니다. (안드로이드 디바이스에서만 사용됨)

  • 콜백 : 없음

// AOS
window.nativeBridge.registerBackKeyListener()
/**
* 벡키 권한 전달 성공 여부를 '0', '1'로 구분하여 SDK에서 registerBack
* @param {('0' | '1')} success 성공 여부
*/
window.nativeCallback.registerBackKeyListener = (success: '0
// '0': Back Key 권한 얻지 못함
// '1': Back Key 권한 얻음
}
/**
* Back Key 이벤트 발생시 SDK에서 onBackKeyPress 콜백을 호출합니다.
*/
window.nativeCallback.onBackKeyPress()

2.2.5 pushUrl()

  • 설명 : 현재 인앱 브라우저의 웹뷰 url을 변경합니다.

  • 콜백 : 없음

  • param

    • url: 변경될 url

    • replace(optional): 현재 인앱 브라우저 웹뷰의 history 스택 쌓을지 혹은 교체할지에 대한 옵션

      • replace는 옵셔널이며 디폴트 값은 false 입니다.

      • replace 값 false인 경우 현재 인앱 브라우저의 history 스택에 변경될 url을 쌓으면서 이동합니다

      • replace 값 true인 경우 현재 인앱 브라우저의 history 스택에 변경될 url로 교체하면서 이동합니다

// AOS
window.nativeBridge.pushUrl(JSON.stringify({
  url: "https://,,,",
  replace: true
}))

// iOS
window.webkit.messageHandlers.nativeBridge.postMessage(JSON.stringify({
	fncName: "pushUrl",
	params: {
		url: "https://,,,",
		replace: true
	}
}))

2.3 광고 클릭에 대한 이벤트 처리 추가

팝콘텐츠 이벤트 페이지 내, 스크립트로 동작하는 광고에 대한 클릭 처리 추가가 필요합니다.

아래 설정이 누락될 경우, 광고 클릭 시, 웹뷰 내에서 광고 페이지로 랜딩됩니다.

특히나, 외부 브라우저로 광고가 넘어가기 전, 실제로 유저의 클릭이 발생했는지에 대한 조건 처리가 필요합니다. 해당 처리가 누락되어 있을 경우 일부 광고에서 유저의 클릭 없이도 외부 페이지로 넘어갈 수 있습니다.

// Android의 경우 웹뷰에 TouchListener를 연결하여, 유저가 실제로 touch 했는지를 체크 한 뒤, 
// 해당 flag 값을 클릭 이벤트 처리에서 활용합니다.
private boolean mIsClicked = false;

private void setWebViewSetting(){
      webView = new WebView(context.getApplicationContext());
      webView.addJavascriptInterface(this, "nativeBridge");
      webView.setOnTouchListener(mOnTouchListener);
}

private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
      private float startX;
      private float startY;
      private int CLICK_ACTION_THRESHOLD = 200;
      
      @Override
      public boolean onTouch(View v, MotionEvent event) {
	switch (event.getAction()) {
	      case MotionEvent.ACTION_DOWN:
		startX = event.getX();
		startY = event.getY();
		break;
	      case MotionEvent.ACTION_UP:
		float endX = event.getX();
		float endY = event.getY();
		if (isAClick(startX, endX, startY, endY)) {
		      mIsClicked = true;
		}
		break;
	}
	return false;
      }

      private boolean isAClick(float startX, float endX, float startY, float endY) {
	float differenceX = Math.abs(startX - endX);
	float differenceY = Math.abs(startY - endY);
	if (differenceX > CLICK_ACTION_THRESHOLD/* =5 */ || differenceY > CLICK_ACTION_THRESHOLD) {
	      return false;
	}
	return true;
      }
};

private WebViewClient mWebViewClient = new WebViewClient(){
      @Override
      public boolean shouldOverrideUrlLoading(final WebView view, WebResourceRequest request) {
            try {
                  String url;
                  if (Build.VERSION.SDK_INT >= 21) {
                        url = request.getUrl().toString();
                  } else {
                        url = view.getUrl();
                  }
                  return urlLoading(view, url);
            } catch (Exception e) {
                  e.printStackTrace();
                  return false;
            }
      }

      @Override
      public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
            try {
                  return urlLoading(view, url);
            } catch (Exception e) {
                  e.printStackTrace();
                  return false;
            }
      }

      @Override
      public void onPageFinished(final WebView view, String url) {
         super.onPageFinished(view, url);
      }
      
      private boolean urlLoading(WebView view, String url) {
            if (mIsClicked && url != null) {
                  mIsClicked = false;
                  Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                  context.startActivity(intent);
                  return true;
             } else {
                  view.loadUrl(url);
                  return false;
            }
      }
};
// iOS의 경우 유저의 클릭인지를 정확히 판단하기 위해, navigationAction의 navigationType을
// 체크하여, 클릭 일 경우에만 랜딩 시켜 줍니다.
#pragma mark WKUIDelegate
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSString *requestString = navigationAction.request.URL.absoluteString;
    if(navigationAction.navigationType == WKNavigationTypeLinkActivated ||
       navigationAction.navigationType == WKNavigationTypeOther)
    {
        NSURL *requestURL = [NSURL URLWithString:requestString];
        if(@available(iOS 10, *))
        {
            [[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
                if (success) {
                    [_webView stopLoading];
                }
            }];
        }
        else
        {
            [[UIApplication sharedApplication] openURL:requestURL];
            [_webView stopLoading];
        }
    }
    return nil;
}

#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSString *requestString = navigationAction.request.URL.absoluteString;
    if(navigationAction.navigationType == WKNavigationTypeLinkActivated)
    {
        decisionHandler(WKNavigationActionPolicyCancel);
        NSURL *requestURL = [NSURL URLWithString:requestString];
        if(@available(iOS 10, *))
        {
            [[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
                if (success) {
                    [_webView stopLoading];
                }
            }];
        }
        else
        {
            [[UIApplication sharedApplication] openURL:requestURL];
            [_webView stopLoading];
        }
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
// shouldOverrideUrlLoading 코드 블록을 참고하여,
// 광고 클릭 시 외부 브라우저 호출 처리를 진행합니다. 

InAppWebView(
        initialUrlRequest: URLRequest(url: WebUri(url)),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptEnabled: true,
            useShouldOverrideUrlLoading: true
          ),
        ),
        onWebViewCreated: (controller) {
          _webViewController = controller;
          _addJavaScriptHandler();
        },
        onLoadStop: (controller, url) async {
          if (!isPageLoaded && enableOnPageCallback) {
            print('main.dart evaluateJavascript');
            await _webViewController?.evaluateJavascript(source: "window.nativeCallback.onPageLoaded('1');");
          }
          isPageLoaded = true;
        },
        shouldOverrideUrlLoading: (controller, navigationAction) async { // <-- shouldOverrideUrlLoading 사용
          print('main.dart shouldOverrideUrlLoading : ${navigationAction.request.url.toString()}');
          if (isPageLoaded && _shouldOpenExternally(navigationAction.request.url.toString())) {
            _launchExternalBrowser(navigationAction.request.url.toString());
            return NavigationActionPolicy.CANCEL;
          }
          return NavigationActionPolicy.ALLOW;
        },
)
// setNavigationDelegate 코드 블록을 참고하여, 광고 클릭 시 외부 브라우저 호출 처리를 진행합니다.

void _setupWebViewController() {
  final PlatformWebViewControllerCreationParams params =
        const PlatformWebViewControllerCreationParams();
  
  _controller = WebViewController.fromPlatformCreationParams(params)
          ..setJavaScriptMode(JavaScriptMode.unrestricted)
          ..addJavaScriptChannel(
            'nativeBridge',
            onMessageReceived: (JavaScriptMessage message) {
              _handleJavascriptMessage(message.message);
            },
          )
          ..setNavigationDelegate(
            NavigationDelegate(
              onPageFinished: (url) async {
                print('main.dart onPageFinished : ${url}');
                if (!isPageLoaded && enableOnPageCallback) {
                  await _controller.runJavaScript("window.nativeCallback.onPageLoaded('1');");
                }
                isPageLoaded = true;
              },
              onNavigationRequest: (request) {
                print('main.dart onNavigationRequest : ${request.url}');
                if (isPageLoaded && _shouldOpenExternally(request.url)) {
                    _launchExternalBrowser(request.url);
                    return NavigationDecision.prevent;
                }
                return NavigationDecision.navigate;
              },
            ),
          )
        ..loadRequest(Uri.parse("LandingURL"));
}

2.4 Sample Code

아래 플랫폼별 샘플 코드는 2.2 Interfaces 구현과 관련된 사항만 간략히 작성해둔 코드이며, 이에 아래 코드를 참고하여 개발 환경에 맞게 세팅하면 됩니다.

public class AdPopcornSSPPopContentsAdActivity extends Activity{
   private boolean enableOnPageCallback = false;
   private boolean enableBackKeyCallback = false;
   private final String REGISTER_BACKKEY_ON_SCRIPT = "window.nativeCallback.registerBackKeyListener('1')";
   private final String REGISTER_BACKKEY_OFF_SCRIPT = "window.nativeCallback.registerBackKeyListener('0')";
   private final String PRESS_BACKKEY_SCRIPT = "window.nativeCallback.onBackKeyPress()";
   private final String ON_PAGE_LOADED_SCRIPT = "window.nativeCallback.onPageLoaded('1')";
   private Boolean isNeedToClearHistory = false;
   private boolean mIsClicked = false;
   
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onCreate(savedInstanceState);
      
      webView= new NonLeakingWebView(context.getApplicationContext());
      webView.getSettings().setJavaScriptEnabled(true);
      webView.setWebViewClient(mWebViewClient);
      webView.addJavascriptInterface(this, "nativeBridge");
      webView.setOnTouchListener(mOnTouchListener);
   }
   
   // Android의 경우 웹뷰에 TouchListener를 연결하여, 유저가 실제로 touch 했는지를 체크 한 뒤, 
   // 해당 flag 값을 클릭 이벤트 처리에서 활용합니다.
   private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
      private float startX;
      private float startY;
      private int CLICK_ACTION_THRESHOLD = 200;
      
      @Override
      public boolean onTouch(View v, MotionEvent event) {
	switch (event.getAction()) {
	      case MotionEvent.ACTION_DOWN:
		startX = event.getX();
		startY = event.getY();
		break;
	      case MotionEvent.ACTION_UP:
		float endX = event.getX();
		float endY = event.getY();
		if (isAClick(startX, endX, startY, endY)) {
		      mIsClicked = true;
		}
		break;
	}
	return false;
      }

      private boolean isAClick(float startX, float endX, float startY, float endY) {
	float differenceX = Math.abs(startX - endX);
	float differenceY = Math.abs(startY - endY);
	if (differenceX > CLICK_ACTION_THRESHOLD/* =5 */ || differenceY > CLICK_ACTION_THRESHOLD) {
	      return false;
	}
	return true;
      }
   };
   
   @JavascriptInterface
   public void onPageLoaded() {
      try {
         enableOnPageCallback = true;
      }catch(Exception e){}
   }
   
   @JavascriptInterface
   public void finish() {
      try {
         super.finish();
      }catch(Exception e){}
   }
   
   @JavascriptInterface
   public void openExternalBrowser(String url){
      try{
         Intent openBrowser = new Intent(Intent.ACTION_VIEW);
         Uri u = Uri.parse(url);
         openBrowser.setData(u);
         context.startActivity(openBrowser);
      }catch(Exception e){}
   }
   
   @JavascriptInterface
   public void registerBackKeyListener(){
      try{
         enableBackKeyCallback = true;
         callJavascript(REGISTER_BACKKEY_ON_SCRIPT);
         return;
      }catch(Exception e){}
      callJavascript(REGISTER_BACKKEY_OFF_SCRIPT);
   }
   
   @JavascriptInterface
   public void pushUrl(String url, boolean replace){
      try{   	
   	nonLeakingWebView.post(new Runnable() {
   	   @Override
   	   public void run() {
   	      nonLeakingWebView.loadUrl(url);
   	      if(replace)
                  isNeedToClearHistory = true;
   	      else
   	          isNeedToClearHistory = false;
   				}
   	   });
     }catch(Exception e){}
   }

   
   @Override
   public void onBackPressed() {
       try {
          if(enableBackKeyCallback) {
             callJavascript(PRESS_BACKKEY_SCRIPT);
          }
          else{
             super.onBackPressed();
          }
       }catch (Exception e){e.printStackTrace();}
   }
   
   public void callJavascript(final String script){
      try {
         if(webView!= null){
            webView.post(new Runnable() {
               @Override
               public void run() {
                  webView.evaluateJavascript(script, null);
               });
         }
      }catch(Exception e){}
   }

   private WebViewClient mWebViewClient = new WebViewClient(){
      @Override
      public boolean shouldOverrideUrlLoading(final WebView view, WebResourceRequest request) {
            try {
                  String url;
                  if (Build.VERSION.SDK_INT >= 21) {
                        url = request.getUrl().toString();
                  } else {
                        url = view.getUrl();
                  }
                  return urlLoading(view, url);
            } catch (Exception e) {
                  e.printStackTrace();
                  return false;
            }
      }

      @Override
      public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
            try {
                  return urlLoading(view, url);
            } catch (Exception e) {
                  e.printStackTrace();
                  return false;
            }
      }

      @Override
      public void onPageFinished(final WebView view, String url) {
         super.onPageFinished(view, url);
         if(!isPageLoaded && enableOnPageCallback)
            callJavascript(ON_PAGE_LOADED_SCRIPT);
         isPageLoaded = true;
         if(isNeedToClearHistory)
            nonLeakingWebView.clearHistory();
         isNeedToClearHistory = false;
      }
      
      private boolean urlLoading(WebView view, String url) {
         if (mIsClicked && url != null) {
            mIsClicked = false;
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
            return true;
         } else {
            view.loadUrl(url);
            return false;
         }
      }
   };
} // end of AdPopcornSSPPopContentsAdActivity
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSString *requestString = navigationAction.request.URL.absoluteString;
    if(navigationAction.navigationType == WKNavigationTypeLinkActivated ||
       navigationAction.navigationType == WKNavigationTypeOther)
    {
        NSURL *requestURL = [NSURL URLWithString:requestString];
        if(@available(iOS 10, *))
        {
            [[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
                if (success) {
                    [_webView stopLoading];
                }
            }];
        }
        else
        {
            [[UIApplication sharedApplication] openURL:requestURL];
            [_webView stopLoading];
        }
    }
    return nil;
}

#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
{
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
    if(!_isContentsLoaded && _enableOnPageCallback)
    {
        [self callJavascript:@"window.nativeCallback.onPageLoaded('1')"];
    }
    _isContentsLoaded = YES;
}

- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
    if(!_isContentsLoaded && _enableOnPageCallback)
    {
        [self callJavascript:@"window.nativeCallback.onPageLoaded('0')"];
    }
    _isContentsLoaded = YES;
}

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSString *requestString = navigationAction.request.URL.absoluteString;
    if(navigationAction.navigationType == WKNavigationTypeLinkActivated)
    {
        decisionHandler(WKNavigationActionPolicyCancel);
        NSURL *requestURL = [NSURL URLWithString:requestString];
        if(@available(iOS 10, *))
        {
            [[UIApplication sharedApplication] openURL:requestURL options:@{} completionHandler:^(BOOL success) {
                if (success) {
                    [_webView stopLoading];
                }
            }];
        }
        else
        {
            [[UIApplication sharedApplication] openURL:requestURL];
            [_webView stopLoading];
        }
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

// Javascript Interface message 처리
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message
{
    if([message.name isEqualToString:@"nativeBridge"]){
        id JSON;
        if ([[message body] isKindOfClass:[NSString class]]) {
            id strJson = [message body];
            NSData *nsData = [strJson dataUsingEncoding:NSUTF8StringEncoding];
            JSON = [NSJSONSerialization JSONObjectWithData:nsData options:0 error:nil];
        }
        else
        {
            JSON = [message body];
        }
        
        NSString *fncName = [self checkNilToBlankString:[JSON valueForKey:@"fncName"]];
        
        if([fncName isEqualToString:@"onPageLoaded"])
        {
            _enableOnPageCallback = YES;
        }
        else if([fncName isEqualToString:@"finish"])
        {
            [_webView loadHTMLString:@"" baseURL:nil];
            [_webView stopLoading];
            _webView.UIDelegate = nil;
            _webView.navigationDelegate = nil;
            [_webView removeFromSuperview];
            _webView = nil;
            [[self presentingViewController] dismissViewControllerAnimated:NO completion:nil];
        }
        else if([fncName isEqualToString:@"openExternalBrowser"])
        {
            NSString *paramsJson = [self checkNilToBlankString:[JSON valueForKey:@"params"]];
            NSString *url = [self checkNilToBlankString:[paramsJson valueForKey:@"url"]];
            [self openWebBrowser:url];
        }
        else if([fncName isEqualToString:@"pushUrl"])
        {
            NSString *paramsJson = [self checkNilToBlankString:[JSON valueForKey:@"params"]];
            NSString *url = [self checkNilToBlankString:[paramsJson valueForKey:@"url"]];
            BOOL replace = [self checkNilToNo:[paramsJson valueForKey:@"replace"]];
            if(replace)
            {
                dispatch_async(dispatch_get_main_queue(), ^(void){
                    NSString *html = [NSString stringWithFormat:@"<script>window.location.replace('%@');</script>", url];
                    [_webView loadHTMLString:html baseURL:nil];
                });
            }
            else
            {
                dispatch_async(dispatch_get_main_queue(), ^(void){
                    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
                    [_webView loadRequest:request];
                });
            }
        }
    }
}

#pragma mark General Method
- (NSString *)checkNilToBlankString:(id)target
{
    NSString *returnString = @"";
    if (!([target isEqual:[NSNull null]] || target == nil))
    {
        returnString = target;
    }
    
    return returnString;
}

+ (BOOL)checkNilToNo:(id)target
{
    BOOL returnBool = NO; 
    if (!([target isEqual:[NSNull null]] || target == nil))
    {
        returnBool = (BOOL)[target boolValue];
    }
    
    return returnBool;
}

- (void)callJavascript:(NSString *)data
{
    dispatch_async(dispatch_get_main_queue(), ^(void){
        [_webView evaluateJavaScript:data completionHandler:^(id result, NSError *error) {
            
         }];
    });
}

- (void)openWebBrowser:(NSString *)url
{
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url] options:@{} completionHandler:^(BOOL success) {
    }];
}
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:convert';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  InAppWebViewController? _webViewController;
  bool enableOnPageCallback = false;
  bool enableBackKeyCallback = false;
  bool isPageLoaded = false;

  final String onPageLoadedScript = "window.nativeCallback.onPageLoaded('1')";
  final String pressBackKeyScript = "window.nativeCallback.onBackKeyPress()";
  final String registerBackKeyOnScript = "window.nativeCallback.registerBackKeyListener('1')";
  final String registerBackKeyOffScript = "window.nativeCallback.registerBackKeyListener('0')";

  @override
  void initState() {
    super.initState();
    _initializeAdPopcorn();
  }

  void _initializeAdPopcorn() {
    if (Platform.isAndroid) {
      AdPopcornSSP.init('android app key');
    } else if (Platform.isIOS) {
      AdPopcornSSP.init('ios app key');
    }
    AdPopcornSSP.setUserId('TEST_USN');
  }

  Widget _inAppWebViewWithBridge() {
    String url = "https://reward-webview.adpopcornpopcontents.com/fortune/?userId=800296516:test_userId&clientSessionId=adid:08283f4e-2094-4e96-94e5-5b95b536827d&pageId=1";
    return SizedBox(
      height: 700,
      child: InAppWebView(
        initialUrlRequest: URLRequest(url: WebUri(url)),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptEnabled: true,
            useShouldOverrideUrlLoading: true
          ),
        ),
        onWebViewCreated: (controller) {
          _webViewController = controller;
          _addJavaScriptHandler();
        },
        onLoadStop: (controller, url) async {
          if (!isPageLoaded && enableOnPageCallback) {
            await _webViewController?.evaluateJavascript(source: "window.nativeCallback.onPageLoaded('1');");
          }
          isPageLoaded = true;
        },
        onLoadError: (controller, url, code, message) {
        },
        shouldOverrideUrlLoading: (controller, navigationAction) async { // <-- shouldOverrideUrlLoading 사용
          if (isPageLoaded && _shouldOpenExternally(navigationAction.request.url.toString())) {
            _launchExternalBrowser(navigationAction.request.url.toString());
            return NavigationActionPolicy.CANCEL;
          }
          return NavigationActionPolicy.ALLOW;
        },
      ),
    );
  }

  void _addJavaScriptHandler() {
    print('main.dart _addJavaScriptHandler callback : ${_webViewController}');
    _webViewController?.addJavaScriptHandler(
      handlerName: 'finish',
      callback: (args) {
        if (Platform.isIOS) {
          Future.delayed(const Duration(milliseconds: 100), () {
            if (mounted) Navigator.of(context).maybePop();
          });
        } else {
          if (mounted) Navigator.of(context).pop();
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'openExternalBrowser',
      callback: (args) {
        if (args.isNotEmpty && args[0] is String) {
          try {
            final Map<String, dynamic> params = jsonDecode(args[0]);
            if (params['url'] != null && params['url'] is String) {
              _launchExternalBrowser(params['url']);
            }
          } catch (e) {
            print("❌ Error decoding JSON parameter (openExternalBrowser): $e");
          }
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'registerBackKeyListener',
      callback: (args) {
        print('main.dart _addJavaScriptHandler (registerBackKeyListener) callback: ${args.toString()}');
        if (Platform.isAndroid) {
          enableBackKeyCallback = true;
          _webViewController?.evaluateJavascript(source: registerBackKeyOnScript);
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'unregisterBackKeyListener',
      callback: (args) {
        if (Platform.isAndroid) {
          enableBackKeyCallback = false;
          _webViewController?.evaluateJavascript(source: registerBackKeyOffScript);
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'pushUrl',
      callback: (args) {
        if (args.isNotEmpty && args[0] is String) {
          try {
            final Map<String, dynamic> params = jsonDecode(args[0]);
            final String? url = params['url'];
            final bool replace = params['replace'] ?? false;
            if (url != null) {
              _pushUrl(url, replace);
            }
          } catch (e) {
            print("❌ Error decoding JSON parameter (pushUrl): $e");
          }
        }
      },
    );

    _webViewController?.addJavaScriptHandler(
      handlerName: 'onPageLoaded',
      callback: (args) {
        enableOnPageCallback = true;
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WillPopScope(
        onWillPop: () async {
          return await _onBackPressed();
        },
        child: Scaffold(
          appBar: AppBar(
            title: const Text('AdPopcornSSP flutter InAppWebView Demo'),
          ),
          body: ListView(
            children: [
              _inAppWebViewWithBridge(),
              const Divider(),
            ],
          ),
        ),
      ),
    );
  }

  Future<bool> _onBackPressed() async {
    if (enableBackKeyCallback && Platform.isAndroid && _webViewController != null) {
      final canGoBack = await _webViewController!.canGoBack();
      if (canGoBack) {
        _webViewController!.goBack();
        return false; // WebView가 뒤로 가도록 처리
      } else {
        // WebView history가 없으면 시스템 백키 처리
        return true;
      }
    } else {
      return true; // 시스템이 기본 백키(pop) 처리하도록 허용
    }
  }

  Future<void> _launchExternalBrowser(String url) async {
    final uri = Uri.parse(url);
    if (await canLaunchUrl(uri)) {
      await launchUrl(uri, mode: LaunchMode.externalApplication);
    } else {
      debugPrint('Could not launch $url');
    }
  }

  void _pushUrl(String url, bool replace) async {
    if (replace && _webViewController != null) {
      await _webViewController!.loadUrl(urlRequest: URLRequest(url: WebUri("about:blank")));
      await _webViewController!.loadUrl(urlRequest: URLRequest(url: WebUri(url)));
    } else if (_webViewController != null) {
      await _webViewController!.loadUrl(urlRequest: URLRequest(url: WebUri(url)));
    }
  }

  bool _shouldOpenExternally(String url) {
    return url.startsWith("http");
  }
}




import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io' show Platform;

import 'package:flutter/services.dart';
import 'package:adpopcornssp_flutter/adpopcornssp_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:convert';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late WebViewController _controller;
  bool enableOnPageCallback = false;
  bool enableBackKeyCallback = false;
  bool isPageLoaded = false;

  final String onPageLoadedScript = "window.nativeCallback.onPageLoaded('1')";
  final String pressBackKeyScript = "window.nativeCallback.onBackKeyPress()";
  final String registerBackKeyOnScript = "window.nativeCallback.registerBackKeyListener('1')";
  final String registerBackKeyOffScript = "window.nativeCallback.registerBackKeyListener('0')";
  
  @override
  void initState() {
    super.initState();

    _initializeAdPopcorn();
    _setupWebViewController();
  }

  void _initializeAdPopcorn() {
    if (Platform.isAndroid) {
      AdPopcornSSP.init('android app key');
    } else if (Platform.isIOS) {
      AdPopcornSSP.init('ios app key');
    }
    AdPopcornSSP.setUserId('TEST_USN');
  }
  
  void _setupWebViewController() {
      final PlatformWebViewControllerCreationParams params =
      const PlatformWebViewControllerCreationParams();
      String url = "https://reward-webview.adpopcornpopcontents.com/fortune/?userId=800296516:test_userId&clientSessionId=adid:08283f4e-2094-4e96-94e5-5b95b536827d&pageId=1";
      
      _controller = WebViewController.fromPlatformCreationParams(params)
        ..setJavaScriptMode(JavaScriptMode.unrestricted)
        ..addJavaScriptChannel(
          'nativeBridge',
          onMessageReceived: (JavaScriptMessage message) {
            _handleJavascriptMessage(message.message);
          },
        )
        ..setNavigationDelegate(
          NavigationDelegate(
            onPageFinished: (url) async {
              print('main.dart onPageFinished : ${url}');
              if (!isPageLoaded && enableOnPageCallback) {
                await _controller.runJavaScript("window.nativeCallback.onPageLoaded('1');");
              }
              isPageLoaded = true;
            },
            onNavigationRequest: (request) {
              print('main.dart onNavigationRequest : ${request.url}');
              if (isPageLoaded && _shouldOpenExternally(request.url)) {
                  _launchExternalBrowser(request.url);
                  return NavigationDecision.prevent;
              }
              return NavigationDecision.navigate;
            },
          ),
        )
      ..loadRequest(Uri.parse(url));
  }

  Widget _webViewWithBridge() {
    return WebViewWidget(controller: _controller);
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WillPopScope(
        onWillPop: () async {
          return await _onBackPressed();
        },
        child: Scaffold(
          appBar: AppBar(
            title: const Text('AdPopcornSSP flutter WebView Demo'),
          ),
          body: ListView(
            children: [
              SizedBox(height: 700, child: _webViewWithBridge()),
              const Divider(),
            ],
          ),
        ),
      ),
    );
  }

  void _handleJavascriptMessage(String messageJson) {
    print('main.dart handleJavascriptMessage : $messageJson');

    try {
      final Map<String, dynamic> json = jsonDecode(messageJson);
      final String fncName = json['fncName'];
      final dynamic params = json['params']; // null일 수도 있음

      print('main.dart fncName : $fncName');
      print('main.dart params : $params');

      switch (fncName) {
        case 'onPageLoaded':
          enableOnPageCallback = true;
          break;

        case 'finish':
          if (Platform.isIOS) {
            Future.delayed(Duration(milliseconds: 100), () {
              if (mounted) Navigator.of(context).maybePop();
            });
          } else {
            if (mounted) Navigator.of(context).pop();
          }
          break;

        case 'openExternalBrowser':
          if (params is Map && params['url'] != null) {
            _launchExternalBrowser(params['url']);
          }
          break;

        case 'registerBackKeyListener':
          if (Platform.isAndroid) {
            enableBackKeyCallback = true;
            _controller.runJavaScript(registerBackKeyOnScript);
          }
          break;

        case 'unregisterBackKeyListener':
          if (Platform.isAndroid) {
            enableBackKeyCallback = false;
            _controller.runJavaScript(registerBackKeyOffScript);
          }
          break;

        case 'pushUrl':
          if (params is Map) {
            final url = params['url'] ?? '';
            final replace = params['replace'] ?? false;
            _pushUrl(url, replace);
          }
          break;
      }
    } catch (e, stacktrace) {
      print("❌ Error decoding JavaScript message: $e");
      print(stacktrace);
    }
  }

  Future<bool> _onBackPressed() async {
    print('main.dart _onBackPressed : $enableBackKeyCallback');
    if (enableBackKeyCallback) {
      await _controller.runJavaScript("window.nativeCallback.onBackKeyPress();");
      return false; // JS가 처리하게 하고, 시스템 백키는 막음
    }
    else {
      return true; // 시스템이 기본 백키(pop) 처리하도록 허용
    }
  }

  Future<void> _launchExternalBrowser(String url) async {
    final uri = Uri.parse(url);
    if (await canLaunchUrl(uri)) {
      await launchUrl(uri, mode: LaunchMode.externalApplication);
    }
    else {
      debugPrint('Could not launch $url');
    }
  }

  void _pushUrl(String url, bool replace) async {
    if (replace) {
      // Step 1: Blank 페이지 로딩으로 히스토리 날림
      await _controller.loadRequest(Uri.parse("about:blank"));
      // Step 2: 실제 URL 로딩
      await _controller.loadRequest(Uri.parse(url));
    } else {
      _controller.loadRequest(Uri.parse(url));
    }
  }

  bool _shouldOpenExternally(String url) {
    return url.startsWith("http");
  }
}


Previous팝콘텐츠 연동가이드Next리워드 콜백

Last updated 10 days ago

Was this helpful?