Chào các dân chơi hệ Flutter! Anh Creyt lại lên sóng với một chủ đề mà nhiều khi anh em mình hay né, nhưng thực ra nó lại là một “siêu năng lực” khi cần thiết: PlatformView.
PlatformView là gì mà “ghê gớm” vậy?
Để anh Creyt kể cho nghe một câu chuyện thế này. Tưởng tượng Flutter của chúng ta là một đầu bếp siêu đẳng, có thể nấu đủ mọi món ngon từ Âu sang Á, từ món chay đến món mặn (tức là tạo ra mọi loại UI bằng Flutter widgets). Nhưng đôi khi, có những món đặc sản “gia truyền” mà chỉ có đầu bếp nhà hàng bên cạnh (hệ điều hành native như Android, iOS) mới làm ra hương vị chuẩn chỉnh được. Ví dụ, món "Bản đồ Google" hay "Trình duyệt web siêu tốc" chẳng hạn. Bếp nhà Flutter có thể cố gắng làm một phiên bản tương tự, nhưng không bao giờ đạt được độ ngon, độ mượt mà, và đầy đủ tính năng như bản gốc.
Lúc này, PlatformView chính là cái “người vận chuyển đồ ăn chuyên nghiệp” của chúng ta. Nó không tự nấu, mà nó chỉ giúp mang nguyên cái món đặc sản "gia truyền" đó từ nhà hàng native về đặt lên bàn tiệc Flutter của bạn, mà vẫn giữ nguyên được hương vị, độ nóng hổi và chất lượng đỉnh cao. Nghĩa là, PlatformView là một widget đặc biệt trong Flutter, cho phép bạn nhúng trực tiếp các UI components (view) được render bởi hệ điều hành native (Android View hoặc iOS UIKit View) vào trong cây widget của ứng dụng Flutter.
Để làm gì? Đơn giản là để:
- Tận dụng sức mạnh Native: Khi bạn cần dùng các tính năng, hiệu năng, hoặc giao diện mà native cung cấp tốt hơn, hoặc Flutter chưa có widget tương đương (ví dụ: Google Maps SDK, WebView, AdMob, các SDK phần cứng chuyên biệt).
- Khắc phục giới hạn của Flutter: Một số trường hợp Flutter không thể tái tạo hoàn hảo một UI native phức tạp, hoặc việc tái tạo sẽ tốn quá nhiều công sức và không hiệu quả về hiệu năng.
Code Ví Dụ Minh Hoạ: "Trình duyệt mini" với WebView
Ví dụ kinh điển nhất của PlatformView là WebView. Thay vì viết một trình duyệt từ đầu trong Flutter, chúng ta dùng webview_flutter plugin, mà bản thân nó lại dùng PlatformView để nhúng WebView native của Android và iOS. Cùng xem nhé!
Đầu tiên, bạn cần thêm webview_flutter vào pubspec.yaml:
dependencies:
flutter:
sdk: flutter
webview_flutter: ^4.2.2 # Hoặc phiên bản mới nhất
webview_flutter_android: ^3.9.0 # Cần thiết cho Android
webview_flutter_wkwebview: ^3.7.1 # Cần thiết cho iOS
Cấu hình Native (quan trọng lắm nha!):
-
Android: Mở
android/app/src/main/AndroidManifest.xmlvà đảm bảo có quyềnINTERNET:<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <application ... android:usesCleartextTraffic="true" <!-- Chỉ dùng cho dev, không khuyến khích cho production với HTTP --> ... </application> </manifest> -
iOS: Mở
ios/Runner/Info.plistvà thêm:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>(Cũng như Android,
NSAllowsArbitraryLoadschỉ nên dùng cho dev, hãy cấu hình cụ thể nếu bạn có các URL HTTP trong production).
Bây giờ là code Flutter:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget {
const WebViewExample({Key? key}) : super(key: key);
@override
State<WebViewExample> createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Cập nhật tiến độ tải trang
debugPrint('WebView is loading (progress: $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://youtube.com')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent; // Ngăn không cho điều hướng đến YouTube
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse('https://flutter.dev')); // Tải trang flutter.dev
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter WebView Demo')),
body: WebViewWidget(controller: controller), // Đây là nơi PlatformView hoạt động!
);
}
}
void main() {
runApp(const MaterialApp(home: WebViewExample()));
}
Trong ví dụ trên, WebViewWidget chính là cái "người vận chuyển" PlatformView đó. Nó lấy một WebViewController đã được cấu hình và "nhúng" cái WebView native vào ứng dụng Flutter của chúng ta. Bạn sẽ thấy một trình duyệt web mini hiển thị ngay trong app của mình, mượt mà và đầy đủ tính năng như khi bạn dùng trình duyệt Safari hay Chrome vậy.
Mẹo (Best Practices) từ Anh Creyt để "chơi" với PlatformView
- "Dùng đúng lúc, đúng chỗ": PlatformView không phải là giải pháp cho mọi vấn đề. Nếu Flutter có widget tương đương hoặc bạn có thể xây dựng UI đó hiệu quả bằng Flutter, hãy ưu tiên Flutter. Dùng PlatformView chỉ khi bạn thực sự cần tận dụng sức mạnh native hoặc khi không có lựa chọn nào khác tốt hơn. Nó có thể có overhead về hiệu năng và tài nguyên.
- "Hiểu rõ ranh giới": Khi nhúng PlatformView, bạn đang làm việc với hai thế giới riêng biệt (Flutter và Native). Tương tác giữa chúng có thể phức tạp. Nếu cần giao tiếp sâu giữa Flutter và view native, bạn sẽ phải dùng
MethodChannelhoặcEventChannelđể gửi/nhận dữ liệu hai chiều. Đó là một chủ đề khác mà anh em mình sẽ "đào" sau. - "Thử nghiệm đa nền tảng": Hiệu năng và trải nghiệm của PlatformView có thể khác nhau đáng kể giữa Android và iOS, và giữa các phiên bản hệ điều hành. Luôn luôn test kỹ trên cả hai nền tảng và nhiều loại thiết bị.
- "Quản lý vòng đời": Đảm bảo view native được khởi tạo và hủy đúng cách. Các plugin như
webview_flutterthường đã xử lý tốt việc này, nhưng nếu bạn tự viết PlatformView, hãy cẩn thận vớidispose()để tránh rò rỉ bộ nhớ. - "Tối ưu hiệu năng": Hạn chế số lượng PlatformView cùng lúc. Nếu bạn có nhiều PlatformView trong một
ListViewhoặcPageView, hãy cân nhắc việc lazy loading hoặc chỉ hiển thị PlatformView khi nó thực sự cần thiết để tránh làm chậm ứng dụng.
Ví Dụ Thực Tế: Ai đã dùng PlatformView rồi?
- Google Maps: Hầu hết các ứng dụng Flutter có tích hợp bản đồ Google Maps (thông qua
google_maps_flutterplugin) đều đang dùng PlatformView để nhúng native Google Maps SDK. Đây là một ví dụ điển hình về việc tận dụng UI native phức tạp. - Quảng cáo (AdMob, Facebook Audience Network): Các banner quảng cáo hoặc quảng cáo interstitial thường được nhúng qua PlatformView để đảm bảo hiển thị đúng định dạng và tương tác tốt nhất với SDK quảng cáo native.
- Trình duyệt nhúng (In-app browser): Như ví dụ
WebViewở trên, rất nhiều ứng dụng đọc báo, thương mại điện tử, hoặc các ứng dụng cần hiển thị nội dung web mà không muốn người dùng thoát ra ngoài đều dùng PlatformView. - Video Players (đặc biệt là các player cao cấp): Một số thư viện video player phức tạp có thể dùng PlatformView để nhúng native player (như ExoPlayer trên Android, AVPlayer trên iOS) nhằm đạt hiệu suất phát video tối ưu và hỗ trợ các định dạng chuyên biệt.
Thử nghiệm và Nên Dùng Cho Case Nào?
Anh Creyt đã từng "vật lộn" với việc tích hợp một SDK quét mã vạch chuyên dụng của một hãng thứ 3 vào một ứng dụng Flutter. Ban đầu, anh nghĩ có thể dùng Camera plugin của Flutter và xử lý logic quét mã vạch hoàn toàn bằng Dart. Nhưng thực tế, SDK đó có một native UI riêng để hiển thị luồng camera và các hiệu ứng quét rất đặc thù, mà việc tái tạo nó trong Flutter vừa khó, vừa không đạt được hiệu năng như native. Cuối cùng, giải pháp tối ưu nhất là dùng PlatformView để nhúng nguyên cái native view của SDK đó vào app Flutter. Bài học là: đừng ngại "đụng" đến native khi nó là giải pháp tốt nhất!
Nên dùng PlatformView khi:
- Bạn cần hiển thị bản đồ tương tác (Google Maps, Apple Maps).
- Bạn cần một trình duyệt web đầy đủ tính năng bên trong ứng dụng.
- Bạn cần tích hợp các SDK native phức tạp mà Flutter chưa có wrapper (ví dụ: một số SDK của ngân hàng, thanh toán, hoặc thiết bị IoT chuyên biệt, camera custom).
- Bạn cần hiển thị quảng cáo native từ các nền tảng lớn.
- Bạn cần hiệu năng đồ họa cao cấp hoặc các tính năng UI rất đặc thù mà Flutter widget khó lòng đáp ứng.
Không nên/Cần cân nhắc kỹ khi:
- Bạn chỉ muốn hiển thị một UI đơn giản mà Flutter có thể làm tốt (ví dụ: một nút bấm, một đoạn text). Dùng PlatformView cho những thứ này là "dao mổ trâu giết gà".
- Bạn muốn kiểm soát hoàn toàn giao diện và hành vi qua Flutter mà không muốn dính dáng đến native logic.
- Bạn lo ngại về kích thước ứng dụng tăng lên (do phải bundling native SDK).
- Bạn muốn tránh sự phức tạp khi debug tương tác giữa Flutter và native, đặc biệt là khi có lỗi phát sinh từ phía native.
Nhớ nhé, PlatformView là một công cụ cực mạnh mẽ, nhưng cũng giống như mọi "vũ khí" khác, phải hiểu rõ nó thì mới dùng hiệu quả được. Đừng lạm dụng, nhưng cũng đừng sợ hãi khi cần đến nó. Đó chính là cách để bạn biến ứng dụng Flutter của mình thành một "cỗ máy" đa năng, kết hợp tinh hoa của cả hai thế giới!
Chúc anh em code mượt, app chất! Hẹn gặp lại trong bài giảng tiếp theo!
Thuộc Series: Flutter
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!