Platform-specific Code trong Flutter
1. Platform Detection
import 'dart:io';
if (Platform.isAndroid) {
// Android code
} else if (Platform.isIOS) {
// iOS code
} else if (Platform.isWindows) {
// Windows code
} else if (Platform.isMacOS) {
// macOS code
} else if (Platform.isLinux) {
// Linux code
}Với kIsWeb
import 'package:flutter/foundation.dart';
if (kIsWeb) {
// Web code
} else if (Platform.isAndroid) {
// Android code
}2. Platform Widgets
Cupertino vs Material
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Widget buildButton() {
if (Platform.isIOS) {
return CupertinoButton(
onPressed: () {},
child: Text('iOS Button'),
);
} else {
return ElevatedButton(
onPressed: () {},
child: Text('Android Button'),
);
}
}Platform-aware Widget
class PlatformButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
const PlatformButton({
super.key,
required this.text,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Platform.isIOS
? CupertinoButton(
onPressed: onPressed,
child: Text(text),
)
: ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
}3. Platform Channels
Gọi native code từ Flutter:
Dart side
import 'package:flutter/services.dart';
class BatteryService {
static const platform = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final result = await platform.invokeMethod('getBatteryLevel');
return result as int;
} on PlatformException catch (e) {
throw Exception('Failed to get battery: ${e.message}');
}
}
}Android (Kotlin)
// MainActivity.kt
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}iOS (Swift)
// AppDelegate.swift
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(
name: "com.example.app/battery",
binaryMessenger: controller.binaryMessenger
)
batteryChannel.setMethodCallHandler { call, result in
if call.method == "getBatteryLevel" {
self.receiveBatteryLevel(result: result)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(code: "UNAVAILABLE", message: "Battery unavailable", details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
}4. Conditional Imports
// interface.dart
abstract class StorageService {
Future<void> save(String key, String value);
Future<String?> load(String key);
}
// storage_mobile.dart
import 'package:shared_preferences/shared_preferences.dart';
class StorageServiceImpl implements StorageService {
Future<void> save(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
}
// ...
}
// storage_web.dart
import 'dart:html' as html;
class StorageServiceImpl implements StorageService {
Future<void> save(String key, String value) async {
html.window.localStorage[key] = value;
}
// ...
}
// storage.dart
export 'storage_stub.dart'
if (dart.library.io) 'storage_mobile.dart'
if (dart.library.html) 'storage_web.dart';5. Platform-specific Permissions
Android
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />iOS
<!-- ios/Runner/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>This app needs camera access</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access</string>6. Platform-specific UI Patterns
Back Button
// Android: Hardware back button
// iOS: Swipe gesture
// Customize với WillPopScope / PopScope
PopScope(
canPop: false,
onPopInvoked: (didPop) async {
if (didPop) return;
// Show confirmation
},
child: Scaffold(...),
)Pull to Refresh
RefreshIndicator(
onRefresh: _refresh,
// Material style on Android, Cupertino style configurable
child: ListView(...),
)7. Theme theo Platform
ThemeData buildTheme() {
if (Platform.isIOS) {
return ThemeData(
// iOS-like styling
appBarTheme: AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
),
);
} else {
return ThemeData(
// Material styling
useMaterial3: true,
);
}
}8. Plugin Packages
Nhiều packages xử lý platform-specific code cho bạn:
dependencies:
# Camera
camera: ^0.10.0
# Image picker
image_picker: ^1.0.0
# Permissions
permission_handler: ^11.0.0
# Local storage
shared_preferences: ^2.2.0
# Device info
device_info_plus: ^9.0.0
# URL launcher
url_launcher: ^6.2.0📝 Tóm tắt
| Approach | Khi nào dùng |
|---|---|
Platform.isX | Simple platform checks |
| Platform widgets | Different UI per platform |
| Platform channels | Native features không có package |
| Conditional imports | Platform-specific implementations |
| Platform | Native Language |
|---|---|
| Android | Kotlin/Java |
| iOS | Swift/Objective-C |
| Web | JavaScript |
| Desktop | C++/Swift |
Last updated on