Skip to main content
Praktikum 4·
28 September 2026

Navigasi & Passing Data

REPOSITORY_GITHUB

Praktikum IV membahas cara berpindah antarhalaman (navigasi) pada aplikasi Flutter dan cara mengirim data dari satu halaman ke halaman lain. Materi ini melanjutkan aplikasi Expense Tracker dari modul sebelumnya, yang sudah memiliki halaman Dashboard (Modul 1 dan 2) dan halaman form tambah transaksi (Modul 3).

Pada praktikum ini, tombol dan daftar transaksi yang sebelumnya belum berfungsi diaktifkan. Tombol tambah (FAB) membuka form transaksi menggunakan Navigator.push, setiap baris transaksi membuka halaman detail sambil membawa data transaksi melalui constructor, dan form transaksi tertutup otomatis setelah disimpan menggunakan Navigator.pop. Proyek disusun menjadi empat file terpisah di folder lib untuk mensimulasikan struktur aplikasi yang lebih rapi.

ITujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:

  • ✓Menerapkan Navigator.push untuk berpindah ke layar baru
  • ✓Menggunakan Navigator.pop untuk menutup layar setelah aksi selesai.
  • ✓Mengirim data (String, int) antar layar melalui Constructor.

IIDasar Teori

Navigator dan Konsep Tumpukan (Stack)

Navigator adalah widget pengelola rute (halaman) di Flutter. Navigator menyimpan halaman-halaman aplikasi dalam struktur tumpukan (stack), seperti tumpukan kartu. Halaman yang tampil di layar selalu halaman yang berada di posisi paling atas tumpukan.

  • Push (maju): meletakkan halaman baru di atas tumpukan, sehingga halaman baru itulah yang tampil.

  • Pop (mundur): menghapus halaman teratas dari tumpukan, sehingga pengguna kembali ke halaman di bawahnya.

Karena bekerja dengan prinsip tumpukan, halaman yang terakhir dibuka adalah halaman yang pertama ditutup (Last In, First Out). Ketika halaman baru di-push, AppBar pada halaman tersebut otomatis menampilkan tombol panah kiri (back) yang menjalankan pop.

MaterialPageRoute

MaterialPageRoute adalah jenis rute yang dipakai bersama Navigator.push. Rute ini membangun halaman tujuan melalui parameter builder dan memberikan animasi transisi standar Material Design saat berpindah halaman.

Constructor dan Passing Data

Untuk mengirim data dari Halaman A ke Halaman B, Halaman B harus menyiapkan tempat penampung data dan constructor yang menerima data tersebut. Ada dua komponen penting:

  • Variabel final: tempat penampung data (misalnya title). Pada StatelessWidget, variabel ini harus final karena nilainya tidak berubah setelah widget dibuat.

  • Kata kunci required: penanda bahwa data tersebut wajib diisi saat berpindah layar. Jika tidak diisi, kode tidak dapat dikompilasi.

IIILangkah Kerja Praktikum

1

Langkah 1: Menyiapkan Proyek dan Struktur File

Buat proyek baru dengan nama praktikum4

2

Langkah 2: Membuat File main.dart

File main.dart adalah titik awal aplikasi. File ini mengatur tema berwarna merah dan menentukan DashboardScreen sebagai halaman pertama.

dart
import 'package:flutter/material.dart';
import 'dashboard_screen.dart';
 
void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Expense Tracker - Modul 4',
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: const DashboardScreen(), // Halaman pertama yang dimuat
    );
  }
}

Penjelasan kode:

  1. import 'dashboard_screen.dart',

    Mengimpor file Dashboard agar class DashboardScreen bisa dipakai di file ini.

  1. void main()

    Fungsi utama yang pertama kali dijalankan. runApp() menampilkan widget MyApp ke layar.

  2. class MyApp extends StatelessWidget

    Widget akar aplikasi. Menggunakan StatelessWidget karena tidak menyimpan data yang berubah.

  3. debugShowCheckedModeBanner: false

    Menyembunyikan label "DEBUG" di pojok kanan atas layar.

  4. theme: ThemeData(primarySwatch: Colors.red)

    Mengatur warna tema aplikasi menjadi merah sesuai slide Modul 4.

  5. home: const DashboardScreen()

    Menentukan Dashboard sebagai halaman pertama, yaitu halaman paling bawah pada tumpukan Navigator.

3

Langkah 3: Membuat Halaman Detail Transaksi (Penerima Data)

