프로그래밍/flutter

[flutter] native(앱) 의 웹뷰(vue3) 양방향 통신 방법 InAppWebView 사용

소행성왕자 2023. 8. 11. 13:07

pubspec.yaml

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
  flutter_inappwebview: ^5.3.2
  hex: ^0.2.0

main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:webview_flutter/src/webview_controller.dart';
import 'SocketManager.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'InAppWebView Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  late InAppWebViewController _webViewController;

  final socketManager = SocketManager();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InAppWebView Example'),
      ),
      body: InAppWebView(
        initialUrlRequest: URLRequest(url: Uri.parse('http://192.168.0.6:5173/')),
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
            javaScriptCanOpenWindowsAutomatically: true,
            javaScriptEnabled: true,
            useOnDownloadStart: true,
            useOnLoadResource: true,
            useShouldOverrideUrlLoading: true,
            mediaPlaybackRequiresUserGesture: true,
            allowFileAccessFromFileURLs: true,
            allowUniversalAccessFromFileURLs: true,
            verticalScrollBarEnabled: true,
            userAgent: 'agent-> naya moblie_wv',
          ),
          android: AndroidInAppWebViewOptions(

          ),
          ios: IOSInAppWebViewOptions(

          ),
        ),
        onWebViewCreated: (controller) {
          _webViewController = controller;

          // JavaScript 채널 추가
          _webViewController.addJavaScriptHandler(
            handlerName: 'JsChannel', // 채널 이름
            callback: (message) {

              // 자바스크립트 채널로부터 받은 데이터 처리
              print('Received data from JavaScript: ${message[0]}');
              _web2flutter(message[0]);
            },
          );
        },
        onConsoleMessage: (webViewController, consoleMessage) {
          print("JavaScript Console Message: ${consoleMessage.message}");
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: () {
          _webViewController.reload();
        },
      ),
    );
  }

  _web2flutter(message) async {

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

    var action = parsedJson['action'];
    var inputHexString = parsedJson['hexString'];
    print('acton:'+action);
    print('inputHexString:'+inputHexString);

    var receivedHexString = '';
    print(action);

    switch(action) {
      case 'connect':
        connect();
        break;
      case 'sendTr' :
        sendTr(inputHexString);
        break;
      case 'bufferTest' :
        bufferTest();
        break;
    }
  }


  Future<void> connect() async {
    await socketManager.connectToSocket(_webViewController);    // 소켓 접속한다.
    var receivedConnected = 'connected : ${socketManager.connected}';
    print(receivedConnected);
    //_webViewController.runJavaScript("flutter2web('$receivedConnected')");
  }


  Future<void> sendTr(sendHexString) async {
    socketManager.sendTr(sendHexString);  // 접속된 소켓에 데이터를 전송한다. input
  }


  void bufferTest() {

    List<int> buffer = [0x00, 0x00, 0x07, 0x04]; // 예시 데이터

    int length = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];

    print(buffer[0]);
    print(buffer[1]);
    print(buffer[2]);
    print(buffer[3]);

    int part1 = buffer[0] << 24;  // 24비트(3byte) 왼쪽으로 이동 -> 0x 00 00 00 00 (32bit - 4byte)
    int part2 = buffer[1] << 16;  // 16비트(2byte) 왼쪽으로 이동 -> 0x 00 00 00    (24bit - 3byte)
    int part3 = buffer[2] << 8;   //  8비트(1byte) 왼쪽으로 이동 -> 0x 07 00       (16bit - 2byte)
    int part4 = buffer[3];        // 그대로 사용               -> 0x 04           (8bit - 1byte)

    /**
     *
        그럼 0x03 을 24비트 좌측 쉬프트 하면 ?
        0x03 이진수로 표현 00000000 00000000 00000000 00000011

        0x03 << 24
        0x00 00 00 03

        그럼 0x03 을 16비트 좌측 쉬프트 하면 ?
        0x03 << 16
        0x00 03 00 00

        그럼 0x03 을 8비트 좌측 쉬프트 하면 ?
        0x03 << 8
        0x00 00 03 00

        -------------------------------------------------------
        0x00 << 24
        0x00 00 00 00

        0x00 << 16
        0x00 00 00 00

        0x07 << 8
        0x00 00 07 00

        0x04 그대로 사용
        0x00 00 00 04

        ==> |  or 연산하면

        0x00 00 00 00
        0x00 00 00 00
        0x00 00 07 00
        0x00 00 00 04
        -------------
        0x00 00 07 04

        결론 0x00000704
        16진수 704
        10진수로 변환하면 1796
     */

    // 10진수 값을 16진수 문자열로 변환
    print('Part 1: ${part1}');
    print('Part 1: ${part1.toRadixString(16)}');
    print('Part 2: ${part2.toRadixString(16)}');
    print('Part 3: ${part3.toRadixString(16)}');
    print('Part 4: ${part4.toRadixString(16)}');

    print('Length1: ${length.toRadixString(16)}');


    print('Length2: $length');

  }


}

