Membuat to-do list widget flutter
Apa Itu Widget di Flutter
Widget adalah komponen utama yang membentuk tampilan di Flutter.
Semua yang kamu lihat di layar — teks, gambar, tombol, bahkan tata letak — adalah widget.
Flutter membangun UI dari pohon widget (widget tree), di mana setiap widget bisa berisi widget lain.
⚙️ Jenis Widget
1. StatelessWidget
Tidak punya data yang berubah.
Cocok untuk tampilan statis.
class MyText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Halo Flutter!');
}
}
2. StatefulWidget
Punya state (data yang bisa berubah).
Cocok untuk tampilan yang interaktif.
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Nilai: $count'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text('Tambah'),
),
],
);
}
}
Jenis Widget Berdasarkan Fungsi
Jenis Contoh
Teks & Media Text, Image, Icon
Layout Row, Column, Container, Stack
Input & Aksi TextField, ElevatedButton, Switch
Struktur Halaman Scaffold, AppBar, MaterialApp
✨ Kesimpulan
Semua di Flutter adalah widget.
Ada Stateless dan Stateful widget.
Widget membentuk pohon UI.
Flutter cepat karena hanya memperbarui bagian widget yang berubah.
📱codenya
// lib/main.dart
import 'dart:convert';
import 'dart:html' as html; // NOTE: hanya untuk Flutter Web
import 'package:flutter/material.dart';
void main() {
runApp(const TodoApp());
}
class TodoItem {
String id;
String title;
bool done;
TodoItem({required this.id, required this.title, this.done = false});
factory TodoItem.fromJson(Map<String, dynamic> json) => TodoItem(
id: json['id'] as String,
title: json['title'] as String,
done: json['done'] as bool? ?? false,
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'done': done,
};
}
class TodoStorage {
static const _key = 'flutter_web_todos_v1';
static List<TodoItem> loadTodos() {
try {
final raw = html.window.localStorage[_key];
if (raw == null) return <TodoItem>[];
final List<dynamic> arr = jsonDecode(raw);
return arr.map((e) => TodoItem.fromJson(e)).toList();
} catch (e) {
// kalau error, kembalikan list kosong
return <TodoItem>[];
}
}
static void saveTodos(List<TodoItem> todos) {
try {
final raw = jsonEncode(todos.map((t) => t.toJson()).toList());
html.window.localStorage[_key] = raw;
} catch (e) {
// ignore
}
}
}
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo Web (Flutter)',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: const TodoHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class TodoHomePage extends StatefulWidget {
const TodoHomePage({super.key});
@override
State<TodoHomePage> createState() => _TodoHomePageState();
}
class _TodoHomePageState extends State<TodoHomePage> {
final _controller = TextEditingController();
List<TodoItem> _todos = [];
@override
void initState() {
super.initState();
_todos = TodoStorage.loadTodos();
}
void _addTodo(String text) {
final trimmed = text.trim();
if (trimmed.isEmpty) return;
final item = TodoItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: trimmed,
done: false,
);
setState(() {
_todos.insert(0, item);
_controller.clear();
TodoStorage.saveTodos(_todos);
});
}
void _toggleDone(TodoItem item) {
setState(() {
item.done = !item.done;
TodoStorage.saveTodos(_todos);
});
}
void _deleteTodo(TodoItem item) {
setState(() {
_todos.removeWhere((t) => t.id == item.id);
TodoStorage.saveTodos(_todos);
});
}
void _clearCompleted() {
setState(() {
_todos.removeWhere((t) => t.done);
TodoStorage.saveTodos(_todos);
});
}
@override
Widget build(BuildContext context) {
final remaining = _todos.where((t) => !t.done).length;
return Scaffold(
appBar: AppBar(
title: const Text('Todo List (Web)'),
actions: [
IconButton(
tooltip: 'Hapus selesai',
onPressed: _todos.any((t) => t.done) ? _clearCompleted : null,
icon: const Icon(Icons.delete_sweep),
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
onSubmitted: _addTodo,
decoration: const InputDecoration(
labelText: 'Tambah todo',
hintText: 'Contoh: Belajar Flutter',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => _addTodo(_controller.text),
child: const Text('Tambah'),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Align(
alignment: Alignment.centerLeft,
child: Text('$remaining item tersisa'),
),
),
const SizedBox(height: 8),
Expanded(
child: _todos.isEmpty
? const Center(child: Text('Belum ada todo. Tambahkan yang pertama!'))
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: _todos.length,
itemBuilder: (context, index) {
final item = _todos[index];
return Card(
margin: const EdgeInsets.symmetric(vertical: 6),
child: ListTile(
leading: Checkbox(
value: item.done,
onChanged: (_) => _toggleDone(item),
),
title: Text(
item.title,
style: item.done
? const TextStyle(decoration: TextDecoration.lineThrough, color: Colors.grey)
: null,
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteTodo(item),
),
onTap: () => _toggleDone(item),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
tooltip: 'Tambah contoh',
onPressed: () => _addTodo('Todo contoh ${_todos.length + 1}'),
child: const Icon(Icons.add),
),
);
}
}
📷potonya

Komentar
Posting Komentar