Buat file transaction_detail_screen.dart. Halaman ini menerima data transaksi (judul, nominal, kategori, dan tanggal) melalui constructor, lalu menampilkannya pada UI.

dart
import 'package:flutter/material.dart';
 
class TransactionDetailScreen extends StatelessWidget {
  // Deklarasi variabel penampung data (harus final karena StatelessWidget)
  final String title;
  final String amount;
  final String category;
  final String date;
 
  // Constructor yang mewajibkan pengisian data (required)
  const TransactionDetailScreen({
    super.key,
    required this.title,
    required this.amount,
    required this.category,
    required this.date,
  });
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Detail Transaksi"),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const Icon(Icons.receipt_long, size: 80, color: Colors.redAccent),
            const SizedBox(height: 24),
            Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(amount, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.red)),
            const Divider(height: 40, thickness: 2),
            _buildDetailRow('Kategori', category),
            const SizedBox(height: 16),
            _buildDetailRow('Tanggal', date),
            const SizedBox(height: 16),
            _buildDetailRow('Status', 'Berhasil'),
          ],
        ),
      ),
    );
  }
 
  // Widget pembantu untuk merapikan layout baris detail
  Widget _buildDetailRow(String label, String value) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: const TextStyle(fontSize: 16, color: Colors.grey)),
        Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
      ],
    );
  }
}

Penjelasan kode:

  1. final String title; amount; category; date;

    Variabel penampung data yang dikirim dari Dashboard. Harus final karena widget ini adalah StatelessWidget.

  2. const TransactionDetailScreen({ ... })

    Constructor dengan parameter bernama (named parameter) di dalam kurung kurawal.

  3. required this.title, ...

    Keempat data wajib diisi. this.title langsung menyimpan nilai parameter ke variabel title.

  4. super.key

    Meneruskan key ke class induk. Key dipakai Flutter untuk mengenali widget.

  5. appBar: AppBar(...)

    AppBar merah bertuliskan "Detail Transaksi". Tombol back muncul otomatis karena halaman ini dibuka dengan push.

  6. Icon(Icons.receipt_long, size: 80)

    Ikon struk berukuran besar sebagai penanda halaman detail.

  7. Text(title, ...) / Text(amount, ...)

    Menampilkan judul dan nominal transaksi dari data yang diterima. Nominal ditampilkan lebih besar dan berwarna merah.

  8. Divider(height: 40, thickness: 2)

    Garis pemisah antara bagian judul dan bagian rincian.

  9. _buildDetailRow('Kategori', category)

    Memanggil widget pembantu untuk menampilkan satu baris rincian (label di kiri, nilai di kanan).

  10. _buildDetailRow(String label, String value)

    Widget pembantu berisi Row dengan MainAxisAlignment.spaceBetween agar label dan nilai terpisah ke dua sisi. Baris Status diisi tetap "Berhasil".

4

Langkah 4: Mengaktifkan Navigasi pada Dashboard (Push & Passing Data)

Buat file dashboard_screen.dart. Halaman ini menggabungkan UI Modul 1 dan 2, lalu mengaktifkan dua jenis navigasi: tombol FAB membuka form tambah transaksi, dan setiap ListTile transaksi membuka halaman detail sambil mengirim data baris yang diklik.

dart
import 'package:flutter/material.dart';
import 'add_transaction_screen.dart';
import 'transaction_detail_screen.dart';
 
