LAPORAN PRAKTIKUM APLIKASI MOBILE

MODUL IV: Navigasi & Passing Data (Navigator, Konsep Stack, dan Constructor)

NIM: 2411533019 - Adila Bintang Fauzi

Flutter / Mobile App Repository GitHub

I. Tujuan Praktikum

  • Menerapkan Navigator.push untuk berpindah dari satu layar ke layar baru.
  • Menggunakan Navigator.pop untuk menutup layar dan kembali ke halaman sebelumnya setelah aksi selesai.
  • Mengirim data (String, int) antar layar menggunakan Constructor.
  • Mengimplementasikan komponen showDatePicker untuk memilih tanggal transaksi serta menambahkan validasi input.

II. Alat dan Bahan

  1. Flutter SDK
  2. Code Editor: Visual Studio Code / Android Studio
  3. Emulator / Perangkat Seluler (Android/iOS)

III. Dasar Teori

  • Navigator & Stack: Flutter mengelola rute/halaman menggunakan struktur data tumpukan (stack). Navigator.push menumpuk halaman baru di paling atas, sedangkan Navigator.pop menghapus halaman teratas untuk kembali ke halaman sebelumnya.
  • Constructor & Passing Data: Untuk mengalirkan data dari halaman asal ke halaman tujuan, kelas target harus menyiapkan variabel penampung bertipe final dan dideklarasikan pada constructor menggunakan kata kunci required.
  • Form Validation & DatePicker: Menggunakan Form dengan GlobalKey<FormState> untuk memvalidasi input teks, serta memanfaatkan fungsi showDatePicker bawaan Flutter untuk memilih tanggal secara interaktif.

IV. Struktur Proyek

Proyek disusun ke dalam 4 file terpisah di dalam folder lib/:

  • lib/main.dart – Inisialisasi dan konfigurasi utama aplikasi.
  • lib/dashboard_screen.dart – Halaman utama yang menampilkan saldo dan daftar transaksi.
  • lib/add_transaction_screen.dart – Form pencatatan transaksi baru (termasuk input tanggal dan validasi).
  • lib/transaction_detail_screen.dart – Halaman detail untuk menampilkan informasi transaksi yang dipilih.

V. Implementasi Kode & Tampilan Hasil

1. File: lib/main.dart

Menjalankan aplikasi dan mengatur tema warna utama menjadi merah.

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(),
    );
  }
}
2. File: lib/dashboard_screen.dart

Menampilkan antarmuka utama. Tombol FAB bernavigasi ke halaman form AddTransactionScreen, dan item transaksi bernavigasi ke TransactionDetailScreen sambil membawa data transaksi (passing data).

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,
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
        child: const Icon(Icons.add),
        onPressed: () {
          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),
          ],
        ),
      ),
    );
  }

  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: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => 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) => TransactionDetailScreen(
                    title: 'Gaji Bulanan',
                    amount: '+ Rp 5.000.000',
                    category: 'Pemasukan',
                    date: '1 September 2026',
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}
3. File: lib/add_transaction_screen.dart

Menyediakan form pencatatan transaksi. Sesuai dengan instruksi Latihan 1.5, ditambahkan input tanggal menggunakan showDatePicker dengan format DD/MM/YYYY dan dilengkapi validator required.

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();

  @override
  void dispose() {
    _titleController.dispose();
    _amountController.dispose();
    _dateController.dispose();
    super.dispose();
  }

  // Fungsi untuk menampilkan date picker dialog
  Future<void> _selectDate(BuildContext context) async {
    DateTime? pickedDate = await showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2000),
      lastDate: DateTime(2100),
    );

    if (pickedDate != null) {
      setState(() {
        _dateController.text =
            "${pickedDate.day.toString().padLeft(2, '0')}/${pickedDate.month.toString().padLeft(2, '0')}/${pickedDate.year}";
      });
    }
  }

  @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 == null || 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 == null || value.isEmpty ? "Tidak boleh kosong" : null,
              ),
              const SizedBox(height: 16),
              // Input Tanggal Transaksi (Tugas Latihan)
              TextFormField(
                controller: _dateController,
                readOnly: true,
                onTap: () => _selectDate(context),
                decoration: InputDecoration(
                  labelText: "Tanggal Transaksi (DD/MM/YYYY)",
                  border: const OutlineInputBorder(),
                  suffixIcon: IconButton(
                    icon: const Icon(Icons.calendar_today),
                    onPressed: () => _selectDate(context),
                  ),
                ),
                validator: (value) =>
                    value == null || value.isEmpty ? "Tanggal wajib diisi!" : 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,
                      ),
                    );
                    Navigator.pop(context); // Kembali ke Dashboard
                  }
                },
                child: const Text("Simpan", style: TextStyle(fontSize: 16)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Pengujian Input Tanggal & Validasi Form

Tampilan dialog pemilih tanggal (showDatePicker) dan pesan error saat formulir coba disimpan tanpa mengisi kolom tanggal:

Dialog DatePicker Flutter

1. Dialog Pemilihan Tanggal (DatePicker)

Hasil Validasi Form Tanggal

2. Validasi Input Tanggal Wajib Diisi

Input Tanggal Terisi pada Catat Transaksi

3. Form Catat Transaksi Terisi Lengkap Beserta Tanggal Transaksi

4. File: lib/transaction_detail_screen.dart

Menerima parameter data (title, amount, category, date) via constructor dan menginstasikannya ke tampilan antarmuka.

import 'package:flutter/material.dart';

class TransactionDetailScreen extends StatelessWidget {
  final String title;
  final String amount;
  final String category;
  final String date;

  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 _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)),
      ],
    );
  }
}
Tampilan Detail Transaksi (Passing Data)

Hasil penerimaan data dari halaman Dashboard menuju Halaman Detail melalui Constructor:

Detail Transaksi Hasil Praktikum 4

4. Antarmuka Detail Transaksi yang Menerima Parameter Data

VI. Hasil dan Pembahasan

  1. Navigasi Layar (Navigator.push & Navigator.pop):
    • Menekan tombol tambah (+) membuka layar AddTransactionScreen. Setelah form divalidasi dan disimpan, perintah Navigator.pop(context) mematikan/menutup layar form dan membawa pengguna kembali ke DashboardScreen.
    • Menekan salah satu daftar transaksi pada ListTile membuka layar TransactionDetailScreen.
  2. Pengiriman Data (Passing Data):
    • Data seperti judul, nominal, kategori, dan tanggal berhasil dikirimkan dari DashboardScreen melalui parameter constructor ke TransactionDetailScreen dan ditampilkan secara dinamis.
  3. Penyelesaian Tugas Latihan (Input Tanggal & Validasi):
    • Ditambahkan TextFormField khusus tanggal dengan fitur readOnly: true.
    • Pengguna menekan kolom input tanggal atau ikon kalender untuk memunculkan modal dialog showDatePicker.
    • Tanggal yang dipilih dikonversi ke format teks DD/MM/YYYY.
    • Validasi diterapkan sehingga form tidak dapat disimpan jika kolom tanggal dalam keadaan kosong (menampilkan pesan kesalahan *"Tanggal wajib diisi!"*).

VII. Kesimpulan

Pada praktikum ini, telah berhasil dipelajari dan diimplementasikan konsep dasar navigasi di Flutter menggunakan Navigator.push dan Navigator.pop. Selain itu, mekanisme pengiriman data antar layar melalui constructor berhasil diterapkan secara baik. Penambahan komponen showDatePicker dan mekanisme validasi form melengkapi fitur pencatatan transaksi sesuai dengan tugas latihan yang diberikan.