Menu
CoddyTech

Pelacak Kebiasaan: Tandai Satu Hari

Termasuk bagian Proyek dalam perjalanan Flutter di Coddy. Pelajaran 30 dari 36.

Ketuk lingkaran untuk menandai hari, lalu ketuk lagi untuk menghapus tandanya. Data kebiasaan tersimpan dalam state layar, tetapi lingkaran digambar jauh di dalam HabitCard. Ketukan itu harus diteruskan ke tempat data berada.

Inilah cara mengangkat state dengan callback. Layar mengelola perubahan:

void _toggle(Habit habit, int day) {
  setState(() {
    habit.days[day] = !habit.days[day];
  });
}

Layar memberikan setiap kartu sebuah fungsi, onToggle: (day) => _toggle(habit, day). Tipe fungsi tersebut pada kartu adalah void Function(int day), yaitu fungsi yang menerima nomor hari. Setiap lingkaran mendapatkan VoidCallback sederhana, onTap: () => onToggle(i), lalu membungkus Container-nya dengan GestureDetector yang memanggilnya.

!habit.days[day] membalik nilai bool: true menjadi false dan sebaliknya, sehingga satu fungsi dapat menandai maupun menghapus tanda.

challenge icon

Tantangan

Sulit

Tandai hari:

  • tambahkan method _toggle ke state layar
  • berikan HabitCard required onToggle bertipe void Function(int day), lalu teruskan (day) => _toggle(habit, day)
  • berikan DayCircle required VoidCallback onTap, bungkus Container-nya dengan GestureDetector, lalu teruskan () => onToggle(i)

Coba sendiri

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
      ),
      home: const HabitScreen(),
    );
  }
}

const List<String> dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

class Habit {
  Habit(this.name, this.days);

  final String name;
  final List<bool> days;
}

class HabitScreen extends StatefulWidget {
  const HabitScreen({super.key});

  @override
  State<HabitScreen> createState() => _HabitScreenState();
}

class _HabitScreenState extends State<HabitScreen> {
  final List<Habit> _habits = [
    Habit('Drink water', [true, true, true, false, true, false, false]),
    Habit('Read 10 pages', [true, false, true, true, false, false, false]),
    Habit('Stretch', List.filled(7, false)),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Habits')),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 8, 16, 88),
        children: [
          for (Habit habit in _habits) HabitCard(habit: habit),
        ],
      ),
    );
  }
}

class HabitCard extends StatelessWidget {
  const HabitCard({super.key, required this.habit});

  final Habit habit;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    return Card(
      margin: const EdgeInsets.only(top: 12),
      clipBehavior: Clip.antiAlias,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(habit.name, style: textTheme.titleMedium),
            const SizedBox(height: 12),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                for (int i = 0; i < 7; i++) DayCircle(label: dayNames[i], done: habit.days[i]),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class DayCircle extends StatelessWidget {
  const DayCircle({super.key, required this.label, required this.done});

  final String label;
  final bool done;

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    return Container(
      width: 40,
      height: 40,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: done ? colors.primary : colors.surfaceContainerHighest,
      ),
      child: Text(
        label,
        style: TextStyle(
          fontSize: 11,
          fontWeight: FontWeight.bold,
          color: done ? colors.onPrimary : colors.onSurfaceVariant,
        ),
      ),
    );
  }
}
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Proyek

Latihan sendiri: Compiler Flutter online