# Flutter InAppWebView 연동

Flutter InAppWebView를 사용하는 개발 환경에서 전면, 전면 비디오, 리워드 비디오, 비디오 믹스를 연동하고자 할 경우에는 아래의 과정으로 연동을 진행합니다.

## 1. 기본 준비사항

### 1.1 flutter plugin 설치

아래 연동 진행을 위해서는 애드팝콘에서 제공하는 adpopcornssp\_flutter 플러그인 설치는 필수입니다.&#x20;

아래 링크의 가이드를 보고 adpopcornssp\_flutter 를 설치하세요.

{% content-ref url="" %}
[](https://adpopcornssp.gitbook.io/ssp-sdk/flutter)
{% endcontent-ref %}

### 1.2 flutter\_inappwebview 선언 및 연결

&#x20;아래 샘플을 참고하여, AdPopcornInAppWebViewBridgeHandler에 InAppWebViewController 를 연결합니다.

```dart
late AdPopcornInAppWebViewBridgeHandler bridgeHandler;
InAppWebViewController? webViewController;
...
  @override
  void initState() {
    super.initState();
    bridgeHandler = AdPopcornInAppWebViewBridgeHandler();
  }
....

  @override
  Widget build(BuildContext context){
  
    onWebViewCreated: (controller) {
      webViewController = controller;
      bridgeHandler.setWebViewController(controller);
      bridgeHandler.registerJavaScriptHandlers(controller);
    },
    
  }
...
```

전체 예시>&#x20;

```dart
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 'adpopcornssp_flutter_inappwebview_handler.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'dart:convert';

void main() {
  runApp(const AdPopcornWebViewPage());
}
class AdPopcornWebViewPage extends StatefulWidget {
  const AdPopcornWebViewPage({super.key});

  @override
  State<AdPopcornWebViewPage> createState() => _AdPopcornWebViewPageState();
}

class _AdPopcornWebViewPageState extends State<AdPopcornWebViewPage> {
  InAppWebViewController? webViewController;
  late AdPopcornInAppWebViewBridgeHandler bridgeHandler;
  bool isLoading = true;
  String eventLog = '';

  @override
  void initState() {
    super.initState();
    bridgeHandler = AdPopcornInAppWebViewBridgeHandler();
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      appBar: AppBar(
        title: const Text('AdPopcorn SSP Test'),
        backgroundColor: const Color(0xFF667EEA),
        actions: [
          if (isLoading)
            const Padding(
              padding: EdgeInsets.all(16.0),
              child: SizedBox(
                width: 20,
                height: 20,
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                  valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                ),
              ),
            ),
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () {
              webViewController?.reload();
            },
          ),
        ],
      ),
      body: InAppWebView(
        initialData: InAppWebViewInitialData(
          data: _generateHtmlContent(),
        ),

        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptEnabled: true,
            useShouldOverrideUrlLoading: false,
            clearCache: false,
            cacheEnabled: true,
            supportZoom: true,
            useOnLoadResource: false,
            useOnDownloadStart: false,
            mediaPlaybackRequiresUserGesture: false,
          ),
          android: AndroidInAppWebViewOptions(
            useHybridComposition: true,
            domStorageEnabled: true,
            allowContentAccess: true,
            allowFileAccess: true,
            useWideViewPort: true,
            loadWithOverviewMode: true,
            builtInZoomControls: true,
            displayZoomControls: false,
          ),
          ios: IOSInAppWebViewOptions(
            allowsInlineMediaPlayback: true,
            allowsBackForwardNavigationGestures: true,
          ),
        ),

        onWebViewCreated: (controller) {
          webViewController = controller;
          bridgeHandler.setWebViewController(controller);
          bridgeHandler.registerJavaScriptHandlers(controller);
        },

        onLoadStart: (controller, url) {
          setState(() {
            isLoading = true;
          });
        },

        onLoadStop: (controller, url) {
          setState(() {
            isLoading = false;
          });
        },

        onLoadError: (controller, url, code, message) {
          setState(() {
            isLoading = false;
          });
        },

        onLoadHttpError: (controller, url, statusCode, description) {
          setState(() {
            isLoading = false;
          });
        },

        onConsoleMessage: (controller, consoleMessage) {
          print('Console: ${consoleMessage.message}');
        },
      ),
    ));
  }
```

### 1.3 adpopcornssp\_flutter\_inappwebview\_handler.dart 추가

```dart
import 'package:flutter/services.dart';
import 'dart:convert';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:adpopcornssp_flutter/adpopcornssp_flutter.dart';

class AdPopcornInAppWebViewBridgeHandler {
  InAppWebViewController? _webViewController;

  AdPopcornInAppWebViewBridgeHandler();

  void setWebViewController(InAppWebViewController controller) {
    _webViewController = controller;
  }

  String returnMessageFormat(String eventName, Map<String, dynamic> eventData) {
    final messageMap = {
      "EventName": eventName,
      "Data": jsonEncode(eventData),
    };
    return jsonEncode(messageMap);
  }

  String publishEvent(String functionName, String data) {
    final jsonStringLiteral = jsonEncode(data);
    return """
      (function() {
        try {
          const event = new CustomEvent("$functionName", {
            detail: { data: $jsonStringLiteral }
          });
          window.dispatchEvent(event);
        } catch(e) {
          console.error('Error dispatching event:', e);
        }
      })();
    """;
  }

  void _handleNativeEvent(String data) {
    if (_webViewController != null) {
      _webViewController!.evaluateJavascript(
        source: publishEvent("NativeEvent", data),
      );
    }
  }

  void registerJavaScriptHandlers(InAppWebViewController controller) {
    _webViewController = controller;

    controller.addJavaScriptHandler(
      handlerName: 'init',
      callback: (args) {
        String appKey = args[0] as String;
        AdPopcornSSP.init(appKey);
        AdPopcornSSP.onInitializeListener = () {
          _handleNativeEvent(returnMessageFormat("AdPopcornSSPSDKDidInitialize", {}));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'setUserId',
      callback: (args) {
        String userId = args[0] as String;
        AdPopcornSSP.setUserId(userId);
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'loadInterstitial',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.loadInterstitial(appKey, placementId);
        AdPopcornSSP.interstitialAdLoadSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          print('[AdPopcornSSP] interstitialAdLoadSuccessListener called: $placementId');
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdLoadSuccess", dataJson));
        };
        AdPopcornSSP.interstitialAdLoadFailListener = (placementId, errorCode) {
          final dataJson = {
            "placementId": placementId,
            "errorCode": errorCode
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdLoadFail", dataJson));
        };
        print('[AdPopcornSSP] loadInterstitial called: $placementId');
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'showInterstitial',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.showInterstitial(appKey, placementId);
        AdPopcornSSP.interstitialAdShowSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdShowSuccess", dataJson));
        };
        AdPopcornSSP.interstitialAdShowFailListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdShowFail", dataJson));
        };
        AdPopcornSSP.interstitialAdClickedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdClicked", dataJson));
        };
        AdPopcornSSP.interstitialAdClosedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialAdClosed", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'loadInterstitialVideo',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.loadInterstitialVideo(appKey, placementId);
        AdPopcornSSP.interstitialVideoAdLoadSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialVideoAdLoadSuccess", dataJson));
        };
        AdPopcornSSP.interstitialVideoAdLoadFailListener = (placementId, errorCode) {
          final dataJson = {
            "placementId": placementId,
            "errorCode": errorCode
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialVideoAdLoadFail", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'showInterstitialVideo',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.showInterstitialVideo(appKey, placementId);
        AdPopcornSSP.interstitialVideoAdShowSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialVideoAdShowSuccess", dataJson));
        };
        AdPopcornSSP.interstitialVideoAdShowFailListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialVideoAdShowFail", dataJson));
        };
        AdPopcornSSP.interstitialVideoAdClosedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPInterstitialVideoAdClosed", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'loadRewardVideo',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.loadRewardVideo(appKey, placementId);
        AdPopcornSSP.rewardVideoAdLoadSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdLoadSuccess", dataJson));
        };
        AdPopcornSSP.rewardVideoAdLoadFailListener = (placementId, errorCode) {
          final dataJson = {
            "placementId": placementId,
            "errorCode": errorCode
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdLoadFail", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'showRewardVideo',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.showRewardVideo(appKey, placementId);
        AdPopcornSSP.rewardVideoAdShowSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdShowSuccess", dataJson));
        };
        AdPopcornSSP.rewardVideoAdShowFailListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdShowFail", dataJson));
        };
        AdPopcornSSP.rewardVideoAdClosedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdClosed", dataJson));
        };
        AdPopcornSSP.rewardVideoAdCompletedListener = (placementId, adNetworkNo, completed) {
          final dataJson = {
            "placementId": placementId,
	    "adNetworkNo": adNetworkNo,
  	    "completed": completed
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardVideoAdPlayCompleted", dataJson));
        };
        AdPopcornSSP.rewardPlusCompletedListener = (result, resultCode, reward) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPRewardPlusCompleted", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'loadVideoMix',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.loadVideoMix(appKey, placementId);
        AdPopcornSSP.videoMixAdLoadSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdLoadSuccess", dataJson));
        };
        AdPopcornSSP.videoMixAdLoadFailListener = (placementId, errorCode) {
          final dataJson = {
            "placementId": placementId,
            "errorCode": errorCode
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdLoadFail", dataJson));
        };
        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'showVideoMix',
      callback: (args) {
        String appKey = args[0] as String;
        String placementId = args[1] as String;
        AdPopcornSSP.showVideoMix(appKey, placementId);
        AdPopcornSSP.videoMixAdShowSuccessListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdShowSuccess", dataJson));
        };
        AdPopcornSSP.videoMixAdShowFailListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdShowFail", dataJson));
        };
        AdPopcornSSP.videoMixAdClosedListener = (placementId, campaignType) {
          final dataJson = {
            "placementId": placementId,
	    "campaignType": campaignType
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdClosed", dataJson));
        };
        AdPopcornSSP.videoMixAdCompletedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdPlayCompleted", dataJson));
        };
        AdPopcornSSP.videoMixAdClickedListener = (placementId) {
          final dataJson = {
            "placementId": placementId
          };
          _handleNativeEvent(returnMessageFormat("APSSPVideoMixAdClicked", dataJson));
        };
        return null;
      },
    );
  }
}

```

## 2. 웹 Javacript API 연동

위의 기본 연동이 끝난 상태에서 실제 웹 페이지에선 아래의 API를 호출하여 광고 연동 및 이벤트 연동을 진행 합니다.

### 2.1 전면 광고

#### 2.1.1 전면 광고 요청

* 전면 형태의 광고 요청이 필요할 때 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('loadInterstitial', APP_KEY, PLACEMENT_ID_INTERSTITIAL);
```

* appKey : 앱키
* placementId: 지면키

#### 2.1.2 전면 광고 노출

* 전면 광고 노출 시 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('showInterstitial', APP_KEY, PLACEMENT_ID_INTERSTITIAL);
```

* appKey : 앱키
* placementId: 지면키

#### 2.1.3 전면 광고 이벤트

* 전면 광고 관련 이벤트를 받고자 할 때 사용됩니다.

```html
<body>
  <!-- ... -->
  <script>
    window.addEventListener('NativeEvent', function(event) {
        const eventDataString = event.detail.data;
        let eventObject = null;
        
        if (typeof eventDataString === 'string') {
            try {
                eventObject = JSON.parse(eventDataString);
            } catch (e) {
                console.error('JSON parsing failed:', e);
                console.log('Failed to parse string:', eventDataString);
            }
        }
        
        if (eventObject) {
            console.log('EventName received:', eventObject.EventName);
            logEvent('Received', eventObject);
            handleAdEvent(eventObject);
        }
    });
  </script>
</body>
```

* 지원되는 EventName
  * APSSPInterstitialAdLoadSuccess : 전면 광고 로드 성공
    * placementId
  * APSSPInterstitialAdLoadFail : 전면 광고 로드 실패
    * placementId
    * errorCode
  * APSSPInterstitialAdShowSuccess : 전면 광고 노출 성공
    * placementId
  * APSSPInterstitialAdShowFail : 전면 광고 노출 실패
    * placementId
  * APSSPInterstitialAdClosed : 전면 광고 닫기
    * placementId
  * APSSPInterstitialAdClicked : 전면 광고 클릭
    * placementId

### 2.2 전면 비디오 광고

#### 2.2.1 전면 비디오광고 요청

* 전면 형태의 비디오광고 요청이 필요할 때 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('loadInterstitialVideo', APP_KEY, PLACEMENT_ID_INTERSTITIAL_VIDEO); 
```

* appKey : 앱키
* placementId: 지면키

#### 2.2.2 전면 비디오광고 노출

* 전면 비디오광고 노출 시 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('showInterstitialVideo', APP_KEY, PLACEMENT_ID_INTERSTITIAL_VIDEO);
```

* appKey : 앱키
* placementId: 지면키

#### 2.2.3 전면 비디오 광고 이벤트

* 전면 비디오광고 관련 이벤트를 받고자 할 때 사용됩니다.

```html
<body>
  <!-- ... -->
  <script>
    window.addEventListener('NativeEvent', function(event) {
        const eventDataString = event.detail.data;
        let eventObject = null;
        
        if (typeof eventDataString === 'string') {
            try {
                eventObject = JSON.parse(eventDataString);
            } catch (e) {
                console.error('JSON parsing failed:', e);
                console.log('Failed to parse string:', eventDataString);
            }
        }
        
        if (eventObject) {
            console.log('EventName received:', eventObject.EventName);
            logEvent('Received', eventObject);
            handleAdEvent(eventObject);
        }
    });
  </script>
</body>
```

* 지원되는 이벤트
  * APSSPInterstitialVideoAdLoadSuccess  : 전면 비디오 광고 로드 성공
    * placementId
  * APSSPInterstitialVideoAdLoadFail : 전면 비디오 광고 로드 실패
    * placementId
    * errorCode
  * APSSPInterstitialVideoAdShowSuccess : 전면 비디오 광고 노출 성공
    * placementId
  * APSSPInterstitialVideoAdShowFail : 전면 비디오 광고 노출 실패
    * placementId
    * errorCode
  * APSSPInterstitialVideoAdClosed : 전면 비디오 광고 닫기

    * placementId

### 2.3 리워드 비디오 광고

#### 2.3.1 리워드 비디오광고 요청

* 리워드 비디오광고 요청이 필요할 때 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('loadRewardVideo', APP_KEY, PLACEMENT_ID_REWARD_VIDEO);
```

* appKey : 앱키
* placementId: 지면키

#### 2.3.2 리워드 비디오광고 노출

* 리워드 비디오광고 노출 시 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('showRewardVideo', APP_KEY, PLACEMENT_ID_REWARD_VIDEO); 
```

* appKey : 앱키
* placementId: 지면키

#### 2.3.3 리워드비디오 광고 이벤트

* 리워드비디오광고 관련 이벤트를 받고자 할 때 사용됩니다.

```html
<body>
  <!-- ... -->
  <script>
    window.addEventListener('NativeEvent', function(event) {
        const eventDataString = event.detail.data;
        let eventObject = null;
        
        if (typeof eventDataString === 'string') {
            try {
                eventObject = JSON.parse(eventDataString);
            } catch (e) {
                console.error('JSON parsing failed:', e);
                console.log('Failed to parse string:', eventDataString);
            }
        }
        
        if (eventObject) {
            console.log('EventName received:', eventObject.EventName);
            logEvent('Received', eventObject);
            handleAdEvent(eventObject);
        }
    });
  </script>
</body>
```

* 지원되는 이벤트
  * APSSPRewardVideoAdLoadSuccess : 리워드비디오 광고 로드 성공
    * placementId
  * APSSPRewardVideoAdLoadFail : 리워드 비디오 광고 로드 실패
    * placementId
    * errorCode
  * APSSPRewardVideoAdShowSuccess : 리워드 비디오 광고 노출 성공
    * placementId
  * APSSPRewardVideoAdShowFail : 리워드 비디오 광고 노출 실패
    * placementId
    * errorCode
  * APSSPRewardVideoAdPlayCompleted : 리워드 비디오 광고 재생 완료
    * placementId
    * adNetworkNo
    * completed
  * APSSPRewardVideoAdClosed : 리워드 비디오 광고 닫기
    * placementId

### 2.4 비디오 믹스 광고

#### 2.4.1 비디오 믹스광고 요청

* 비디오 믹스 광고 요청이 필요할 때 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('loadVideoMix', APP_KEY, PLACEMENT_ID_VIDEO_MIX);
```

* appKey : 앱키
* placementId: 지면키

#### 2.4.2 비디오 믹스광고 노출

* 비디오 믹스광고 노출 시 사용되는 API 입니다.

```javascript
window.flutter_inappwebview.callHandler('showVideoMix', APP_KEY, PLACEMENT_ID_VIDEO_MIX);
```

* appKey : 앱키
* placementId: 지면키

#### 2.4.3 비디오 믹스광고 이벤트

* 비디오 믹스광고 관련 이벤트를 받고자 할 때 사용됩니다.

```html
<body>
  <!-- ... -->
  <script>
    window.addEventListener('NativeEvent', function(event) {
        const eventDataString = event.detail.data;
        let eventObject = null;
        
        if (typeof eventDataString === 'string') {
            try {
                eventObject = JSON.parse(eventDataString);
            } catch (e) {
                console.error('JSON parsing failed:', e);
                console.log('Failed to parse string:', eventDataString);
            }
        }
        
        if (eventObject) {
            console.log('EventName received:', eventObject.EventName);
            logEvent('Received', eventObject);
            handleAdEvent(eventObject);
        }
    });
  </script>
</body>
```

* 지원되는 이벤트
  * APSSPVideoMixAdLoadSuccess : 비디오 믹스 광고 로드 성공
    * placementId
  * APSSPVideoMixAdLoadFail : 비디오 믹스광고 로드 실패
    * placementId
    * errorCode
  * APSSPVideoMixAdShowSuccess : 비디오 믹스광고 노출 성공
    * placementId
  * APSSPVideoMixAdShowFail : 비디오 믹스광고 노출 실패
    * placementId
    * errorCode
  * APSSPVideoMixAdPlayCompleted  : 비디오 믹스광고 재생 완료
    * placementId
  * APSSPVideoMixAdClosed : 비디오 믹스광고 닫기
    * placementId
    * campaignType : 2(전면), 4(리워드 비디오), 6(전면 비디오)
  * APSSPVideoMixAdClicked : 비디오 믹스 광고 클릭
    * placementId
