Latihan di pelajaran ini butuh akun gratis. Progres, kode, dan catatanmu hanya tersimpan di browser ini. Masuk untuk menyimpannya
Materi Pengurus koperasi karyawan di Balikpapan memakai aplikasi daftar tugas sederhana, dan programnya ada di bawah. _DaftarTugasState memegang daftar tugas, formulir di atas menambah tugas baru, dan setiap baris berupa BarisTugas yang stateless. Bacalah dengan urutan ini: data apa yang disimpan State, kapan setState dipanggil, dan ke mana callback dari BarisTugas mengarah.
class Tugas {
final String judul;
final bool selesai;
const Tugas(this.judul, {this.selesai = false});
Tugas tandai(bool nilai) => Tugas(judul, selesai: nilai);
}
class DaftarTugas extends StatefulWidget {
const DaftarTugas({super.key});
@override
State<DaftarTugas> createState() => _DaftarTugasState();
}
class _DaftarTugasState extends State<DaftarTugas> {
final _formKey = GlobalKey<FormState>();
final _judul = TextEditingController();
final List<Tugas> _tugas = [];
@override
void dispose() {
_judul.dispose();
super.dispose();
}
void _tambah() {
if (!_formKey.currentState!.validate()) return;
setState(() {
_tugas.add(Tugas(_judul.text.trim()));
});
_judul.clear();
}
@override
Widget build(BuildContext context) {
final sisa = _tugas.where((t) => !t.selesai).length;
return Scaffold(
appBar: AppBar(title: Text('Tugas ($sisa belum selesai)')),
body: Column(
children: [
Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _judul,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Judul wajib diisi';
}
if (value.trim().length > 40) return 'Maksimal 40 huruf';
return null;
},
),
),
IconButton(icon: const Icon(Icons.add), onPressed: _tambah),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tugas.length,
itemBuilder: (context, i) => BarisTugas(
tugas: _tugas[i],
onUbah: (nilai) => setState(() => _tugas[i] = _tugas[i].tandai(nilai)),
),
),
),
],
),
);
}
}
class BarisTugas extends StatelessWidget {
const BarisTugas({super.key, required this.tugas, required this.onUbah});
final Tugas tugas;
final ValueChanged<bool> onUbah;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(tugas.judul),
value: tugas.selesai,
onChanged: (v) => onUbah(v ?? false),
);
}
}Empat soal berikut memakai program yang sama. Dua di antaranya mengubah satu bagian; bayangkan perubahan itu pada salinan program, bukan pada aslinya. Judul AppBar dihitung di build dari isi _tugas, jadi judul itu hanya berubah bila build milik _DaftarTugasState dijalankan ulang.
Sebelum menjawab, tandai tiga tempat di program: _tambah yang memvalidasi lalu menambah tugas di dalam setState, onUbah yang mengganti tugas di dalam setState, dan validator yang memeriksa judul setelah trim(). Hampir setiap soal bergantung pada salah satu dari ketiganya.
Tugas adalah kelas imutabel
Mencentang tugas tidak mengubah objek Tugas yang lama. tandai() membuat objek baru dengan nilai selesai yang baru, lalu objek itu menggantikan yang lama di dalam daftar, di dalam setState.
Latihan 0/4 lulus Misi akhir modul: luluskan minimal 4 dari 4 tugas. Jawaban diperiksa di server. Lulus berarti kamu memahami contohnya, bukan penilaian seluruh pekerjaanmu.
Latihan 1 dari 4 Pengguna mengetik tiga spasi di kolom judul lalu mengetuk tombol plus. Apa yang terjadi?
class Tugas {
final String judul;
final bool selesai;
const Tugas(this.judul, {this.selesai = false});
Tugas tandai(bool nilai) => Tugas(judul, selesai: nilai);
}
class DaftarTugas extends StatefulWidget {
const DaftarTugas({super.key});
@override
State<DaftarTugas> createState() => _DaftarTugasState();
}
class _DaftarTugasState extends State<DaftarTugas> {
final _formKey = GlobalKey<FormState>();
final _judul = TextEditingController();
final List<Tugas> _tugas = [];
@override
void dispose() {
_judul.dispose();
super.dispose();
}
void _tambah() {
if (!_formKey.currentState!.validate()) return;
setState(() {
_tugas.add(Tugas(_judul.text.trim()));
});
_judul.clear();
}
@override
Widget build(BuildContext context) {
final sisa = _tugas.where((t) => !t.selesai).length;
return Scaffold(
appBar: AppBar(title: Text('Tugas ($sisa belum selesai)')),
body: Column(
children: [
Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _judul,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Judul wajib diisi';
}
if (value.trim().length > 40) return 'Maksimal 40 huruf';
return null;
},
),
),
IconButton(icon: const Icon(Icons.add), onPressed: _tambah),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tugas.length,
itemBuilder: (context, i) => BarisTugas(
tugas: _tugas[i],
onUbah: (nilai) => setState(() => _tugas[i] = _tugas[i].tandai(nilai)),
),
),
),
],
),
);
}
}
class BarisTugas extends StatelessWidget {
const BarisTugas({super.key, required this.tugas, required this.onUbah});
final Tugas tugas;
final ValueChanged<bool> onUbah;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(tugas.judul),
value: tugas.selesai,
onChanged: (v) => onUbah(v ?? false),
);
}
}Butuh petunjuk?
Latihan 2 dari 4 Pengguna menambahkan Setor kas harian, Beli kertas struk, dan Rekap absen, lalu mencentang Beli kertas struk. Apa judul AppBar sekarang?
class Tugas {
final String judul;
final bool selesai;
const Tugas(this.judul, {this.selesai = false});
Tugas tandai(bool nilai) => Tugas(judul, selesai: nilai);
}
class DaftarTugas extends StatefulWidget {
const DaftarTugas({super.key});
@override
State<DaftarTugas> createState() => _DaftarTugasState();
}
class _DaftarTugasState extends State<DaftarTugas> {
final _formKey = GlobalKey<FormState>();
final _judul = TextEditingController();
final List<Tugas> _tugas = [];
@override
void dispose() {
_judul.dispose();
super.dispose();
}
void _tambah() {
if (!_formKey.currentState!.validate()) return;
setState(() {
_tugas.add(Tugas(_judul.text.trim()));
});
_judul.clear();
}
@override
Widget build(BuildContext context) {
final sisa = _tugas.where((t) => !t.selesai).length;
return Scaffold(
appBar: AppBar(title: Text('Tugas ($sisa belum selesai)')),
body: Column(
children: [
Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _judul,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Judul wajib diisi';
}
if (value.trim().length > 40) return 'Maksimal 40 huruf';
return null;
},
),
),
IconButton(icon: const Icon(Icons.add), onPressed: _tambah),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tugas.length,
itemBuilder: (context, i) => BarisTugas(
tugas: _tugas[i],
onUbah: (nilai) => setState(() => _tugas[i] = _tugas[i].tandai(nilai)),
),
),
),
],
),
);
}
}
class BarisTugas extends StatelessWidget {
const BarisTugas({super.key, required this.tugas, required this.onUbah});
final Tugas tugas;
final ValueChanged<bool> onUbah;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(tugas.judul),
value: tugas.selesai,
onChanged: (v) => onUbah(v ?? false),
);
}
}Butuh petunjuk?
Latihan 3 dari 4 Pada salinan program, setState di _tambah dihapus sehingga isinya hanya _tugas.add(...). Pengguna menambah Bayar listrik ke daftar yang masih kosong. Apa yang terlihat?
class Tugas {
final String judul;
final bool selesai;
const Tugas(this.judul, {this.selesai = false});
Tugas tandai(bool nilai) => Tugas(judul, selesai: nilai);
}
class DaftarTugas extends StatefulWidget {
const DaftarTugas({super.key});
@override
State<DaftarTugas> createState() => _DaftarTugasState();
}
class _DaftarTugasState extends State<DaftarTugas> {
final _formKey = GlobalKey<FormState>();
final _judul = TextEditingController();
final List<Tugas> _tugas = [];
@override
void dispose() {
_judul.dispose();
super.dispose();
}
void _tambah() {
if (!_formKey.currentState!.validate()) return;
setState(() {
_tugas.add(Tugas(_judul.text.trim()));
});
_judul.clear();
}
@override
Widget build(BuildContext context) {
final sisa = _tugas.where((t) => !t.selesai).length;
return Scaffold(
appBar: AppBar(title: Text('Tugas ($sisa belum selesai)')),
body: Column(
children: [
Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _judul,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Judul wajib diisi';
}
if (value.trim().length > 40) return 'Maksimal 40 huruf';
return null;
},
),
),
IconButton(icon: const Icon(Icons.add), onPressed: _tambah),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tugas.length,
itemBuilder: (context, i) => BarisTugas(
tugas: _tugas[i],
onUbah: (nilai) => setState(() => _tugas[i] = _tugas[i].tandai(nilai)),
),
),
),
],
),
);
}
}
class BarisTugas extends StatelessWidget {
const BarisTugas({super.key, required this.tugas, required this.onUbah});
final Tugas tugas;
final ValueChanged<bool> onUbah;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(tugas.judul),
value: tugas.selesai,
onChanged: (v) => onUbah(v ?? false),
);
}
}Butuh petunjuk?
Latihan 4 dari 4 Kenapa judul AppBar ikut berubah saat kotak centang di BarisTugas diketuk, padahal BarisTugas stateless?
class Tugas {
final String judul;
final bool selesai;
const Tugas(this.judul, {this.selesai = false});
Tugas tandai(bool nilai) => Tugas(judul, selesai: nilai);
}
class DaftarTugas extends StatefulWidget {
const DaftarTugas({super.key});
@override
State<DaftarTugas> createState() => _DaftarTugasState();
}
class _DaftarTugasState extends State<DaftarTugas> {
final _formKey = GlobalKey<FormState>();
final _judul = TextEditingController();
final List<Tugas> _tugas = [];
@override
void dispose() {
_judul.dispose();
super.dispose();
}
void _tambah() {
if (!_formKey.currentState!.validate()) return;
setState(() {
_tugas.add(Tugas(_judul.text.trim()));
});
_judul.clear();
}
@override
Widget build(BuildContext context) {
final sisa = _tugas.where((t) => !t.selesai).length;
return Scaffold(
appBar: AppBar(title: Text('Tugas ($sisa belum selesai)')),
body: Column(
children: [
Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _judul,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Judul wajib diisi';
}
if (value.trim().length > 40) return 'Maksimal 40 huruf';
return null;
},
),
),
IconButton(icon: const Icon(Icons.add), onPressed: _tambah),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tugas.length,
itemBuilder: (context, i) => BarisTugas(
tugas: _tugas[i],
onUbah: (nilai) => setState(() => _tugas[i] = _tugas[i].tandai(nilai)),
),
),
),
],
),
);
}
}
class BarisTugas extends StatelessWidget {
const BarisTugas({super.key, required this.tugas, required this.onUbah});
final Tugas tugas;
final ValueChanged<bool> onUbah;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(tugas.judul),
value: tugas.selesai,
onChanged: (v) => onUbah(v ?? false),
);
}
}Butuh petunjuk?