class DashboardScreen extends StatelessWidget {
  const DashboardScreen({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dashboard Keuangan'),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      // Tombol mengambang (FAB) untuk tambah transaksi
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
        child: const Icon(Icons.add),
        onPressed: () {
          // NAVIGASI PUSH: Berpindah ke halaman Form
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const AddTransactionScreen()),
          );
        },
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Halo, Budi', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
            const SizedBox(height: 20),
            _buildBalanceCard(),
            const SizedBox(height: 20),
            const Text('Transaksi Terakhir', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 12),
            _buildTransactionList(context), // Mengirim context untuk navigasi
          ],
        ),
      ),
    );
  }
 
  Widget _buildBalanceCard() {
    return Card(
      elevation: 4,
      color: Colors.redAccent,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: const Padding(
        padding: EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Saldo Utama', style: TextStyle(fontSize: 16, color: Colors.white70)),
            SizedBox(height: 8),
            Text('Rp 5.000.000', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white)),
          ],
        ),
      ),
    );
  }
 
  Widget _buildTransactionList(BuildContext context) {
    return Card(
      elevation: 2,
      child: Column(
        children: [
          ListTile(
            leading: const CircleAvatar(backgroundColor: Colors.orange, child: Icon(Icons.fastfood, color: Colors.white)),
            title: const Text('Makan Siang'),
            subtitle: const Text('Hari ini'),
            trailing: const Text('- Rp 50.000', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
            onTap: () {
              // NAVIGASI PUSH DENGAN DATA (Passing Data)
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const TransactionDetailScreen(
                    title: 'Makan Siang',
                    amount: '- Rp 50.000',
                    category: 'Makanan',
                    date: '13 September 2026',
                  ),
                ),
              );
            },
          ),
          const Divider(height: 1),
          ListTile(
            leading: const CircleAvatar(backgroundColor: Colors.green, child: Icon(Icons.attach_money, color: Colors.white)),
            title: const Text('Gaji Bulanan'),
            subtitle: const Text('1 September 2026'),
            trailing: const Text('+ Rp 5.000.000', style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold)),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const TransactionDetailScreen(
                    title: 'Gaji Bulanan',
                    amount: '+ Rp 5.000.000',
                    category: 'Pemasukan',
                    date: '1 September 2026',
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

Penjelasan kode:

  1. import 'add_transaction_screen.dart' ...

    Mengimpor dua halaman tujuan navigasi: form tambah transaksi dan halaman detail.

  2. floatingActionButton: FloatingActionButton(...)

    Tombol bulat merah dengan ikon tambah (+) di pojok kanan bawah.

  3. Navigator.push(context, MaterialPageRoute(builder: ... AddTransactionScreen()))

    Push tanpa data: meletakkan halaman form di atas Dashboard pada tumpukan.

  4. SingleChildScrollView

    Membuat isi halaman bisa di-scroll jika tidak muat di layar.

  5. _buildBalanceCard()

    Kartu merah berisi saldo utama "Rp 5.000.000".

  6. _buildTransactionList(context)

  7. Membuat daftar transaksi. context dikirim sebagai parameter karena Navigator.push membutuhkan context untuk menemukan Navigator.

  8. ListTile(... onTap: () { ... })

    onTap berisi aksi yang dijalankan saat baris transaksi diklik.

  9. TransactionDetailScreen(title: 'Makan Siang', amount: ..., category: ..., date: ...)

    Passing data: nilai baris yang diklik dikirim melalui constructor halaman detail. Baris "Gaji Bulanan" mengirim datanya sendiri dengan cara yang sama.

  10. const Divider(height: 1)

    Garis tipis pemisah antarbaris transaksi.

5

Langkah 5: Menutup Form Otomatis Setelah Simpan (Pop)

Buka file add_transaction_screen.dart dari Modul 3. Tambahkan Navigator.pop(context) pada tombol Simpan agar form tertutup otomatis dan pengguna kembali ke Dashboard setelah data valid disimpan.

dart
import 'package:flutter/material.dart';
 
class AddTransactionScreen extends StatefulWidget {
  const AddTransactionScreen({super.key});
 
  @override
  State<AddTransactionScreen> createState() => _AddTransactionScreenState();
}
 
class _AddTransactionScreenState extends State<AddTransactionScreen> {
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Catat Transaksi"),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                controller: _titleController,
                decoration: const InputDecoration(labelText: "Judul", border: OutlineInputBorder()),
                validator: (value) => value!.isEmpty ? "Tidak boleh kosong" : null,
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _amountController,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(labelText: "Nominal", border: OutlineInputBorder()),
                validator: (value) => value!.isEmpty ? "Tidak boleh kosong" : null,
              ),
              const SizedBox(height: 32),
              ElevatedButton(
                style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16)),
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Transaksi Berhasil Disimpan!'), backgroundColor: Colors.green),
                    );
                    // NAVIGASI POP: Kembali ke halaman sebelumnya (Dashboard)
                    Navigator.pop(context);
                  }
                },
                child: const Text("Simpan", style: TextStyle(fontSize: 16)),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Penjelasan kode:

  1. class AddTransactionScreen extends StatefulWidget

    Menggunakan StatefulWidget karena form memiliki state, yaitu isi input dan status validasi.

  2. final _formKey = GlobalKey<FormState>()

    Kunci untuk mengakses state Form, misalnya untuk menjalankan validasi.

  3. _titleController / _amountController

    Controller untuk membaca dan mengatur teks pada input Judul dan Nominal.

  4. TextFormField(... validator: ...)

    Input dengan validasi. Jika kosong, muncul pesan "Tidak boleh kosong".

  5. keyboardType: TextInputType.number

    Menampilkan keyboard angka untuk input Nominal.

  6. _formKey.currentState!.validate()

    Menjalankan semua validator. Bernilai true jika semua input valid.

  7. ScaffoldMessenger.of(context).showSnackBar(...)

    Menampilkan notifikasi hijau "Transaksi Berhasil Disimpan!" di bagian bawah layar.

  8. Navigator.pop(context)

    Pop: menghapus halaman form dari atas tumpukan sehingga Dashboard tampil kembali.

