프로그래밍/flutter

[flutter] native 와 webview 양방향 통신 방법

소행성왕자 2023. 8. 4. 16:37

목적 : 네이티브와 웹뷰간의 양방향 통신 방법을 알아본다.

네이티브 : 서버와 tcp/ip 소켓접속 후 데이터 전송하여 받아오는 역할을 한다.

웹뷰: input 정보를 네이티브에 전송하고 서버에서 받아온 응답 데이터를 네이티브에서 받아온다.

현재까지는 hexString 으로 input 값으로 전달한후 output 값도 hexString 으로 받아온다.

output

  • connect : tcp/ip 소켓 접속연결한다.
  • sendTr : 연결된 소켓으로 input hexString 보낸다. 

Flutter

프로젝트 구조

main.dart

/*
 * https://pub.dev/packages/webview_flutter
 * https://kbwplace.tistory.com/176
 *
 */

import 'dart:convert';
import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'SocketManager.dart';

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

class MyApp extends StatelessWidget {

  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  /*
   * tcp/ip 소켓 connect / send / close Class
   */
  final socketManager = SocketManager();

  /*
   * 웹뷰 컨트롤러 설정
    JavaScriptMode.disabled: WebView에서 JavaScript를 비활성화합니다. 웹 페이지의 JavaScript 코드가 실행되지 않습니다.
    JavaScriptMode.unrestricted: WebView에서 JavaScript를 활성화합니다. 웹 페이지의 JavaScript 코드가 실행되며, JavaScript로 인터랙티브한 동작이 가능해집니다.
    setBackgroundColor(const Color(0x00000000)): 이 부분은 WebView의 배경 색상을 설정하는 것입니다. 여기서 Color(0x00000000)은 투명한 배경색을 나타냅니다. 즉, WebView가 투명한 배경을 가지게 됩니다.
   */
  late final controller = WebViewController()
    ..setJavaScriptMode(JavaScriptMode.unrestricted)
    ..setBackgroundColor(const Color(0x00000000))
    ..setNavigationDelegate(
      NavigationDelegate(
        onProgress: (int progress) {
          // update loading bar.
        },
        onPageStarted: (String url) {},
        onPageFinished: (String url) {},
        onWebResourceError: (WebResourceError error) {},
        onNavigationRequest: (NavigationRequest request) {
          if (request.url.startsWith('https://www.youtube.com/')) {
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },
      ),
    )

    /*
      addJavaScriptChannel('Toaster', onMessageReceived: (JavaScriptMessage message) { ... }): 이 부분은 WebView에 JavaScript 채널을 추가하는 것입니다.
      채널은 JavaScript와 Flutter 사이의 통신을 가능하게 해주는 메커니즘입니다.
      여기서 'Toaster'는 채널의 이름을 나타냅니다.
      이 이름을 기반으로 JavaScript에서 해당 채널을 호출하고, Flutter에서 해당 채널로 메시지를 수신할 수 있습니다.
      onMessageReceived: (JavaScriptMessage message) { ... }:
      이 부분은 JavaScript에서 Flutter로 메시지를 전달할 때 호출되는 콜백 함수를 정의하는 것입니다.
      JavaScriptMessage는 flutter_inappwebview 라이브러리에서 제공되는 클래스로서, JavaScript에서 전달된 메시지를 Flutter에서 받을 때 사용합니다.
      따라서 addJavaScriptChannel을 사용하여 'Toaster'라는 이름의 채널을 추가하고, 해당 채널로 메시지가 전달되면 onMessageReceived 콜백 함수가 호출됩니다.
      이때, message 매개변수를 통해 JavaScript에서 전달된 메시지를 받을 수 있습니다.
      실제 웹페이지의 javascript 확인해보면 Toaster 로 된 메시지가 있습니다.
     */
    ..addJavaScriptChannel('Toaster', onMessageReceived: (JavaScriptMessage message) {
      _web2flutter(message);
    })
    ..loadRequest(
      Uri.parse('http://220.72.212./flutter_index2.html')
    );

  /*
   * 사용자 정의 함수
   */

  _reload() {
    controller.reload();
  }

  _web2flutter(JavaScriptMessage message) {
    /*
     * json 형태를 객체로 변경
     */
    Map<String, dynamic> parsedJson = jsonDecode(message.message);

    var action = parsedJson['action'];
    var sendHexString = parsedJson['hexString'];
    var receivedHexString = '';

    switch(action) {
      case 'connect':
        socketManager.connectToSocket();    // 소켓 접속한다.
        break;
      case 'sendTr' :
        socketManager.sendTr(sendHexString);  // 접속된 소켓에 데이터를 전송한다. input
        receivedHexString = socketManager.hexString;  // 응답 데이터 가져온다. output
        break;
    }

    print('received222 : $receivedHexString');

    /*
      응답한 hexString 을 웹뷰의 javascript flutter2web() 함수로 전달한다.
     */
    controller.runJavaScript("flutter2web('$receivedHexString')");
  }

  dd(String str) {
    if (kDebugMode) {
      print(str);
    }
  }

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

  @override
  void reassemble() {
    super.reassemble();
    _reload();
  }


}

SocketManageer.dart

import 'dart:io';
import 'dart:typed_data';
import 'package:hex/hex.dart';

class SocketManager {
  late Socket _socket;
  bool _connected = false;
  late String _hexString = '';


