2023년 1월 25일 유튜브 Flutter 공식 채널에서 Flutter forward 2023이 진행되었다. Flutter는 3.7.0로, Dart는 3.0으로 업데이트가 되었다.
Element Embedding의 개념
Flutter 콘텐츠를 모든 표준 웹에 추가할 수 있습니다. 이러한 방식으로 통합하면 Flutter는 단순히 웹 구성 요소로 전환되어 웹 DOM과 원활하게 통합되고 CSS 선택기 및 변환을 사용하여 부모 Flutter 개체의 스타일을 지정할 수도 있습니다.
→ 플러터에서 제공한 웹을 보았을 때, 웹에서 플러터앱을 사용할 수 있는 기능을 말한다.
Element Embedding을 사용할 수 있는 웹
💫 https://flutter-forward-demos.web.app/#/
위의 사이트는 flutter forward에서 element embedding을 선보인 사이트이다. 이를 통해 HTML기반 웹 페이지에 포함된 간단한 flutter앱을 만질 수 있다. 또한, JavaScript 이벤트 핸들러와 HTML 버튼을 사용하여 Flutter 상태를 변경하는 방법도 보여주고 있다.
Element Embedding 활용하기
전제조건
- flutter channel 중 master 채널을 사용해야 한다.
- Dart 버전은 3.0.0이상이어야 한다.
master 채널로 변경하기
flutter channel
을 쳐보면 아래와 같이 나온다. 기본적으로 안정성을 위해 stable로 설정되어 있다.
channel을 변경하게 되면 아래와 같이 오류가 뜨게 된다. 아래에 해결책이 나온다.
flutter upgrade를 해야지만 해당 채널로 변경할 수 있다고 나온다. 그러면 전제 조건 2를 하게 되면 전제 조건1도 해결되게 된다. 다음으로 넘어가자.
Dart 3.0.0버전으로 변경하기
flutter --version
을 쳐서 flutter version을 확인해준다. 아래와 같이 3.3.10이 나온다면 3.7버전으로 업그레이드해야 한다.
3.7버전으로 업그레이드해주면 dart도 자동으로 업그레이드 되게 된다. 그리고 나서 flutter channel도 확인해보면 자동으로 master로 변경되게 된다.
만약 변경되지 않았다면 flutter channel master을 쳐서 변경해준다.
프로젝트 생성하기
💫 내가 할 프로젝트는 카운터 상태를 조정했을 때 js에 있는 카운터 상태도 조정되게 웹을 만들 것이다.
flutter create web_embedding --platforms web
→ 여기서 flutter 프로젝트를 생성하는 것을 말하면, —platforms은 flutter앱을 어디서 여는 것을 지정한다.
- —platforms web : 웹에서 flutter 앱을 여는 것을 말한다.
js 패키지 추가
flutter pub add js
→ Dart 코드에서 JavaScript API를 호출하거나 그 반대로 호출하려는 경우에 js 패키지를 사용한다. 또한, js 패키지의 두 번째 라이브러리인 js_util가정적 주석 API로 JavaScript를 래핑할 수 없을 때 사용할 수 있는 저수준 유틸리티를 제공한다.
코드 작성
프로젝트를 생성해서 보면 아래와 같이 파일 생성된다. main.dart를 들어가서 코드를 작성해주도록 한다. MyApp과 MyHomePage이 기본적으로 생성되어 있다.
플러터 앱에서 셋팅
위에서 본 웹에서 웹이랑 플러터앱이 서로 통신할려면 셋팅을 해야 한다. dart에서 쓰는 변수들을 js단에서 사용하려면, js에서 접근 가능하도록 해야 한다.
1) state 전체를 export하기
// main.dart
import 'package:js/js.dart' as js;
import 'package:js/js_util.dart' as js_util;
위에서 js패키지 설치한 것을 가져온다.
// main.dart
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
@js.JSExport()
class _MyHomePageState extends State<MyHomePage> {}
- @JSExport : @js.JSExport()을 붙임으로, dart객체인 _MyHomePageState를 내보낼 수 있다.
void initState() {
super.initState();
final export = js_util.createDartExport(this);
js_util.setProperty(js_util.globalThis, '_appState', export);
}
- createDartExport : 내보낼 객체를 js객체 리터럴로 생성되게 된다. 즉, JavaScript 코드에서 액세스할 수 있는 Dart 개체에 대한 참조가 있는 JavaScript 개체를 만드는 데 사용된다.
- setProperty : globalThis, JS에서 사용할 변수명, (3) 생성한 객체를 인자로 넣어 호출한다.
- globalThis : 각 환경에서 접근할 수 있는 전역 객체를 말한다.
- export : Dart 개체와 상호 작용하기 위해 JavaScript 코드에서 액세스할 수 있다.
- setProperty()과 callMethod()는 JavaScript 속성을 설정하고 JavaScript 함수를 호출하는 데 사용된다.
2) 웹에서 플러터앱 보이게 하기
아래와 같이 main.dart에 설정한 변수를 가져와서 쓸려면 js에 설정해줘야 한다. 내 경우에는 js파일을 따로 만들어 관리하지 않고 코드가 짧기 때문에 index.html에 작성하여 관리하였다.
// index.html
let appState = window._appState;
그리고 플러터앱과 input변수의 상호작용을 보여주기 위해 설정해야 한다. html 중간에 삽입해준다. 그리고 웹페이지를 새로고침했을 경우 플러터앱이 안 보인다.
<section class="contents">
<aside id="demo_controls">
<fieldset id="interop">
<legend>JS Interop</legend>
<!--This is the value box-->
<label for="value">
Value
<input id="value" value="" type="text" readonly />
</label>
<!--This is the button-->
<input id="increment" value="Increment" type="button" />
</fieldset>
</aside>
<!--This is the div which contains the flutter app-->
<article>
<div id="flutter_target" class="center"></div>
</article>
</section>
여기서 설정을 더 해줘야 하는데, index.html의 맨아래에 보면 보이는 아래의 코드를
window.addEventListener('load', function (ev) {
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function (engineInitializer) {
engineInitializer.initializeEngine().then
(function (appRunner) {
appRunner.runApp();
});
}
});
});
아래와 같이 바꿔주면 된다.
window.addEventListener("load", function (ev) {
let target = document.querySelector("#flutter_target");
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function (engineInitializer) {
let appRunner = await engineInitializer.initializeEngine({
hostElement: target,
});
await appRunner.runApp();
},
});
});
- js코드로 위에서 index.html에 설정한 플러터앱의 id를 가져온다.
- 그리고 이것이 로드될 수 있도록 hostElement에 설정해준다.
그러고 나면 플러터앱이 보여야 하는데 보이지 않는다. 안 보이는 것이 정상이다.
<aside> 💫 이 경우는 플러터앱의 세로의 값이 설정이 안되어서 안 보였다.(height가 0이었다) 그래서 style을 줘서 div에 height속성을 줘서 설정하면 된다.
</aside>
3) 특정 함수를 export하기
@js.JSExport()
void increment() {
setState(() {
counter++;
});
js_util.callMethod<void>(js_util.globalThis, 'setCounter', [counter]);
}
위 코드를 main.dart에 넣어준다.
// index.html
let counter = document.querySelector("#counter");
window.setCounter = (count) => {
counter.value = count;
}
위 코드를 넣어 웹의 input에도 플러터의 값이 올라가면 같이 반영되도록 한다. 그리고 나서 main.dart로 돌아와서
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
));
floatingActionButton을 클릭시에 Text값이 올라가므로 위에서 설정한 코드의 변수대로 변경해준다.
실행화면
위와 같이 변경된다.
🔗 참고
https://medium.com/flutter-community/element-embedding-in-flutter-dda770dad792