SocketManager.dart

import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:hex/hex.dart';
import 'package:webview_flutter/src/webview_controller.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

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

/*  static const String host = "220.72.212.";  // php server
  static const int port = 25003;*/

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


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

      _controller = controller;
      _connected = true;

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


  Future<void> _listenToSocket() async {
    // 데이터를 받을 버퍼를 초기화
    List<int> buffer = [];
    _socket.listen(
      (List<int> data) async {
        // 받은 데이터를 버퍼에 추가
        buffer.addAll(data);

        // 전체 데이터 길이 정보가 도착한 경우
        if (buffer.length >= 4) {
          /**
           * 전체 데이터 길이를 얻음
           * buffer[0], buffer[1], buffer[2], buffer[3] 각각은 1바이트 크기의 정수 값입니다. buffer 배열의 첫 4바이트를 나타냅니다.
              << 연산자는 비트를 왼쪽으로 이동시키는 연산입니다. buffer[0] << 24는 buffer[0]의 비트를 왼쪽으로 24비트 이동시킨 값을 의미합니다.
              마찬가지로 buffer[1] << 16, buffer[2] << 8, buffer[3]은 각각 비트를 왼쪽으로 16, 8, 0비트 이동시킨 값을 나타냅니다.
              | 연산자는 비트별 OR 연산을 수행합니다. 위의 네 개의 이동 연산을 수행한 결과를 비트별 OR 연산하여 하나의 정수 값으로 합쳐줍니다.
           */
          int length = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];

          print('Lengthaaa: $length');

          // 버퍼에 모든 데이터가 도착한 경우
          if (buffer.length >= length + 4) {
            // 데이터를 추출하고 사용
            List<int> receivedData = buffer.sublist(0, length + 4);
            _hexString = HEX.encode(receivedData);
            print('flutter 에서 데이터 수신');
            print(_hexString);

            // 사용한 데이터는 버퍼에서 삭제
            buffer.removeRange(0, length + 4);

            // 비동기 작업을 수행하고자 하는 경우, await 키워드로 Future를 기다릴 수 있습니다.
            await someAsyncTask();
            // 비동기 작업 후 추가적인 코드
          }
        }
      },
      onError: (e) {
        print('Socket error: $e');
        _disconnect();
      },
      onDone: () {
        print('Socket disconnected');
        _disconnect();
      },
      cancelOnError: false,
    );
  }


/*
  Future<void> _listenToSocket() async {
    _socket.listen(
          (List<int> data) async {
            // 데이터 수신 처리
            _hexString = HEX.encode(data);
            print('flutter 에서 데이터 수신');
            print(_hexString);

        // 비동기 작업을 수행하고자 하는 경우, await 키워드로 Future를 기다릴 수 있습니다.
        await someAsyncTask();
        // 비동기 작업 후 추가적인 코드
      },
      onError: (e) {
        print('Socket error: $e');
        _disconnect();
      },
      onDone: () {
        print('Socket disconnected');
        _disconnect();
      },
      cancelOnError: false,
    );
  }*/



  Future<void> someAsyncTask() async {
    // 비동기 작업을 수행하는 함수
    Map<String, dynamic> jsonObject = {
      'action': 'flutter',
      'resHexString': _hexString,
    };
    String jsonString = json.encode(jsonObject);

    print('jsonString');
    print(jsonString);
    //##_controller.runJavaScript("flutter2web('$jsonString')");
    _controller.evaluateJavascript(source: "flutter2web('$jsonString')");
  }


  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;
  }

  bool get connected {
    return _connected;
  }
}

javascript

const connect =_=>{
    Common.debugApp('connecting....');
    const message = {
        key: 'value',
        action: 'connect',
        hexString: 'aa',
    };
    window.flutter_inappwebview.callHandler('JsChannel', JSON.stringify(message));
};

const bufferTest =_=>{
    alert(11)
    const message = {
        action:'bufferTest',
        hexString: 'zz',
    };
    window.flutter_inappwebview.callHandler('JsChannel', JSON.stringify(message));
};

flutter.js

/**
 * flutter socket 에서 응답받은 hexString 을 받는다.
 * @param {*} data 
 */

export const flutter2web = data => {
    $Worker.receiveFlutterApp(data);
};

 

webview_flutter 사용한 웹뷰