☀️Siang
Mobile

Flutter untuk Pemula: Build App Cross-Platform

GRATIS

Panduan lengkap membangun aplikasi mobile cross-platform dengan Flutter — dari dasar Dart, widget system, stateless vs stateful, layout, navigasi, formulir, hingga HTTP request ke API

Artikel: Flutter Dasar Artikel: Flutter Dasar


1. Pengenalan Flutter

Flutter adalah framework UI open-source yang dikembangkan oleh Google untuk membangun aplikasi mobile, web, dan desktop dari satu basis kode. Menggunakan bahasa pemrograman Dart, Flutter memungkinkan developer membangun aplikasi yang berjalan native di Android, iOS, Windows, macOS, Linux, dan web — hanya dengan satu codebase.

Flutter pertama kali diperkenalkan pada tahun 2017 dan sejak itu telah digunakan oleh perusahaan besar seperti Google, BMW, Alibaba, dan banyak lainnya. Keunggulan utama Flutter adalah hot reload yang memungkinkan perubahan kode langsung terlihat tanpa restart aplikasi, serta performa tinggi yang mendekati native berkat rendering engine sendiri.

Mengapa Memilih Flutter?

Keunggulan Penjelasan
Satu CodebaseTulis sekali, jalankan di Android, iOS, Web, Desktop — menghemat waktu dan biaya pengembangan
Hot ReloadPerubahan kode langsung terlihat dalam hitungan milidetik tanpa kehilangan state
Performa NativeDikompilasi ke ARM machine code, menggunakan rendering engine Skia — performa mendekati native
Widget KayaRatusan widget Material Design dan Cupertino yang sudah siap pakai
Komunitas BesarEkosistem package pub.dev yang sangat kaya, dokumentasi lengkap
Gratis & Open SourceBSD license, tidak ada biaya lisensi, bebas digunakan untuk proyek komersial
Google SupportDikembangkan dan dipelihara oleh Google, digunakan di produk-produk Google

Flutter vs Alternatif Lain

Aspek Flutter React Native Native (Swift/Kotlin)
BahasaDartJavaScript/TypeScriptSwift / Kotlin
RenderingSkia (custom engine)Native bridgeNative
Performa🟢 Sangat baik🟡 Baik🟢 Terbaik
Hot Reload✅ Sangat cepat✅ Fast Refresh⚠️ Terbatas
UI ConsistencySama di semua platformNative feel per platformNative per platform
Kurva BelajarMudah-SedangMudah (jika tahu React)Sedang-Sulit
Diagram: Arsitektur Flutter
Arsitektur Flutter

Flutter Engine (C++) — Skia + Dart VM

Platform Embedder (Android / iOS / Web / Desk)

Rendering Engine

Material Lib

Anim Engine

Gesture Detect



2. Dasar Pemrograman Dart

Sebelum mulai dengan Flutter, kita perlu memahami dasar-dasar Dart — bahasa pemrograman yang digunakan oleh Flutter. Dart dikembangkan oleh Google dan memiliki sintaks yang familiar bagi yang pernah menggunakan Java, JavaScript, atau C#.

Instalasi Flutter SDK

Diagram: Layout Widgets
Layout Widgets

Item 1 Item 2 Item 3 ... ListView (↓ scroll) Gr...

Child1 Child2 Row (→) Column (↓) Stack (↑)

Child1 Child2 Child3

Child3 Child2 Child1 ← paling atas ← paling bawah

Row & Column

Dart — Row & Column
// Row — Layout horizontal
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Icon(Icons.star, color: Colors.yellow, size: 40),
    Icon(Icons.star, color: Colors.yellow, size: 40),
    Icon(Icons.star, color: Colors.yellow, size: 40),
  ],
)

// Column — Layout vertikal
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    Text('Judul', style: TextStyle(fontSize: 24)),
    SizedBox(height: 8),
    Text('Subjudul', style: TextStyle(fontSize: 16)),
    SizedBox(height: 16),
    ElevatedButton(onPressed: () {}, child: Text('Aksi')),
  ],
)

// Expanded & Flexible — Mengisi ruang yang tersisa
Row(
  children: [
    Expanded(
      flex: 2,
      child: Container(color: Colors.blue, height: 100),
    ),
    SizedBox(width: 8),
    Expanded(
      flex: 1,
      child: Container(color: Colors.red, height: 100),
    ),
  ],
)

ListView & GridView

Dart — ListView & GridView
// ListView — Scrollable list
ListView(
  children: [
    ListTile(title: Text('Item 1'), leading: Icon(Icons.star)),
    ListTile(title: Text('Item 2'), leading: Icon(Icons.book)),
    ListTile(title: Text('Item 3'), leading: Icon(Icons.music)),
  ],
)

// ListView.builder — Efficient untuk list panjang
ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('Item ${index + 1}'),
      leading: CircleAvatar(child: Text('${index + 1}')),
    );
  },
)

