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

Postingan populer dari blog ini

Zapp. run

golden ratio

Artikel Mengenai Pengembangan Gim