Flutter - InAppWebview Communication (인앱웹뷰로 웹과 앱 양방향 통신하기, 자바스크립트 커뮤니케이션)

반응형

 

Overview

이번 포스팅에서는...

InAppWebview Package를 이용하여 구성된 웹 페이지에서 자바스크립트를 통해

  1. 웹에서 앱으로 데이터를 전달하고 (web -> app)
  2. 앱에서 웹으로 데이터를 전달하고 (웹에서 데이터를 요청하면, app -> web)
  3. 웹과 앱 서로 데이터를 양방향으로 주고 받는 (web <-> app)

방법에 대해서 포스팅합니다.

 

이 포스팅에서는 InAppWebview의 기본 사용 방법은 설명하지 않습니다.
웹 페이지 구성 및 기본 사용 방법에 대한 포스팅은 아래의 포스팅을 참고해주세요.

 

inAppWebview에서 팝업은 기본적으로 보여지지 않기 때문에 따로 구현해야 합니다.
팝업 구현에 대한 포스팅은 아래의 포스팅을 참고해주세요.

 

포스팅에서 설명하는 프로젝트는 아래의 깃허브 주소에서 다운로드 가능합니다.


InAppWebview 기본 구성

아래는 포스팅에서 설명할 InAppWebview의 기본 구성이다.

이해가 쉽도록 가능한 main.dart 파일 하나에 구성하였다.

이 구성을 기준으로 양방향 통신 사용 방법과 설명을 진행하도록 한다.

당연하게도 아래의 코드는 빈 깡통 인앱웹뷰 코드이므로 실행하면, 아무것도 나오지 않는다.

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

Future main() async {
  // 위젯 바인딩 초기화 : 웹뷰와 플러터 엔진과의 상호작용을 위함
  WidgetsFlutterBinding.ensureInitialized();

  if (Platform.isAndroid) {
    await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
  }

  runApp(
    const MaterialApp(home: MyApp()),
  );
}

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

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 인앱웹뷰 컨트롤러
  InAppWebViewController? webViewController;
  InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
      // 플랫폼 상관없이 동작하는 옵션
      crossPlatform: InAppWebViewOptions(
        useShouldOverrideUrlLoading: true, // URL 로딩 제어
        mediaPlaybackRequiresUserGesture: false, // 미디어 자동 재생
        javaScriptEnabled: true, // 자바스크립트 실행 여부
        javaScriptCanOpenWindowsAutomatically: true, // 팝업 여부
      ),
      // 안드로이드 옵션
      android: AndroidInAppWebViewOptions(
        useHybridComposition: true, // 하이브리드 사용을 위한 안드로이드 웹뷰 최적화
      ),
      // iOS 옵션
      ios: IOSInAppWebViewOptions(
        allowsInlineMediaPlayback: true, // 웹뷰 내 미디어 재생 허용
      ));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Stack(
                children: [
                  InAppWebView(
                    // 시작 페이지
                    initialData: InAppWebViewInitialData(data: """ """), // 코드 작성
                    // 초기 설정
                    initialOptions: options,
                    // 인앱웹뷰 생성 시 컨트롤러 정의
                    onWebViewCreated: (controller) {
                      webViewController = controller;
                    },
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

웹-앱 통신 방법

우선 웹과 앱의 통신 방법에 대해서 집고 넘어가자.

 

1. 앱의 InAppWebview에서 자바스크립트 핸들러를 추가하여 정의한다.

  • 핸들러는 onWebViewCreated 부분에 작성한다.
InAppWebView(
    onWebViewCreated: (controller) {
      /* 웹에서 앱과 통신할 수 있는 핸들러를 정의
      * handlerName : 핸들러명
      * callback : 웹에서 보낸 데이터
      * return : 웹으로 보낼 데이터
      */

      controller.addJavaScriptHandler(
          handlerName: '핸들러명',
          callback: (args) {
            // 수행 코드 작성
          });
      },
)

 

2. 웹에서 앱의 해당 자바스크립트 핸들러를 찾아서 호출한다.

  • 웹에서 앱의 자바스크립트 핸들러를 찾아 호출 할 때, 파라미터에 값을 넣어 데이터를 전달할 수도 있다.
  • 자바스크립트에서 다른 프레임워크에 접근하기 위해 명시적으로 window 객체를 사용하여 접근한다.
    포스팅에서의 자바스크립트에 대한 자세한 설명은 생략한다.
<script>
    window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
        /* 웹에서 앱에게 데이터를 보내고 받는 핸들러
        * APP : onWebViewCreated에서 addJavaScriptHandler를 정의
        * WEB : window.flutter_inappwebview.callHandler를 사용하여 APP에서 정의한 핸들러 이름 호출
        *		    두번째 파라미터부터는 앱으로 보낼 데이터들을 나열
        *       callHandler ( 핸들러이름, 데이터1, 데이터2, ...)
        */

        // 웹에서 앱의 핸들러를 호출하고 데이터 보내기
        window.flutter_inappwebview.callHandler('핸들러명', '데이터1', ['배열데이터1', '배열데이터2']);
</script>

 

3. 호출된 핸들러를 통해 앱에서 정의된 기능을 수행한다.

  • 정의된 기능을 처리하고 return 키워드를 통해 다시 웹으로 처리되거나 혹은 필요한 데이터를 보낼 수 있다.
      controller.addJavaScriptHandler(
          handlerName: '핸들러명',
          callback: (args) {
            // 수행 코드 작성
            // return으로 JSON(키[result]와 밸류[ok]) 데이터 리턴
            return 'result' : 'ok';
          });
      },

웹에서 앱으로 데이터 전달하기

웹에서 앱으로 데이터를 보내보자.

  • 문자열 데이터 : 'Hello World!'
  • 사람의 정보가 담긴 배열 : ['Eunbyeol Ko', 33]

 

웹 코드

window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
    // 웹에서 앱으로 데이터 보내기
    window.flutter_inappwebview.callHandler('web2app', 'Hello World!', ['Eunbyeol Ko', 33]);
});

 

