Khi phát triển ứng dụng Flutter cho nền tảng Web, việc tương tác với các thư viện JavaScript (JS) có sẵn là một yêu cầu phổ biến. Bài viết này hướng dẫn cách sử dụng gói js để thiết lập cơ chế Interop (tương tác) giữa Dart và JavaScript thông qua ví dụ tích hợp bản đồ AMap.
1. Cấu hình môi trường
Để bắt đầu, bạn cần khai báo thư viện js trong tệp pubspec.yaml của dự án:
dependencies:
js: ^0.6.1+1
2. Định nghĩa giao diện Dart cho mã JavaScript
Để gọi các hàm hoặc khởi tạo các đối tượng từ JS, chúng ta cần tạo một tệp Dart đóng vai trò làm cầu nối. Sử dụng annotation @JS để định danh các thành phần JS tương ứng.
@JS('AMap')
library amap_interop;
import 'package:js/js.dart';
@JS('Map')
class GMap {
external GMap(dynamic container, MapConfig options);
external plugin(dynamic name, Function callback);
}
@JS()
@anonymous
class MapConfig {
external factory MapConfig({
GeoPoint center,
num zoom,
String viewMode,
});
}
Trong đoạn mã trên, từ khóa external cho Dart biết rằng việc triển khai thực tế của phương thức nằm ở phía JavaScript. Annotation @anonymous được sử dụng cho các lớp đại diện cho các đối tượng JS thuần túy (Object Literals), nơi chúng ta không gọi hàm khởi tạo bằng từ khóa new trong JS.
3. Xử lý các kiểu dữ liệu phức tạp
Khi làm việc với các đối tượng địa lý như tọa độ, chúng ta cần định nghĩa thêm các lớp bổ trợ:
@JS()
class GeoPoint {
external num getLng();
external num getLat();
external GeoPoint(num lng, num lat);
}
Đối với kiểu danh sách, cần lưu ý rằng mảng từ JavaScript khi truyền vào Dart thường được nhận diện dưới dạng List<dynamic>. Nếu bạn cố gắng ép kiểu trực tiếp sang List<String>, ứng dụng có thể gặp lỗi runtime. Cách an toàn nhất là xử lý danh sách động và chuyển đổi dữ liệu khi cần thiết.
4. Cơ chế Callback và Bất đồng bộ
Khi truyền một hàm Dart vào JavaScript dưới dạng callback, bạn phải bao bọc hàm đó bằng allowInterop để đảm bảo tính tương thích về ngữ cảnh thực thi.
gMap.plugin('AMap.ToolBar', allowInterop(() {
print('Plugin đã được tải xong');
}));
Để xử lý các Promise của JavaScript trong môi trường Dart, chúng ta sử dụng hàm promiseToFuture từ thư viện dart:js_util để chuyển đổi chúng thành Future.
import 'dart:js_util';
void initializeMap() async {
var loaderPromise = load(LoaderConfig(key: 'your_key', version: '2.0'));
try {
await promiseToFuture(loaderPromise);
var myMap = GMap('map-container', MapConfig(zoom: 12));
} catch (e) {
print('Lỗi khởi tạo: $e');
}
}
5. Hiển thị thành phần Web trên giao diện Flutter
Để hiển thị một thẻ HTML (ví dụ <div> chứa bản đồ) vào cây Widget của Flutter, chúng ta sử dụng HtmlElementView. Đầu tiên, cần đăng ký một viewFactory thông qua platformViewRegistry.
import 'dart:html';
import 'dart:ui' as ui;
// Đăng ký thẻ div để hiển thị bản đồ
void registerMapView(String viewId) {
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(viewId, (int id) {
return DivElement()
..id = 'map-container'
..style.width = '100%'
..style.height = '100%';
});
}
// Sử dụng trong Widget
@override
Widget build(BuildContext context) {
return HtmlElementView(viewType: 'unique-map-id');
}
6. Lưu ý về Rendering Engine
Flutter Web hỗ trợ hai trình kết xuất: HTML (DomCanvas) và CanvasKit (Skia).
- DomCanvas: Sử dụng các thẻ HTML và CSS, tương thích tốt với các thư viện JS thao tác trên DOM nhưng hiệu năng có thể hạn chế với đồ họa phức tạp.
- CanvasKit: Sử dụng WebAssembly để đưa Skia lên Web, cho hiệu năng mượt mà hơn nhưng có thể gặp vấn đề về tương tác sự kiện (click, scroll) khi lồng ghép các thành phần HTML bên thứ ba.