[Flutter 3.7] Element Embedding

dosilv·2023년 3월 4일
5
post-thumbnail

5000년만의 포스팅..ㅎㅎ

올해 초 플러터 3.7이 공개되었다. 그래픽 퍼포먼스 개선, Material 3, pattern matching 등 여러 업데이트 사항이 있었지만 그 중 이상하게도 젤 흥미로웠던 element embedding에 대해 파헤쳐 봄!



🍍 Element Embedding이란?

간단하게 말해서 네이티브 웹플러터 앱을 집어넣을 수 있는 기능이다. 물론 플러터는 크로스플랫폼 앱인 만큼 기존에도 웹앱으로 동작하긴 했다. 하지만 이전에는 플러터 앱이 html의 body 전체를 오버라이딩하는 방식으로만 작동했었는데, 이번 업데이트로 일반 웹 내부에 앱을 컴포넌트처럼 넣어서 보여줄 수 있다는 뜻!

플러터에서 제공한 샘플 웹

개발자도구로 확인해보니 전체 프레임과 왼쪽 버튼들은 html로 구성된 웹 영역이고, 오른쪽의 카운터 앱 화면은 플러터 앱이다. 더 신기한 건 CSS로 플러터 앱 영역을 조절할 수도 있고, 또 네이티브 웹이나 플러터 앱에서 일어나는 인터랙션들이 서로 영향을 끼침..!(카운트 숫자가 바뀌는 등)

앱에서 웹뷰를 띄워 자바스크립트 핸들러로 통신하는 건 해봤어도 웹에 플러터 앱을 넣어 이렇게 간단하게 상호작용이 되는 게 너무 신기했다.




🍍 직접 해보자!

🍹1) 웹에 앱 embed하기

예제만으로 호기심이 충족되지 않아서(+재밌어보여서) 직접 해봤당. 새로운 기능이라 그런지 구글링해봐도 아직 자료가 별로 없어서 처음엔 좀 헤맸다. 웹 프로젝트를 만들어서 다트 파일을 추가하는 건지, 아니면 플러터 앱에 html을 추가하는 건지... 대체 어디에 뭘 해서 합치는 거지? 싶었는데, 플러터 프로젝트에 기본적으로 생성되는 웹앱용 index.html 파일을 수정해서 만들 수 있었다.

방법을 찾고 나니까 생각보다 간단했음! html이랑 자바스크립트를 홀랑 다 까먹어버려서 고생했을 뿐...


  1. 우선 플러터 프로젝트를 생성한다.
flutter create [프로젝트명]

  1. web 폴더에 있는 index.html을 열고, html을 수정해 원하는 웹 형태를 구성한다. 나는 style.css 파일도 따로 만들어서 연결해줌!

  2. 플러터 앱을 임베딩하고 싶은 영역에 <div> 태그를 추가하고, id를 붙여준다. 나는 app_area라고 명명했다.


  1. 스크립트를 분리하기 위해 script.js 파일을 생성하고, index.html에 아래 태그를 추가해서 연결해준다.
  <script type="text/javascript" src="script.js"></script>


  1. 원래 있던 <scrpit> 내부의 코드는 복사한 뒤 index.html에서 삭제하고, 4번에서 생성한 스크립트 파일에 붙여넣은 후 다음과 같이 수정한다. 기존 코드는 그냥 바디 전체에서 플러터 앱이 돌도록 했는데, querySelector로 3번에서 선언한 div를 선택하고 그 안에서 플러터 앱을 넣도록 고친 것!
// before
window.addEventListener('load', function (ev) {
  _flutter.loader.loadEntrypoint({
    serviceWorker: {
      serviceWorkerVersion: serviceWorkerVersion,
    },
    onEntrypointLoaded: function (engineInitializer) {
      engineInitializer.initializeEngine().then
      (function (appRunner) {
        appRunner.runApp();
      });
    }
  });
});
// after
window.addEventListener('load', function (ev) {
  var appArea = document.querySelector("app_area");
  _flutter.loader.loadEntrypoint({
    serviceWorker: {
      serviceWorkerVersion: serviceWorkerVersion,
    },
    onEntrypointLoaded: function (engineInitializer) {
      engineInitializer.initializeEngine({
        hostElement: appArea,
      }).then(function (appRunner) {
        appRunner.runApp();
      });
    }
  });
});

그리고 flutter run을 해서 크롬으로 열면...!

아무일도 일어나지 않았다...!🥲

뭐가 잘못된 건지 몰라서 한참동안 이것저것 바꾸면서 원인을 찾았는데 어이없을 만큼 간단한 문제였다. 앱이 들어갈 사이즈를 지정안해서..ㅎㅎ;;

#app_area {
    width: 320px;
    height: 480px;
}

css 파일에서 픽셀을 지정해주고 새로고침하니까 앱이 나타났다! 🎉🎉🎉