앱 코드

controller.addJavaScriptHandler(
  handlerName: 'web2app',
  callback: (args) {
    // 수행 코드 작성 : 예시 코드에서는 단순히 print로 받은 데이터를 콘솔에 출력함
    print('web2app : recieve data ===> $args');
  });

 

앱 코드에서는 웹에서 받은 데이터를 볼 수 있도록 print로 콘솔에 출력하였다.


앱에서 웹으로 데이터 전달하기

이번엔 앱에서 웹으로 데이터를 전달해보자.

제약사항이 존재하는데, 웹에서 앱의 핸들러를 호출할 경우 데이터를 전달할 수 있다.

말장난이긴한데... 결국은 웹에서 호출한 핸들러의 반환 값을 주는 것이다.

  • 앱에서 웹으로 전달할 데이터 : (문자열) 'Hello World!'

 

웹 코드

window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
    // 앱 데이터 웹으로 받기
    window.flutter_inappwebview.callHandler('app2web')
        .then(function(result) {
            // 코드 작성 : 예시 코드에서는 앱에서 받은 데이터를 alert로 보여주도록 함
            alert(result);
        });
});

 

앱 코드

controller.addJavaScriptHandler(
  handlerName: 'app2web',
  callback: (args) {
    return 'Hello World!';
  });

 

앱의 핸들러를 호출 후 반환 받은 데이터를 웹 페이지에서 alert를 통해 출력하도록 하였다.


웹-앱 양방향 통신하기

위의 두 개의 방법을 다 정의하면된다.

웹에서 앱의 핸들러 호출 시, 두번째 파라미터부터 데이터를 기재하여 앱으로 데이터를 전달하고

앱에서 호출된 핸들러의 기능을 수행 후, 반환 값을 주면된다.

  • 웹에서 보낼 데이터 : 숫자 1
  • 앱에서 처리할 로직 : 숫자가 1이면 'success' 반환, 아니면 'failed' 반환
  • 앱에서 반환할 데이터 : JSON 형식의 데이터

 

웹 코드

window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
    // 웹과 앱 양방향 통신하기
    window.flutter_inappwebview.callHandler('WebAppDataExchange', 1)
        .then(function(result) {
            // 코드 작성 : 예시 코드에서는 앱에서 받은 데이터를 alert로 보여주도록 함
            // JSON 타입으로 반환하였기 때문에 JSON.stringify를 이용하여 JSON 타입으로 변환
            alert(JSON.stringify(result['result']));
        });
});

 

앱 코드

controller.addJavaScriptHandler(
  handlerName: 'WebAppDataExchange',
  callback: (args) {
    // 수행 코드 작성 : 예시 코드에서는 단순히 print로 받은 데이터를 콘솔에 출력함
    // Web-App Data Exchange : recieve data
    print(
        'Web-App Data Exchange : recieve data ===> $args');

    // 받는 값에 따른 로직 처리 : 예시 코드에서는 값이 1이면 result 성공값 반환
    if (args[0] == 1) {
      return {
        'result': 'success'
      }; // Web-App Data Exchange : send data
    } else {
      return {'result': 'failed'};
    }
  });

 

웹에서 보낸 데이터를 앱이 받을 때 : 콘솔에 받은 데이터를 출력하도록 함

 