IVLatihan dan Tugas

1

Menambahkan showDatePicker

Tugas dikerjakan pada file add_transaction_screen.dart. Input tanggal dapat diketik manual dengan format DD/MM/YYYY atau dipilih melalui ikon kalender yang membuka showDatePicker. Validasi menolak input kosong, format yang salah, dan tanggal yang tidak ada di kalender (misalnya 31/02/2026).

dart
import 'package:flutter/material.dart';
 
class AddTransactionScreen extends StatefulWidget {
  const AddTransactionScreen({super.key});
 
  @override
  State<AddTransactionScreen> createState() => _AddTransactionScreenState();
}
 
class _AddTransactionScreenState extends State<AddTransactionScreen> {
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  final _dateController = TextEditingController(); // TUGAS: controller tanggal
 
  // Batas tanggal yang bisa dipilih
  final DateTime _firstDate = DateTime(2000);
  final DateTime _lastDate = DateTime(2100);
 
  @override
  void dispose() {
    _titleController.dispose();
    _amountController.dispose();
    _dateController.dispose();
    super.dispose();
  }
 
  // Mengubah DateTime menjadi teks DD/MM/YYYY
  String _formatDate(DateTime date) {
    final day = date.day.toString().padLeft(2, '0');
    final month = date.month.toString().padLeft(2, '0');
    return '$day/$month/${date.year}';
  }
 
  // Mengubah teks DD/MM/YYYY menjadi DateTime (null jika tidak valid)
  DateTime? _parseDate(String input) {
    final match = RegExp(r'^(\d{2})/(\d{2})/(\d{4})$').firstMatch(input);
    if (match == null) return null;
 
    final day = int.parse(match.group(1)!);
    final month = int.parse(match.group(2)!);
    final year = int.parse(match.group(3)!);
    final date = DateTime(year, month, day);
 
    // Menolak tanggal yang tidak ada, misalnya 31/02/2026
    if (date.year != year || date.month != month || date.day != day) {
      return null;
    }
    return date;
  }
 
  // Validasi: wajib diisi + format benar
  String? _validateDate(String? value) {
    if (value == null || value.trim().isEmpty) {
      return "Tanggal wajib diisi";
    }
    final date = _parseDate(value.trim());
    if (date == null) {
      return "Format tanggal harus DD/MM/YYYY";
    }
    if (date.isBefore(_firstDate) || date.isAfter(_lastDate)) {
      return "Tanggal di luar rentang yang diizinkan";
    }
    return null;
  }
 