// GridView — Grid layout
GridView.count(
  crossAxisCount: 2,
  crossAxisSpacing: 8,
  mainAxisSpacing: 8,
  padding: EdgeInsets.all(8),
  children: List.generate(6, (index) {
    return Card(
      child: Center(
        child: Text('Grid ${index + 1}',
            style: TextStyle(fontSize: 18)),
      ),
    );
  }),
)
💡 MainAxisAlignment vs CrossAxisAlignment

MainAxisAlignment mengatur posisi sepanjang sumbu utama (Row → horizontal, Column → vertikal). CrossAxisAlignment mengatur posisi sepanjang sumbu silang. Nilai umum: start, center, end, spaceBetween, spaceEvenly, spaceAround.



Flutter menggunakan sistem Navigator berbasis stack untuk berpindah antar halaman (route). Setiap halaman di-push ke stack dan di-pop saat kembali.

Navigator Dasar

Dart — Navigasi Dasar
import 'package:flutter/material.dart';

// ===== HALAMAN UTAMA =====
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Beranda')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                // Push halaman baru ke stack
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => DetailPage(
                      itemId: 42,
                      itemName: 'Produk Flutter',
                    ),
                  ),
                );
              },
              child: Text('Buka Detail'),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                // Push dan ganti halaman (tidak bisa kembali)
                Navigator.pushReplacement(
                  context,
                  MaterialPageRoute(builder: (context) => LoginPage()),
                );
              },
              child: Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}

// ===== HALAMAN DETAIL (menerima data) =====
class DetailPage extends StatelessWidget {
  final int itemId;
  final String itemName;

  const DetailPage({required this.itemId, required this.itemName});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(itemName)),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text('ID: $itemId', style: TextStyle(fontSize: 20)),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                // Kembali dengan mengirim data
                Navigator.pop(context, 'Data dari detail');
              },
              child: Text('Kembali'),
            ),
          ],
        ),
      ),
    );
  }
}

Named Routes

Dart — Named Routes di MaterialApp
// Definisi named routes di MaterialApp
MaterialApp(
  title: 'Aplikasi Flutter',
  initialRoute: '/',
  routes: {
    '/': (context) => HomePage(),
    '/detail': (context) => DetailPage(itemId: 0, itemName: ''),
    '/login': (context) => LoginPage(),
    '/profile': (context) => ProfilePage(),
  },
)

// Navigasi menggunakan named route
Navigator.pushNamed(context, '/profile');

// Mengirim arguments
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': 42, 'name': 'Produk A'},
);

// Menerima arguments di halaman tujuan
final args = ModalRoute.of(context)!.settings.arguments as Map;


7. Formulir & Input Handling

Flutter menyediakan widget Form dan TextFormField untuk menangani input pengguna dengan validasi built-in.

Dart — Formulir dengan Validasi
import 'package:flutter/material.dart';

class RegistrationForm extends StatefulWidget {
  @override
  State<RegistrationForm> createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  final _formKey = GlobalKey<FormState>();
  final _namaController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _obscurePassword = true;
  String? _selectedGender;