  static const String host = "13.125.57.";  // mymq
  static const int port = 9001;

  Future<void> connectToSocket() async {
    try {
      _socket = await Socket.connect(host, port);
      print('Socket connected');

      _connected = true;

      _listenToSocket();
    } catch (e) {
      print('Failed to connect to socket: $e');
      _connected = false;
    }
  }

  void _listenToSocket() {
    _socket.listen(
          (List<int> data) {
        // 데이터 수신 처리
        //String message = String.fromCharCodes(data);
        //print('Received: $message');
        _hexString = HEX.encode(data);
        //print('recived: $_hexString');
      },
      onError: (e) {
        print('Socket error: $e');
        _disconnect();
      },
      onDone: () {
        print('Socket disconnected');
        _disconnect();
      },
    );
  }

  Future<void> sendData(String message) async {
    if (!_connected) {
      print('Socket is not connected');
      return;
    }

    try {
      _socket.write(message);
      await _socket.flush();
      print('Sent: $message');
    } catch (e) {
      print('Failed to send data: $e');
      _disconnect();
    }
  }

  Future<void> sendTr(String message) async {
    if (!_connected) {
      print('Socket is not connected');
      return;
    }

    List<int> bytes = HEX.decode(message);

    try {
      _socket.add(bytes);
      await _socket.flush();
      print('Sent: $message');
    } catch (e) {
      print('Failed to send data: $e');
      _disconnect();
    }
  }

  void _disconnect() {
    _socket?.close();
    _connected = false;
  }

  String get hexString {
    return _hexString;
  }
}

pubspec.yaml

name: webview_flutter_2308
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=2.19.1 <3.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0
  webview_flutter: ^4.2.2
  hex: ^0.2.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

안드로이드 설정

./android/app/build.gradle 파일을 열고 다음과 같이 수정합니다

android {
    defaultConfig {
        minSdkVersion 19
    }
}

 

Html

Toaster.postMessage 를 이용하여 flutter 로 전송한다.

flutter_index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>

  <h1 id="title">This is web title</h1>
  <button type="button" onclick="connect()">connect</button>
  <button type="button" onclick="send()">sendTr</button>
<script>

if (isWebView()) {
  alert('app');
} else {
  alert('web');
}

function isWebView() {
  const userAgent = window.navigator.userAgent.toLowerCase();
  return userAgent.indexOf('wv') > -1;
}

        function send() {
                // 자바스크립트에서 Flutter로 데이터를 전송하는 코드
                const message = {
                        key: 'value', // 전송할 데이터
                        action:'sendTr',
                        hexString: '0000012931334331202020202020202020202020646f6d202020202057313931304130312020202020202020202020202020202020202020202020202020202020203030302e3030302e3030302e303030203030302e3030302e3030302e3030302030302d30302d30302d30302d30302d3030202020533031383238352020202020202020205748202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207b7b2268223a2262617365222c2263223a225731393130413031222c226d223a22726563697665227d7d2020202020202020202020202020202020202020202020202020202020202020202020202020',
                };
                Toaster.postMessage(JSON.stringify(message));
        }

        function connect() {
                const message = {
                        key: 'value',
                        action: 'connect',
                        hexString: '',
                };
                Toaster.postMessage(JSON.stringify(message));
        }


        function flutter2web(msg) {
                alert(msg);
        }

</script>

</body>
</html>