1. Flutter untuk Multi-Platform
Flutter bukan hanya framework mobile — Flutter mendukung pengembangan untuk Web, Windows, macOS, dan Linux dari satu basis kode. Dengan single codebase, Anda membangun aplikasi untuk enam platform.
Sejak Flutter 3.x, dukungan web dan desktop sudah stable dan siap produksi.
Skia / Impeller
WebAssembly
Native rendering
1.1 Inisialisasi Proyek
# Buat proyek Flutter baru flutter create my_multiplatform_app cd my_multiplatform_app # Cek platform yang tersedia flutter devices # Jalankan di web flutter run -d chrome # Jalankan di Windows flutter run -d windows # Enable platform flutter config --enable-web flutter config --enable-windows-desktop flutter config --enable-macos-desktop flutter config --enable-linux-desktop
1.2 Struktur Proyek
my_multiplatform_app/ ├── lib/ │ └── main.dart # Kode utama (shared) ├── android/ # Konfigurasi Android ├── ios/ # Konfigurasi iOS ├── web/ # Konfigurasi Web │ ├── index.html │ ├── manifest.json │ └── favicon.png ├── windows/ # Konfigurasi Windows │ ├── CMakeLists.txt │ └── runner/ ├── macos/ # Konfigurasi macOS ├── linux/ # Konfigurasi Linux ├── test/ # Unit & widget tests └── pubspec.yaml
Gunakan kIsWeb dari package:flutter/foundation.dart untuk mendeteksi web. Untuk desktop, gunakan Platform.isWindows, Platform.isMacOS, atau Platform.isLinux dari dart:io.
2. Responsive Layout
Aplikasi multi-platform harus mendukung berbagai ukuran layar. Flutter menyediakan LayoutBuilder, MediaQuery, dan Flexible/Expanded.
import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
required this.tablet,
required this.desktop,
});
static bool isMobile(BuildContext context) =>
MediaQuery.of(context).size.width < 600;
static bool isDesktop(BuildContext context) =>
MediaQuery.of(context).size.width >= 1200;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1200) return desktop;
if (constraints.maxWidth >= 600) return tablet;
return mobile;
},
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dashboard')),
body: ResponsiveLayout(
mobile: ListView(children: [
_stat('Users', '1,234'), _stat('Revenue', 'Rp 50jt'),
]),
tablet: Row(children: [
Expanded(child: _stat('Users', '1,234')),
Expanded(child: _stat('Revenue', 'Rp 50jt')),
]),
desktop: Row(children: [
Expanded(child: _stat('Users', '1,234')),
Expanded(child: _stat('Revenue', 'Rp 50jt')),
Expanded(child: _stat('Orders', '892')),
]),
),
);
}
Widget _stat(String title, String value) {
return Card(child: Padding(
padding: const EdgeInsets.all(20),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title, style: const TextStyle(color: Colors.grey)),
Text(value, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
]),
));
}
}
2.1 Breakpoints Standar
| Ukuran | Breakpoint | Target |
|---|---|---|
| Compact | < 600dp | Mobile phone |
| Medium | 600–839dp | Tablet portrait |
| Expanded | 840–1199dp | Tablet landscape |
| Large | 1200–1599dp | Desktop |
| Extra Large | ≥ 1600dp | Ultrawide |
3. Adaptive Widgets
Flutter menyediakan widget adaptive yang menyesuaikan tampilan berdasarkan platform.
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AdaptiveScaffold extends StatelessWidget {
final String title;
final Widget body;
const AdaptiveScaffold({super.key, required this.title, required this.body});
@override
Widget build(BuildContext context) {
final isWide = MediaQuery.of(context).size.width > 800;
if (!kIsWeb && Platform.isIOS) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text(title)),
child: body,
);
}
if (isWide) {
return Scaffold(body: Row(children: [
NavigationRail(
selectedIndex: 0,
destinations: const [
NavigationRailDestination(icon: Icon(Icons.dashboard), label: Text('Home')),
NavigationRailDestination(icon: Icon(Icons.settings), label: Text('Settings')),
],
onDestinationSelected: (i) {},
),
const VerticalDivider(width: 1),
Expanded(child: body),
]));
}
return Scaffold(
appBar: AppBar(title: Text(title)),
body: body,
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
destinations: const [
NavigationDestination(icon: Icon(Icons.dashboard), label: 'Home'),
NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
}
}
Import dart:io tidak tersedia di Flutter Web. Selalu gunakan kIsWeb check sebelum menggunakan API dari dart:io.
4. Platform Channels
Platform Channels memungkinkan Flutter berkomunikasi dengan native code (Java/Kotlin, Swift/ObjC, C++).
4.1 MethodChannel
import 'package:flutter/services.dart';
class NativeBridge {
static const _channel = MethodChannel('com.myapp/native');
static Future getDeviceName() async {
try {
final name = await _channel.invokeMethod('getDeviceName');
return name ?? 'Unknown';
} on PlatformException catch (e) {
return 'Error: \${e.message}';
}
}
static Future sendNotification(String title, String body) async {
await _channel.invokeMethod('showNotification', {
'title': title, 'body': body,
});
}
}
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.myapp/native"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceName" -> result.success(android.os.Build.MODEL)
"showNotification" -> {
result.success(null)
}
else -> result.notImplemented()
}
}
}
}
4.2 EventChannel (Streaming)
class BatteryMonitor {
static const _ch = EventChannel('com.myapp/battery');
static Stream get stream =>
_ch.receiveBroadcastStream().map((v) => v as int);
}
// Usage
StreamBuilder(
stream: BatteryMonitor.stream,
builder: (ctx, snap) =>
Text('Battery: \${snap.data ?? 0}%'),
)
5. Plugin Development
Flutter plugin menggabungkan kode Dart dengan implementasi native untuk mengakses fitur platform.
# Buat plugin dengan template flutter create --template=plugin \ --platforms=android,ios,web,windows,macos,linux \ my_custom_plugin # Struktur yang dihasilkan: # my_custom_plugin/ # ├── lib/ # Dart public API # ├── android/src/main/kotlin # Kotlin implementation # ├── ios/Classes/ # Swift implementation # ├── windows/ # C++ implementation # ├── example/ # Example app # └── pubspec.yaml
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
abstract class MyPluginPlatform extends PlatformInterface {
MyPluginPlatform() : super(token: _token);
static final Object _token = Object();
static MyPluginPlatform _instance = MethodChannelMyPlugin();
static MyPluginPlatform get instance => _instance;
static set instance(MyPluginPlatform i) {
PlatformInterface.verifyToken(i, _token);
_instance = i;
}
Future getPlatformVersion() {
throw UnimplementedError();
}
}
6. Fitur Khusus Web
6.1 Web Renderers
| Renderer | Karakteristik | Kapan Digunakan |
|---|---|---|
| HTML | HTML/CSS/JS, ringan | Aplikasi teks-heavy |
| CanvasKit | Skia/Wasm, pixel-perfect | Grafik kompleks |
| Auto | Otomatis | Kebanyakan kasus |
flutter build web --web-renderer html flutter build web --web-renderer canvaskit flutter build web --wasm
6.2 URL Strategy & JS Interop
// URL Strategy - hilangkan '#' dari URL
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void main() {
usePathUrlStrategy();
runApp(const MyApp());
}
// JS Interop
import 'dart:js_interop';
@JS('window.navigator.userAgent')
external String get userAgent;
7. Fitur Khusus Desktop
import 'package:window_manager/window_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized();
const opts = WindowOptions(
size: Size(1280, 800),
minimumSize: Size(800, 600),
center: true,
title: 'My App',
titleBarStyle: TitleBarStyle.hidden,
);
await windowManager.waitUntilReadyToShow(opts, () async {
await windowManager.show();
await windowManager.focus();
});
runApp(const MyApp());
}
import 'package:file_picker/file_picker.dart'; FuturepickFile() async { FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['pdf', 'doc', 'jpg', 'png'], allowMultiple: true, ); if (result != null) { for (var file in result.files) { print('File: \${file.name}, Size: \${file.size}'); } } }
8. Deployment
# Build untuk production flutter build web --release --web-renderer canvaskit # Deploy ke Firebase Hosting firebase deploy --only hosting # Deploy ke Nginx sudo cp -r build/web/* /var/www/html/
# Build Windows flutter build windows --release # Output: build/windows/x64/runner/Release/ # Build macOS flutter build macos --release # Build Linux flutter build linux --release # Packaging MSIX (Windows) flutter pub run msix:create
name: Build All Platforms
on:
push:
tags: ['v*']
jobs:
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter build web --release
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter build windows --release
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter build macos --release
Gunakan Flutter Flavor untuk membuat konfigurasi build berbeda (dev, staging, production) dengan environment berbeda per target.