  @override
  void dispose() {
    _namaController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      // Form valid — proses data
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Registrasi berhasil!')),
      );
      print('Nama: ${_namaController.text}');
      print('Email: ${_emailController.text}');
      print('Gender: $_selectedGender');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Registrasi')),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Nama
              TextFormField(
                controller: _namaController,
                decoration: InputDecoration(
                  labelText: 'Nama Lengkap',
                  prefixIcon: Icon(Icons.person),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Nama tidak boleh kosong';
                  }
                  if (value.length < 3) {
                    return 'Nama minimal 3 karakter';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              // Email
              TextFormField(
                controller: _emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: InputDecoration(
                  labelText: 'Email',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Email tidak boleh kosong';
                  }
                  if (!value.contains('@')) {
                    return 'Email tidak valid';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              // Password
              TextFormField(
                controller: _passwordController,
                obscureText: _obscurePassword,
                decoration: InputDecoration(
                  labelText: 'Password',
                  prefixIcon: Icon(Icons.lock),
                  suffixIcon: IconButton(
                    icon: Icon(_obscurePassword
                        ? Icons.visibility_off
                        : Icons.visibility),
                    onPressed: () {
                      setState(() {
                        _obscurePassword = !_obscurePassword;
                      });
                    },
                  ),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Password tidak boleh kosong';
                  }
                  if (value.length < 8) {
                    return 'Password minimal 8 karakter';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              // Dropdown
              DropdownButtonFormField<String>(
                value: _selectedGender,
                decoration: InputDecoration(
                  labelText: 'Jenis Kelamin',
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                items: ['Laki-laki', 'Perempuan'].map((g) {
                  return DropdownMenuItem(value: g, child: Text(g));
                }).toList(),
                onChanged: (value) {
                  setState(() => _selectedGender = value);
                },
                validator: (value) =>
                    value == null ? 'Pilih jenis kelamin' : null,
              ),
              SizedBox(height: 24),

              // Submit Button
              ElevatedButton(
                onPressed: _submitForm,
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.symmetric(vertical: 16),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                child: Text('Daftar', style: TextStyle(fontSize: 16)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


8. HTTP Requests & API

Untuk berkomunikasi dengan REST API, Flutter menggunakan package http dari pub.dev. Kita akan memanggil API publik dan menampilkan data dalam bentuk list.

Instalasi Package HTTP

Bash
# Tambahkan dependency http
flutter pub add http

# Atau manual di pubspec.yaml:
# dependencies:
#   http: ^1.2.0

# Lalu jalankan:
flutter pub get

Model & Service API

Dart — Model & API Service
import 'dart:convert';
import 'package:http/http.dart' as http;

// ===== MODEL =====
class Post {
  final int id;
  final String title;
  final String body;

  Post({required this.id, required this.title, required this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

// ===== API SERVICE =====
class ApiService {
  static const String baseUrl = 'https://jsonplaceholder.typicode.com';

  // GET — Ambil semua post
  static Future<List<Post>> getPosts() async {
    final response = await http.get(Uri.parse('$baseUrl/posts'));

    if (response.statusCode == 200) {
      List data = json.decode(response.body);
      return data.map((json) => Post.fromJson(json)).toList();
    } else {
      throw Exception('Gagal memuat data');
    }
  }

  // GET — Ambil satu post
  static Future<Post> getPost(int id) async {
    final response = await http.get(Uri.parse('$baseUrl/posts/$id'));

    if (response.statusCode == 200) {
      return Post.fromJson(json.decode(response.body));
    } else {
      throw Exception('Post tidak ditemukan');
    }
  }

  // POST — Buat post baru
  static Future<Post> createPost(String title, String body) async {
    final response = await http.post(
      Uri.parse('$baseUrl/posts'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode({
        'title': title,
        'body': body,
        'userId': 1,
      }),
    );

    if (response.statusCode == 201) {
      return Post.fromJson(json.decode(response.body));
    } else {
      throw Exception('Gagal membuat post');
    }
  }
}

Menampilkan Data dari API

Dart — PostsPage dengan FutureBuilder
class PostsPage extends StatefulWidget {
  @override
  State<PostsPage> createState() => _PostsPageState();
}

class _PostsPageState extends State<PostsPage> {
  late Future<List<Post>> _futurePosts;

  @override
  void initState() {
    super.initState();
    _futurePosts = ApiService.getPosts();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Posts dari API')),
      body: FutureBuilder<List<Post>>(
        future: _futurePosts,
        builder: (context, snapshot) {
          // Loading state
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }

          // Error state
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.error_outline, size: 64, color: Colors.red),
                  SizedBox(height: 16),
                  Text('Error: ${snapshot.error}'),
                  SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _futurePosts = ApiService.getPosts();
                      });
                    },
                    child: Text('Coba Lagi'),
                  ),
                ],
              ),
            );
          }

          // Success state
          final posts = snapshot.data!;
          return RefreshIndicator(
            onRefresh: () async {
              setState(() {
                _futurePosts = ApiService.getPosts();
              });
            },
            child: ListView.builder(
              itemCount: posts.length,
              itemBuilder: (context, index) {
                final post = posts[index];
                return Card(
                  margin: EdgeInsets.symmetric(
                      horizontal: 12, vertical: 4),
                  child: ListTile(
                    leading: CircleAvatar(
                      child: Text('${post.id}'),
                    ),
                    title: Text(
                      post.title,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    subtitle: Text(
                      post.body,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    onTap: () {
                      // Buka detail post
                    },
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}
💡 Alternatif untuk HTTP

Untuk proyek yang lebih besar, pertimbangkan menggunakan Dio — HTTP client yang lebih powerful dengan fitur interceptor, request cancellation, file upload/download, dan transformasi data. Cukup jalankan flutter pub add dio untuk menginstalnya.



9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Flutter:

Pertanyaan 1: Bahasa pemrograman apa yang digunakan oleh Flutter?

a) JavaScript
b) Kotlin
c) Dart
d) Swift

Pertanyaan 2: Widget apa yang digunakan untuk layout horizontal di Flutter?

a) Column
b) Row
c) Stack
d) Wrap

Pertanyaan 3: Method apa yang harus dipanggil untuk memperbarui UI pada StatefulWidget?

a) rebuild()
b) updateUI()
c) setState()
d) refresh()

Pertanyaan 4: Apa fungsi dari widget Expanded dalam Row atau Column?

a) Menambah margin pada widget
b) Membuat widget mengisi ruang yang tersisa
c) Mengatur warna latar belakang
d) Menghapus widget dari tree

Pertanyaan 5: Widget apa yang digunakan untuk menangani input formulir dengan validasi di Flutter?

a) TextField
b) Form + TextFormField
c) InputBox
d) TextFormField saja
🔍 Zoom
100%
🎨 Tema