Flutter - WebView - 웹과 앱 양방향 통신하기(앱에서 웹으로, 웹에서 앱으로 데이터 전달)

반응형

 

Overview

이번 포스팅에서는...

  1. 웹 페이지에서 앱에 데이터를 보내고,
  2. 앱에서 웹 페이지에 데이터를 보내는

방법을 설명합니다.
 

 
웹 뷰의 기본 사용 방법은 아래의 포스팅을 확인해주세요.

Flutter - WebView - 웹 뷰를 이용하여 웹 페이지 표시하기 (webview_flutter 4.0 이상)

Overview 플러터를 이용하여 웹 페이지를 표시하고, 웹 문서를 이용하여 화면 표시가 가능하도록 합니다. 포스팅을 보기 전에... 이 글은 webview_flutter 패키지를 통해 안드로이드 가상 에뮬레이터로

luvris2.tistory.com


기본 웹뷰 소스 코드

해당 포스팅에서 사용할 기본 웹뷰만 구현한 소스 코드입니다.
이 소스 코드를 이용하여 웹 페이지와 앱간의 데이터를 교환하는 방법을 설명합니다.
 

  • 기본 웹뷰를 실행한 화면

 

  • 기본 소스 코드
더보기
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

void main() => runApp(const MaterialApp(home: MyWebView()));

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

  @override
  State<MyWebView> createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  late WebViewController _controller;

  @override
  initState() {
    super.initState();

    // 플랫폼별 웹 뷰 컨트롤러 생성에 필요한 매개변수
    late final PlatformWebViewControllerCreationParams params;

    // 디바이스가 iOS일 경우
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    }
    // 디바이스가 Android일 경우
    else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
      params = const PlatformWebViewControllerCreationParams();
    }

    // 플랫폼에 맞는 컨트롤러 생성
    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);

    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController)
          .setMediaPlaybackRequiresUserGesture(false);
    }

    // 컨트롤러 설정
    controller
      // 웹뷰에서 자바스크립트 코드 실행 여부 : 제한없이 사용
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      // 웹 뷰 배경색 설정 : 투명 배경
      ..setBackgroundColor(const Color(0x00000000))
      // 웹 뷰 이벤트 및 요청을 처리할 대리자 설정
      ..setNavigationDelegate(
        NavigationDelegate(
          // 페이지 로딩 진행률 표시
          onProgress: (int progress) {
            //print('Update loading bar : $progress');
          },
          // 새 페이지가 로드 될 때 실행되는 코드
          onPageStarted: (String url) {
            //print('onPageStarted : $url');
          },
          // 페이지 로드가 완료 될 때 실행되는 코드
          onPageFinished: (String url) {
            //print('onPageFinished : $url');
          },
          // 웹 리소스 로드 오류 시 실행 되는 코드
          onWebResourceError: (WebResourceError error) {
            //print('WebResourceError : $error');
          },
        ),
      )
      // 보여줄 페이지 URL
      ..loadRequest(Uri.parse('https://luvris2.tistory.com/'));

    _controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(
        controller: _controller,
      ),
    );
  }
}

페이지를 보여줄 HTML 파일 정의

앱 자체에 HTML 파일을 넣어 해당 파일을 불러오는 구조로 합니다.
 

  • 프로젝트 디렉토리에 'assets' 폴더 생성
  • 생성된 폴더에 html 파일 생성 (포스팅에서는 testpage.html)

 
디자인

  • Web To App
    • 버튼을 누를 경우 : 왼쪽 텍스트의 내용을 앱으로 전달
  • App To Web
    • 앱에서 플로팅 액션 버튼을 누를 경우 : 내용을 웹 페이지로 전달하여 텍스트 필드에 입력

 

HTML 전체 소스 코드

  • ChannelName.postMessage
    • ChannelName : 앱에서 추가한 자바스크립트 채널 이름을 의미, 웹은 채널 이름으로 앱에 접근
    • postMessage : 앱에 메시지 전달
  • 그 외는 기본적인 자바스크립트 소스 코드입니다.
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
    	// 웹에서 앱으로 데이터를 전달하는 함수
        // 앱에서 자바스크립트 채널 이름(Print)을 사용하여 앱으로 데이터를 전달한다.
        function webToApp() {
            var obj = document.getElementById("web2app");
            var data = obj.value;
            // 앱의 자바스크립트 채널명을 이용하여 데이터 전달
            Print.postMessage(data);
        }

		// 앱에서 웹으로 데이터를 전달하는 함수
        // 앱에서 이 함수를 실행하여 데이터를 전달한다.
        function appToWeb(data) {
            var obj = document.getElementById("app2web");
            obj.value = data;
        }
    </script>
</head>
<body>
    <div style="font-size: 15px;">
        <h1> Web To App </h1>
        웹에서 앱에게 데이터 보내기<br>
        <input id="web2app" value="WebData">
        <input id="btn" type="button" value="앱에 데이터 전달" onclick=webToApp()>

        <hr>
        <h2> App To Web</h2>
        앱에서 웹에게 데이터 보내기<br>
        <input id="app2web">
    </div>
</body>
</html>

앱 내의 HTML 파일 설정

프로젝트 내에서 HTML 파일이 있다는 것을 명시해줘야합니다.
위에서 만든 'assets/파일이름' 을 pubspec.yaml 파일에 추가해줍니다.
 

  • pubspec.yaml 파일 열기
  • 'flutter:' 하단에 아래의 코드를 작성해줍니다. 반드시 들여쓰기 규칙을 지켜야합니다.
flutter:
  assets:
  - assets/

웹 뷰 코드

