Modul Pembelajaran Flutter 2025
Modul Pembelajaran Flutter 2025
FLUTTER
Dari Pemula Hingga Mahir (2025)
DAFTAR ISI
1. Pendahuluan Flutter & Dart
2. Persiapan Lingkungan & Instalasi
3. Struktur Project Flutter
4. Konsep Widget & Layout Dasar
5. Navigasi & Pengiriman Data
6. Form & Validasi Data
7. State Management dengan setState
8. Konsumsi REST API & JSON Parsing
9. Persistent Storage dengan SharedPreferences
10. Database Lokal dengan SQLite
11. Flutter Web & Responsive Design
12. Studi Kasus: Aplikasi Todo Manager
BAB 1: PENDAHULUAN
FLUTTER & DART
1.1 Apa Itu Flutter?
Flutter adalah framework open-source yang dikembangkan oleh Google untuk
membangun aplikasi mobile, web, dan desktop menggunakan satu basis kode
(single codebase). Flutter memungkinkan developer untuk membuat aplikasi
yang indah, cepat, dan responsif di berbagai platform.
Hot Reload
Ubah kode dan lihat perubahan secara instant tanpa perlu rebuild lengkap. Ini
membuat development cycle jauh lebih cepat dan meningkatkan produktivitas
developer.
Performance Tinggi
Flutter mengkompilasi ke native code, bukan webview. Ini menghasilkan
performa yang sangat baik dan smooth animations.
// Boolean (true/false)
bool isActive = true;
Function (Fungsi)
// Function dengan tipe return
int add(int a, int b) {
return a + b;
}
// Constructor
User([Link], [Link], [Link]);
String getInfo() {
return 'n a m e ¿ age tahun) - $email';
}
}
// Inheritance (Pewarisan)
class Admin extends User {
String role;
@override
void introduce() {
print('Admin: $name dengan role: $role');
}
}
// Penggunaan
var user = User('Budi', 25, 'budi@[Link]');
[Link](); // Output: Nama saya Budi, umur 25 tahun
Improved Null Safety: Null Safety yang lebih baik untuk mencegah null
reference errors
Pattern Matching: Fitur pattern matching yang powerful untuk handling
data kompleks
Records: Tipe data records untuk mengelompokkan data dengan lebih
efisien
Extensions: Method extensions untuk menambahkan method ke tipe data
existing
Enhanced Generics: Generics yang lebih powerful untuk type safety
BAB 2: PERSIAPAN
LINGKUNGAN &
INSTALASI
2.1 Instalasi Flutter SDK
Flutter SDK adalah software development kit yang berisi semua tools dan
libraries yang dibutuhkan untuk membuat aplikasi Flutter.
Windows
Langkah 1: Download Flutter SDK
1. Kunjungi [Link]
2. Download file ZIP untuk Windows (versi stable terbaru)
3. Extract ke lokasi yang mudah diakses, misal: C:\flutter
Langkah 2: Setup Environment Variable
macOS/Linux
Tambahkan ke PATH
(untuk Linux/Mac)
export PATH=" P A T H :HOME/flutter/bin"
Verifikasi instalasi
flutter doctor
Menjalankan aplikasi
(gunakan emulator/device)
flutter run
CATATAN: Developer akan fokus di folder 'lib/' untuk membuat kode Dart.
import 'package:flutter/[Link]';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello Flutter',
theme: ThemeData(
primarySwatch: [Link],
useMaterial3: true, // Material Design 3 (2025)
),
home: const MyHomePage(title: 'Flutter Demo'),
);
}
}
@override
State<MyHomePage> createState() => _MyHomePageState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text([Link]),
),
body: Center(
child: Column(
mainAxisAlignment: [Link],
children: <Widget>[
const Text('Anda telah menekan tombol:'),
Text(
'$_counter',
style: [Link](context).[Link],
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon([Link]),
),
);
}
}
dependencies:
flutter:
sdk: flutter
http: ^1.1.0 # Untuk HTTP requests
shared_preferences: ^2.0.0 # Local storage
provider: ^6.0.0 # State management
intl: ^0.19.0 # Internalization
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0 # Linting rules
Kemudian jalankan:
flutter pub get
BAB 3: KONSEP WIDGET
& LAYOUT DASAR
3.1 Apa Itu Widget?
Widget adalah unit UI terkecil di Flutter. Setiap elemen visual (text, button,
image, layout) adalah widget. Flutter menggunakan widget tree untuk
membangun UI.
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: () {
setState(() {
counter++;
});
},
child: Text('Tambah'),
),
],
);
}
}
Button Widgets
// Elevated Button (tombol dengan shadow)
ElevatedButton(
onPressed: () {
print('Elevated button pressed');
},
child: Text('Elevated Button'),
)
// Icon Button
IconButton(
icon: Icon([Link]),
onPressed: () {},
)
Image Widget
// Dari asset (file lokal)
[Link](
'assets/my_image.png',
width: 200,
height: 200,
fit: [Link],
)
Container Widget
Container adalah widget dasar untuk styling dan layout:
Container(
width: 200,
height: 200,
padding: [Link](16),
margin: [Link](horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: [Link],
borderRadius: [Link](12),
border: [Link](color: [Link], width: 2),
boxShadow: [
BoxShadow(
color: [Link](0.2),
blurRadius: 8,
offset: Offset(0, 4),
)
],
),
child: Text('Styled Container'),
)
Stack (Overlay/Tumpukan)
Stack(
children: [
// Background
Container(
width: 200,
height: 200,
color: [Link],
),
// Floating element
Positioned(
right: 10,
bottom: 10,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: [Link],
shape: [Link],
),
child: Icon([Link], color: [Link]),
),
),
],
)
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profil Saya'),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
// Header dengan foto
Container(
padding: [Link](20),
decoration: BoxDecoration(
color: [Link],
),
child: Column(
children: [
CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(
'[Link]
),
),
SizedBox(height: 16),
Text(
'Budi Santoso',
style: TextStyle(
fontSize: 24,
fontWeight: [Link],
color: [Link],
),
),
Text(
'Flutter Developer',
style: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
],
),
),
// Body dengan informasi
Padding(
padding: [Link](20),
child: Column(
crossAxisAlignment: [Link],
children: [
Text(
'Informasi Kontak',
style: TextStyle(
fontSize: 18,
fontWeight: [Link],
),
),
SizedBox(height: 12),
InfoItem(
icon: [Link],
label: 'Email',
value: 'budi@[Link]',
),
InfoItem(
icon: [Link],
label: 'Phone',
value: '+62 812 345 6789',
),
InfoItem(
icon: Icons.location_on,
label: 'Alamat',
value: 'Jakarta, Indonesia',
),
],
),
),
// Button
Padding(
padding: [Link](horizontal: 20, vertical: 20),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: Text('Edit Profil'),
),
),
SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text('Bagikan'),
),
),
],
),
),
],
),
),
);
}
}
const InfoItem({
Key? key,
required [Link],
required [Link],
required [Link],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: [Link](vertical: 12),
child: Row(
children: [
Icon(icon, color: [Link], size: 24),
SizedBox(width: 16),
Column(
crossAxisAlignment: [Link],
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: [Link][600],
fontWeight: FontWeight.w500,
),
),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
);
}
}
// halaman_a.dart
class HalamanA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Halaman A')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Kirim data ke HalamanB
[Link](
context,
MaterialPageRoute(
builder: (context) => HalamanB(
nama: 'Budi',
usia: 25,
),
),
);
},
child: Text('Buka Halaman B'),
),
),
);
}
}
// halaman_b.dart
class HalamanB extends StatelessWidget {
final String nama;
final int usia;
const HalamanB({
Key? key,
required [Link],
required [Link],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Halaman B')),
body: Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Text('Nama: $nama'),
Text('Usia: $usia'),
ElevatedButton(
onPressed: () {
[Link](context);
},
child: Text('Kembali'),
),
],
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Halaman A')),
body: Center(
child: Column(
mainAxisAlignment: [Link],
children: [
if (dataYangDiterima != null)
Text('Data dari B: $dataYangDiterima'),
ElevatedButton(
onPressed: () async {
// Tunggu hasil dari HalamanB
final result = await [Link](
context,
MaterialPageRoute(builder: (context) => HalamanB()),
);
// Terima data
if (result != null) {
setState(() {
dataYangDiterima = result;
});
}
},
child: Text('Buka Halaman B'),
),
],
),
),
);
}
}
// Halaman B - mengirim data kembali
class HalamanB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Halaman B')),
body: Center(
child: ElevatedButton(
onPressed: () {
[Link](context, 'Data dari Halaman B');
},
child: Text('Kirim Data dan Kembali'),
),
),
);
}
}
// [Link]
void main() {
runApp(MyApp());
}
// Menerima arguments
class Detail extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = [Link](context)!.[Link] as Map;
return Scaffold(
appBar: AppBar(title: Text('Detail')),
body: Center(
child: Text('ID: ${args['id']}, Nama: ${args['nama']}'),
),
);
}
}
TextFormField(
decoration: InputDecoration(
labelText: 'Nama Lengkap',
hintText: 'Masukkan nama Anda',
prefixIcon: Icon([Link]),
border: OutlineInputBorder(),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: [Link]),
),
),
validator: (value) {
if (value == null || [Link]) {
return 'Nama tidak boleh kosong';
}
if ([Link] < 3) {
return 'Nama minimal 3 karakter';
}
return null;
},
)
String? _selectedGender;
List<String> genders = ['Laki-laki', 'Perempuan'];
bool _agreeToTerms = false;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_phoneController.dispose();
[Link]();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
if (!_agreeToTerms) {
[Link](context).showSnackBar(
SnackBar(content: Text('Setujui syarat dan ketentuan')),
);
return;
}
// Proses data
print('Nama: ${_nameController.text}');
print('Email: ${_emailController.text}');
print('Gender: $_selectedGender');
[Link](context).showSnackBar(
SnackBar(content: Text('Registrasi berhasil!')),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Formulir Registrasi')),
body: SingleChildScrollView(
padding: [Link](20),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: [Link],
children: [
// Nama
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Nama Lengkap',
hintText: 'Masukkan nama Anda',
prefixIcon: Icon([Link]),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || [Link]) {
return 'Nama tidak boleh kosong';
}
if ([Link] < 3) {
return 'Nama minimal 3 karakter';
}
return null;
},
),
SizedBox(height: 16),
// Email
TextFormField(
controller: _emailController,
keyboardType: [Link],
decoration: InputDecoration(
labelText: 'Email',
hintText: 'example@[Link]',
prefixIcon: Icon([Link]),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || [Link]) {
return 'Email tidak boleh kosong';
}
if () {
return 'Format email tidak valid';
}
return null;
},
),
SizedBox(height: 16),
// Password
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
hintText: 'Minimal 6 karakter',
prefixIcon: Icon([Link]),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || [Link]) {
return 'Password tidak boleh kosong';
}
if ([Link] < 6) {
return 'Password minimal 6 karakter';
}
return null;
},
),
SizedBox(height: 16),
// Phone
TextFormField(
controller: _phoneController,
keyboardType: [Link],
decoration: InputDecoration(
labelText: 'No. Telepon',
hintText: '08xx xxxx xxxx',
prefixIcon: Icon([Link]),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || [Link]) {
return 'No. telepon tidak boleh kosong';
}
if ( || [Link] < 10) {
return 'Format telepon tidak valid';
}
return null;
},
),
SizedBox(height: 16),
// Gender Dropdown
DropdownButtonFormField<String>(
value: _selectedGender,
decoration: InputDecoration(
labelText: 'Jenis Kelamin',
border: OutlineInputBorder(),
prefixIcon: Icon([Link]),
),
items: [Link]((gender) {
return DropdownMenuItem(
value: gender,
child: Text(gender),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedGender = value;
});
},
validator: (value) {
if (value == null) {
return 'Pilih jenis kelamin';
}
return null;
},
),
SizedBox(height: 16),
// Checkbox
CheckboxListTile(
title: Text('Saya setuju dengan syarat dan
ketentuan'),
value: _agreeToTerms,
onChanged: (value) {
setState(() {
_agreeToTerms = value ?? false;
});
},
),
SizedBox(height: 24),
// Button Submit
SizedBox(
width: [Link],
height: 50,
child: ElevatedButton(
onPressed: _submitForm,
child: Text('Daftar'),
),
),
],
),
),
),
);
}
}
BAB 6: STATE
MANAGEMENT DENGAN
SETSTATE
6.1 Konsep State
State adalah data yang dapat berubah dan mempengaruhi tampilan UI. Ketika
state berubah, widget akan di-rebuild untuk menampilkan data terbaru.
void increment() {
setState(() {
counter++; // Update state
});
// UI akan di-rebuild otomatis dengan counter value baru
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Text(
'Counter: $counter',
style: TextStyle(fontSize: 32),
),
ElevatedButton(
onPressed: increment,
child: Text('Increment'),
),
],
),
),
);
}
}
void increaseCounter() {
setState(() {
sharedCounter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Lifting State Up')),
body: Column(
children: [
ChildA(counter: sharedCounter),
ChildB(onIncrement: increaseCounter),
],
),
);
}
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onIncrement,
child: Text('Increment'),
);
}
}
void _addTodo() {
if (_controller.[Link]) return;
setState(() {
[Link](_controller.text);
_controller.clear();
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todo List'),
centerTitle: true,
),
body: Column(
children: [
// Input
Padding(
padding: [Link](16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Tambah todo baru...',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: _addTodo,
child: Icon([Link]),
),
],
),
),
// List
Expanded(
child: [Link]
? Center(child: Text('Tidak ada todo'))
: [Link](
itemCount: [Link],
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index]),
trailing: IconButton(
icon: Icon([Link]),
onPressed: () {
_deleteTodo(index);
},
),
);
},
),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
[Link]();
}
}
HTTP Methods
GET: Mengambil data dari server
POST: Mengirim data baru ke server
PUT: Update data yang sudah ada
DELETE: Menghapus data
dependencies:
http: ^1.1.0
class User {
int id;
String name;
String email;
String phone;
String website;
User({
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
});
class UserService {
static const String _baseUrl = '[Link]
if ([Link] == 200) {
return [Link](jsonDecode([Link]));
} else {
throw Exception('Gagal memuat user');
}
} catch (e) {
throw Exception('Error: $e');
}
}
// Fetch list users
static Future<List<User>> fetchUsers() async {
try {
final response = await [Link](
[Link]('$_baseUrl/users'),
);
if ([Link] == 200) {
List<dynamic> data = jsonDecode([Link]);
return [Link]((user) => [Link](user)).toList();
} else {
throw Exception('Gagal memuat users');
}
} catch (e) {
throw Exception('Error: $e');
}
if ([Link] == 201) {
return [Link](jsonDecode([Link]));
} else {
throw Exception('Gagal membuat user');
}
} catch (e) {
throw Exception('Error: $e');
}
}
}
BAB 8: PERSISTENT
STORAGE DENGAN
SHAREDPREFERENCES
8.1 Konsep Persistent Storage
Persistent storage adalah menyimpan data lokal di device agar tetap ada setelah
app ditutup. SharedPreferences adalah cara paling sederhana untuk small data.
dependencies:
shared_preferences: ^2.0.0
8.3 Menyimpan & Membaca Data
import 'package:shared_preferences/shared_preferences.dart';
class PreferenceService {
static late SharedPreferences _prefs;
// Initialize
static Future<void> init() async {
_prefs = await [Link]();
}
// Save string
static Future<void> saveString(String key, String value) async {
await _prefs.setString(key, value);
}
// Get string
static String? getString(String key) {
return _prefs.getString(key);
}
// Save int
static Future<void> saveInt(String key, int value) async {
await _prefs.setInt(key, value);
}
// Get int
static int? getInt(String key) {
return _prefs.getInt(key);
}
// Save bool
static Future<void> saveBool(String key, bool value) async {
await _prefs.setBool(key, value);
}
// Get bool
static bool? getBool(String key) {
return _prefs.getBool(key);
}
// Delete
static Future<void> delete(String key) async {
await _prefs.remove(key);
}
// Clear all
static Future<void> clear() async {
await _prefs.clear();
}
}
8.4 Contoh: Dark Mode Toggle
class ThemeSettingsScreen extends StatefulWidget {
@override
State<ThemeSettingsScreen> createState() => _ThemeSettingsScreenState();
}
@override
void initState() {
[Link]();
_loadThemePreference();
}
void _loadThemePreference() {
final isDark = [Link]('isDarkMode') ?? false;
setState(() {
_isDarkMode = isDark;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Pengaturan Tema')),
body: Padding(
padding: [Link](20),
child: SwitchListTile(
title: Text('Dark Mode'),
value: _isDarkMode,
onChanged: _toggleTheme,
),
),
);
}
}
BAB 9: DATABASE LOKAL
DENGAN SQLITE
9.1 Setup SQLite
Edit [Link]:
dependencies:
sqflite: ^2.0.0
path: ^1.8.0
Note({
[Link],
required [Link],
required [Link],
required [Link],
});
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _database;
DatabaseHelper._internal();
factory DatabaseHelper() {
return _instance;
}
// CRUD Operations
Future<int> insertNote(Note note) async {
final db = await database;
return await [Link]('notes', [Link]());
}
Run di web
flutter run -d chrome
11.3 Implementation
Model Todo
// models/[Link]
class Todo {
int? id;
String title;
String description;
String category;
bool isCompleted;
DateTime dueDate;
DateTime createdAt;
Todo({
[Link],
required [Link],
required [Link],
required [Link],
[Link] = false,
required [Link],
required [Link],
});
Database Helper
// services/database_helper.dart
import 'package:sqflite/[Link]';
import 'package:path/[Link]';
import '../models/[Link]';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _database;
DatabaseHelper._internal();
// CRUD Methods
Future<int> insertTodo(Todo todo) async {
final db = await database;
return await [Link]('todos', [Link]());
}
Home Screen
// screens/home_screen.dart
import 'package:flutter/[Link]';
import '../models/[Link]';
import '../services/database_helper.dart';
import 'add_todo_screen.dart';
@override
State<HomeScreen> createState() => _HomeScreenState();
}
@override
void initState() {
[Link]();
_dbHelper = DatabaseHelper();
_loadTodos();
}
void
addTodo() {[Link](context,MaterialPageRoute(builder: (context) =>
const AddTodoScreen()),).then(() => _loadTodos());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todo Manager'),
centerTitle: true,
),
body: Column(
children: [
// Category Filter
Padding(
padding: const [Link](8.0),
child: SingleChildScrollView(
scrollDirection: [Link],
child: Row(
children: _categories.map((cat) {
return Padding(
padding: const [Link](horizontal: 4),
child: FilterChip(
label: Text(cat),
selected: _selectedCategory == cat,
onSelected: (selected) {
setState(() {
_selectedCategory = cat;
});
_loadTodos();
},
),
);
}).toList(),
),
),
),
// Todo List
Expanded(
child: _todos.isEmpty
? const Center(child: Text('Tidak ada todo'))
: [Link](
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo =
@override
void dispose() {
_searchController.dispose();
[Link]();
}
}
@override
State<AddTodoScreen> createState() => _AddTodoScreenState();
}
String? _selectedCategory;
DateTime? _selectedDate;
final List<String> _categories = ['Pekerjaan', 'Pribadi', 'Belanja', 'Kesehatan'];
void _submitForm() {
if (_formKey.currentState!.validate()) {
if (_selectedCategory == null || _selectedDate == null) {
[Link](context).showSnackBar(
SnackBar(content: Text('Pilih kategori dan tanggal')),
);
return;
}
_dbHelper.insertTodo(todo);
[Link](context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tambah Todo')),
body: SingleChildScrollView(
padding: [Link](20),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: [Link],
children: [
TextFormField(
controller: _titleController,
decoration: InputDecoration(
labelText: 'Judul',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || [Link]) {
return 'Judul tidak boleh kosong';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: 'Deskripsi',
border: OutlineInputBorder(),
),
maxLines: 4,
),
SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedCategory,
decoration: InputDecoration(
labelText: 'Kategori',
border: OutlineInputBorder(),
),
items: _categories.map((cat) {
return DropdownMenuItem(value: cat, child: Text(cat));
}).toList(),
onChanged: (value) {
setState(() {
_selectedCategory = value;
});
},
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _pickDate,
child: Text(_selectedDate == null
? 'Pilih Tanggal'
: 'Tanggal: ${_selectedDate!.toString().split(' ')[0]}'),
),
SizedBox(height: 24),
SizedBox(
width: [Link],
height: 50,
child: ElevatedButton(
onPressed: _submitForm,
child: Text('Simpan Todo'),
),
),
],
),
),
),
);
}
@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
[Link]();
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo Manager',
theme: ThemeData(
primarySwatch: [Link],
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
Resources Berguna
Official Flutter Documentation: [Link]
Dart Programming Language: [Link]
Flutter YouTube Channel: [Link]
[Link] - Flutter Packages: [Link]
Stack Overflow - Flutter Tag:
[Link]
Medium - Flutter Publication: [Link]
Created for comprehensive Flutter learning with practical examples and best
practices