  // TUGAS (PLUS): memilih tanggal dengan showDatePicker
  Future<void> _pickDate() async {
    final current = _parseDate(_dateController.text.trim());
    final bool currentValid = current != null &&
        !current.isBefore(_firstDate) &&
        !current.isAfter(_lastDate);
 
    final DateTime? picked = await showDatePicker(
      context: context,
      initialDate: currentValid ? current : DateTime.now(),
      firstDate: _firstDate,
      lastDate: _lastDate,
    );
 
    if (picked != null) {
      _dateController.text = _formatDate(picked);
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Catat Transaksi"),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                controller: _titleController,
                decoration: const InputDecoration(labelText: "Judul", border: OutlineInputBorder()),
                validator: (value) => value!.isEmpty ? "Tidak boleh kosong" : null,
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _amountController,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(labelText: "Nominal", border: OutlineInputBorder()),
                validator: (value) => value!.isEmpty ? "Tidak boleh kosong" : null,
              ),
              const SizedBox(height: 16),
              // TUGAS: Input Tanggal Transaksi
              TextFormField(
                controller: _dateController,
                keyboardType: TextInputType.datetime,
                decoration: InputDecoration(
                  labelText: "Tanggal Transaksi",
                  hintText: "DD/MM/YYYY",
                  border: const OutlineInputBorder(),
                  suffixIcon: IconButton(
                    icon: const Icon(Icons.calendar_today),
                    tooltip: "Pilih tanggal",
                    onPressed: _pickDate,
                  ),
                ),
                validator: _validateDate,
              ),
              const SizedBox(height: 32),
              ElevatedButton(
                style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16)),
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Transaksi Berhasil Disimpan!'), backgroundColor: Colors.green),
                    );
                    // NAVIGASI POP: Kembali ke halaman sebelumnya (Dashboard)
                    Navigator.pop(context);
                  }
                },
                child: const Text("Simpan", style: TextStyle(fontSize: 16)),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Penjelasan kode:

  1. final _dateController = TextEditingController()

    Controller baru untuk membaca dan mengisi teks pada input tanggal.

  2. _firstDate / _lastDate

    Batas awal (tahun 2000) dan akhir (tahun 2100) tanggal yang boleh dipilih.

  3. void dispose()

    Melepas ketiga controller saat halaman ditutup agar tidak terjadi kebocoran memori.

  4. _formatDate(DateTime date)

    Mengubah objek DateTime menjadi teks DD/MM/YYYY. padLeft(2, '0') menambahkan angka 0 di depan, misalnya 1 menjadi 01.

  5. _parseDate(String input)

    Mengubah teks menjadi DateTime. RegExp memastikan pola dua digit/dua digit/empat digit. Hasilnya dicek ulang agar tanggal seperti 31/02/2026 ditolak, karena DateTime akan menggesernya menjadi 3 Maret.

  6. _validateDate(String? value)

    Validator input tanggal: mengembalikan pesan "Tanggal wajib diisi" jika kosong, "Format tanggal harus DD/MM/YYYY" jika format salah, atau pesan rentang jika di luar 2000–2100.

  7. Future<void> _pickDate() async

    Fungsi asinkron yang membuka dialog kalender. await menunggu sampai pengguna memilih tanggal atau membatalkan.

  8. showDatePicker(context: ..., initialDate: ..., firstDate: ..., lastDate: ...)

    Menampilkan dialog kalender bawaan Material. Tanggal awal yang disorot memakai isi field jika valid, dan hari ini jika tidak.

  9. if (picked != null) _dateController.text = ...

    Jika pengguna memilih tanggal (bukan membatalkan), tanggal ditulis ke field dalam format DD/MM/YYYY.

  10. TextFormField(controller: _dateController, ...)

    Input tanggal yang diletakkan sebelum tombol Simpan, dengan label "Tanggal Transaksi" dan petunjuk "DD/MM/YYYY".

  11. keyboardType: TextInputType.datetime

    Menampilkan keyboard yang sesuai untuk mengetik tanggal.

  12. suffixIcon: IconButton(... onPressed: _pickDate)

    Ikon kalender di sisi kanan input. Saat diklik, fungsi _pickDate dijalankan.

  13. validator: _validateDate

    Menghubungkan input tanggal dengan fungsi validasi, sehingga ikut diperiksa saat tombol Simpan ditekan.

VKesimpulan

Berdasarkan praktikum yang telah dilakukan, dapat disimpulkan bahwa:

  1. Navigator di Flutter mengelola halaman dengan prinsip tumpukan (stack). Navigator.push bersama MaterialPageRoute menambahkan halaman baru di atas tumpukan, sedangkan Navigator.pop menghapus halaman teratas sehingga pengguna kembali ke halaman sebelumnya.

  2. Halaman yang dibuka dengan push otomatis mendapatkan tombol back di AppBar, sehingga pengguna dapat kembali tanpa kode tambahan.

  3. Data dapat dikirim antarhalaman melalui constructor. Halaman penerima menyiapkan variabel final dan parameter required, lalu halaman pengirim mengisi nilainya saat memanggil constructor di dalam builder.

  4. Navigator.pop yang dipanggil setelah validasi berhasil membuat form tertutup otomatis dan mengembalikan pengguna ke Dashboard.

  5. Pada tugas, input Tanggal Transaksi berhasil ditambahkan dengan dua cara pengisian (ketik manual dan showDatePicker) serta validasi wajib diisi dan format DD/MM/YYYY.

  6. Keterbatasan aplikasi saat ini adalah data transaksi masih statis: transaksi yang disimpan dari form belum ditambahkan ke daftar di Dashboard. Hal ini membutuhkan pengembalian data dari halaman form ke Dashboard atau manajemen state, yang belum dibahas pada modul ini.