앱에서 웹으로 데이터를 반환할 때 : 웹 페이지에서 반환 받은 값을 alert로 출력하도록 함


전체 소스 코드

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

Future main() async {
  // 위젯 바인딩 초기화 : 웹뷰와 플러터 엔진과의 상호작용을 위함
  WidgetsFlutterBinding.ensureInitialized();

  if (Platform.isAndroid) {
    await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
  }

  runApp(
    const MaterialApp(home: MyApp()),
  );
}

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

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 인앱웹뷰 컨트롤러
  InAppWebViewController? webViewController;
  InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
      // 플랫폼 상관없이 동작하는 옵션
      crossPlatform: InAppWebViewOptions(
        useShouldOverrideUrlLoading: true, // URL 로딩 제어
        mediaPlaybackRequiresUserGesture: false, // 미디어 자동 재생
        javaScriptEnabled: true, // 자바스크립트 실행 여부
        javaScriptCanOpenWindowsAutomatically: true, // 팝업 여부
      ),
      // 안드로이드 옵션
      android: AndroidInAppWebViewOptions(
        useHybridComposition: true, // 하이브리드 사용을 위한 안드로이드 웹뷰 최적화
      ),
      // iOS 옵션
      ios: IOSInAppWebViewOptions(
        allowsInlineMediaPlayback: true, // 웹뷰 내 미디어 재생 허용
      ));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Stack(
                children: [
                  InAppWebView(
                    // 시작 페이지
                    initialData: InAppWebViewInitialData(data: """
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>InAppWebview Web-App Communication Test</title>
    <script>
        window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
            /* 웹에서 앱에게 데이터를 보내고 받는 핸들러
            * APP : onWebViewCreated에서 addJavaScriptHandler를 정의
            * WEB : window.flutter_inappwebview.callHandler를 사용하여 APP에서 정의한 핸들러 이름 호출
            *		    두번째 파라미터부터는 앱으로 보낼 데이터들을 나열
            *       callHandler ( 핸들러이름, 데이터1, 데이터2, ...)
            */

            // 웹에서 앱으로 데이터 보내기
            window.flutter_inappwebview.callHandler('web2app', 'Hello World!', ['Eunbyeol Ko', 33]);

            // 앱 데이터 웹으로 받기
            window.flutter_inappwebview.callHandler('app2web')
                .then(function(result) {
                    // 코드 작성 : 예시 코드에서는 앱에서 받은 데이터를 alert로 보여주도록 함
                    alert(result);
                });
                
            // 웹과 앱 양방향 통신하기
            window.flutter_inappwebview.callHandler('WebAppDataExchange', 1)
                .then(function(result) {
                    // 코드 작성 : 예시 코드에서는 앱에서 받은 데이터를 alert로 보여주도록 함
                    // JSON 타입으로 반환하였기 때문에 JSON.stringify를 이용하여 JSON 타입으로 변환
                    alert(JSON.stringify(result['result']));
                });
        });
    </script>
</head>
<body>
  <center>
    <h2>InAppWebview</h2>
    <h4>Web-App Data Exchange Test</h4>
</body>
</html>
                    """),
                    // 초기 설정
                    initialOptions: options,
                    // 인앱웹뷰 생성 시 컨트롤러 정의
                    onWebViewCreated: (controller) {
                      /* 웹에서 앱과 통신할 수 있는 핸들러를 정의
                      * handlerName : 핸들러명
                      * callback : 웹에서 보낸 데이터
                      * return : 웹으로 보낼 데이터
                      */

                      controller.addJavaScriptHandler(
                          handlerName: 'app2web',
                          callback: (args) {
                            return 'Hello World!';
                          });

                      controller.addJavaScriptHandler(
                          handlerName: 'web2app',
                          callback: (args) {
                            // 수행 코드 작성 : 예시 코드에서는 단순히 print로 받은 데이터를 콘솔에 출력함
                            print('web2app : recieve data ===> $args');
                          });

                      controller.addJavaScriptHandler(
                          handlerName: 'WebAppDataExchange',
                          callback: (args) {
                            // 수행 코드 작성 : 예시 코드에서는 단순히 print로 받은 데이터를 콘솔에 출력함
                            // Web-App Data Exchange : recieve data
                            print(
                                'Web-App Data Exchange : recieve data ===> $args');

                            // 받는 값에 따른 로직 처리 : 예시 코드에서는 값이 1이면 result 성공값 반환
                            if (args[0] == 1) {
                              return {
                                'result': 'success'
                              }; // Web-App Data Exchange : send data
                            } else {
                              return {'result': 'failed'};
                            }
                          });

                      webViewController = controller;
                    },
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

참고

반응형