앱 내부의 HTML파일로 웹 페이지 표시하기

위에서 제공한 기본 웹뷰 코드에서 loadRequest 메서드를 loadFlutterAsset 메서드로 변경합니다.
컨트롤러가 내부의 HTML 파일을 표시하도록 설정하는 메서드입니다.
 

  • 기존 : loadRequest > 변경 : loadFlutterAsset('경로')
// 컨트롤러 설정
controller

  /* 코드 생략 */

  // 보여줄 페이지 URL
  //..loadRequest(Uri.parse('https://luvris2.tistory.com/'));
  ..loadFlutterAsset('assets/testpage.html');

_controller = controller;
}

웹에서 앱으로 데이터 받기

데이터를 받으면 출력될 다이얼로그를 정의합니다.

  • 디자인

 

  • 다이얼로그 정의 코드
    • _showDialog 라는 함수로 정의
  void _showDialog(BuildContext context, String data) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('웹으로부터 받은 데이터'),
          content: Text(data), // 웹으로 받은 데이터를 저장하는 변수 : data
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(); // 다이얼로그 닫기
              },
              child: const Text('Close'),
            ),
          ],
        );
      },
    );

 
자바스크립트 채널을 추가하여 웹에서 접근할 수 있도록 설정합니다.

  • 자바스크립트 채널 추가 설정 코드
    • 웹에서 보낸 데이터는 message.message로 확인 가능
// 컨트롤러 설정
controller

  // 자바스크립트 채널 추가 : Print
  ..addJavaScriptChannel('Print',
      onMessageReceived: (JavaScriptMessage message) {
    // 웹에서 보낸 데이터를 콘솔에 출력
    print(
        '########## onMessageReceived ########## ===>>> ${message.message}');

    setState(() {
      // 다이얼로그에 웹에서 보낸 데이터를 보여주기
      _showDialog(context, message.message);
    });
  })

  /* 코드 생략 */

  // 보여줄 페이지 URL
  ..loadFlutterAsset('assets/testpage.html');

_controller = controller;
}

앱에서 웹으로 데이터 보내기

HTML 파일에서 정의한 appToWeb 함수를 이용하여 데이터를 전달합니다.
예제에서는 플로팅 액션 버튼을 클릭하면 미리 정의해둔 메시지를 웹으로 전달하게끔 구현하였습니다.
 

 

  • 자바스크립트 appToWeb 함수 코드
<!-- HTML 파일 내용 중 앱에서 웹으로 데이터를 전달하는 함수 -->
function appToWeb(data) {
    var obj = document.getElementById("app2web");
    obj.value = data;
}

 

  • 플로팅 액션 버튼 정의 코드
    • 컨트롤러의 runJavaScript 메서드를 이용하여 웹으로 메시지 전달
    • 전달 내용 : 앱에서 보낸 메시지
  floatingActionButton: FloatingActionButton(
    // 버튼 클릭 시 웹에서 정의한 함수(appToWeb) 실행
    onPressed: () {
      _controller.runJavaScript("appToWeb('앱에서 보낸 메시지')");
    },
  ),

웹뷰 전체 소스 코드

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

void main() => runApp(const MaterialApp(home: MyWebView()));

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

  @override
  State<MyWebView> createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  late WebViewController _controller;

  @override
  initState() {
    super.initState();

    // 플랫폼별 웹 뷰 컨트롤러 생성에 필요한 매개변수
    late final PlatformWebViewControllerCreationParams params;

    // 디바이스가 iOS일 경우
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    }
    // 디바이스가 Android일 경우
    else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
      params = const PlatformWebViewControllerCreationParams();
    }

    // 플랫폼에 맞는 컨트롤러 생성
    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);

    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController)
          .setMediaPlaybackRequiresUserGesture(false);
    }

    // 컨트롤러 설정
    controller
      // 웹뷰에서 자바스크립트 코드 실행 여부 : 제한없이 사용
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      // 웹 뷰 배경색 설정 : 투명 배경
      ..setBackgroundColor(const Color(0x00000000))
      ..addJavaScriptChannel('Print',
          onMessageReceived: (JavaScriptMessage message) {
        print(
            '########## onMessageReceived ########## ===>>> ${message.message}');

        setState(() {
          _showDialog(context, message.message);
        });
      })
      // 웹 뷰 이벤트 및 요청을 처리할 대리자 설정
      ..setNavigationDelegate(
        NavigationDelegate(
          // 페이지 로딩 진행률 표시
          onProgress: (int progress) {
            //print('Update loading bar : $progress');
          },
          // 새 페이지가 로드 될 때 실행되는 코드
          onPageStarted: (String url) {
            //print('onPageStarted : $url');
          },
          // 페이지 로드가 완료 될 때 실행되는 코드
          onPageFinished: (String url) {
            //print('onPageFinished : $url');
          },
          // 웹 리소스 로드 오류 시 실행 되는 코드
          onWebResourceError: (WebResourceError error) {
            //print('WebResourceError : $error');
          },
        ),
      )
      // 보여줄 페이지 URL
      //..loadRequest(Uri.parse('https://luvris2.tistory.com/'));
      ..loadFlutterAsset('assets/testpage.html');

    _controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(
        controller: _controller,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.runJavaScript("appToWeb('앱에서 보낸 메시지')");
        },
      ),
    );
  }

  void _showDialog(BuildContext context, String data) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('웹으로부터 받은 데이터'),
          content: Text(data),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(); // 다이얼로그 닫기
              },
              child: const Text('Close'),
            ),
          ],
        );
      },
    );
  }
}

 

반응형