🍹2) 웹-앱 통신하기

다음으로 자바스크립트 영역과 플러터 앱 영역이 통신할 수 있도록 해보자.


우선 프로젝트에 js 패키지를 추가한다.

flutter pub add js

이때 중요한 것! 패키지 버전이 0.6.7 이상이어야 하는데, 만약 dart sdk와 충돌이 나서 안 된다면... sdk 버전을 3.0.0 이상으로 업그레이드시켜줘야 한다. 현재 해당 sdk는 베타채널로만 풀려있는데(23.02.20 기준) 나는 채널을 옮겨서 업그레이드해줌!



🍋 Dart에서 JS로 export하기

다트에서 정의한 변수, 함수 등을 자바스크립트단에서 사용할 수 있도록 export해보자. state 전체를 내보내서 모든 필드에 접근 가능하도록 할 수 도 있고, 특정한 메서드만 지정해서 내보낼 수도 있다.

🍸 state 전체를 export하는 방법

// main.dart
()
class _MyAppState extends State<MyApp> {
...


void initState() {
  final stateToexport = createDartExport(this);
  setProperty(globalThis, 'myAppState', stateToexport);
  super.initState();
  }
  
  ...
}

State에 @JSExport 어노테이션을 붙여주고, createDartExport(this)로 export할 객체를 생성한다. 그리고 setProperty 메서드에 (1) globalThis*, (2) JS에서 사용할 변수명, (3) 생성한 객체를 인자로 넣어 호출한다.
*globalThis는 각 환경에서 접근할 수 있는 전역 객체를 가리킨다고 함!

그리고 자바스크립트 내에서 사용할 때는 window 객체를 통해 위 (2)에서 설정한 이름으로 접근하면 된다.

// script.js
window.myAppState.(함수 혹은 변수명)


🍸 특정 method를 export하는 방법

// main.dart

void initState() {
  final setThemeColorToExport = allowInterop(setThemeColor);
  setProperty(globalThis, 'setThemeColor', setThemeColorToExport);
  super.initState();
}

void setThemeColor(List rgbList) {
  setState(() {
    themeColor = Color.fromRGBO(rgbList[0], rgbList[1], rgbList[2], 1);
  });
}

export할 메서드를 allowInterop이라는 함수에 넣어 이를 할당하고, 마찬가지로 setProperty(1) globalThis, (2) JS에서 사용할 함수명, (3) 생성한 객체를 넣어 호출한다.
나는 테마 컬러를 바꾸는 메서드를 생성해 JS에서 컨트롤할 수 있도록 넘겼다.

사용할 때는 똑같이 window 객체를 이용해 호출할 수 있음!

// script.js
function updateColor(e) {
  let bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  let rgbList = bgColor.split("(")[1].split(")")[0].split(", ").map((e) => parseInt(e));
  window.setThemeColor(rgbList);
}

updateColor라는 함수 내부에서 다트 함수를 호출하도록 했고, 이벤트리스너로 색상별 버튼에 달아줬다.





🍋 JS 함수 Dart에서 사용하기

플러터앱에서 자바스크립트 함수를 사용하는 건 더 쉽다! 우선 스크립트 파일에서 window 객체에 원하는 함수를 정의한다. (함수명 앞에 'window.' 붙이기)
나는 버튼을 눌러 카운터 숫자가 올라갈 때마다 웹의 input에서도 해당 값이 반영되도록 하는 함수를 정의했다.

// script.js
let counter = document.querySelector("#counter");

window.setCounter = (count) => {
   counter.value = count;
}

그리고 다트 파일로 돌아와 callMethod 함수에 (1) globalThis, (2) JS에서 정의한 함수명, (3) 전달할 파라미터 리스트를 넣어 호출하면 됨! 이때 당연히 정의한 함수의 파라미터(인자)와 전달하는 파라미터의 형식을 맞춰줘야 한다.

// main.dart
 void _incrementCounter() {
   setState(() {
     _counter++;
   });
   callMethod(globalThis, 'setCounter', [_counter]);
 }





🍍 완성!

그렇게 쪼금 더 다듬어서 완성된 플러터 웹~~~ 호스팅도 완료 >>> 바로가기 🔍
굳이 호스팅까지 한 이유는! 다시 벨로그를 성실하게 쓰고.. 결과물 앱 페이지들을 저기 추가하겠다는 나만의 포부...💪🏻🌟



📚 References
Whats Next For Flutter
Element Embedding in Flutter Web
dart:js library - Dart API
dart:js_util library - Dart API

profile
DevelOpErUN 성장일기🌈

2개의 댓글

comment-user-thumbnail
2023년 4월 7일

와.. 플러터 신기해요

답글 달기
comment-user-thumbnail
2023년 5월 30일

[React] Drag and Drop 기능 구현하기
위 글에 댓글 달았는데...전체 적인 코드 좀 알 수 있을까요

답글 달기