Report 2 AppMobile — Widgets, State, Layouting dan Styling

Aplikasi Mobile Semester 5 Putri Balqis Afradinata

I. Tujuan Praktikum

  • Memahami kerangka dasar aplikasi Flutter menggunakan MaterialApp dan Scaffold.
  • Membedakan penggunaan StatelessWidget dan StatefulWidget.
  • Memahami penggunaan state pada StatefulWidget.
  • Menerapkan fungsi setState() untuk membuat UI yang interaktif.
  • Menerapkan widget Row dan Column dalam penyusunan layout.
  • Mengatur padding, margin, warna, dan tampilan menggunakan Container.
  • Menggunakan ListTile untuk membuat daftar transaksi.
  • Menggunakan SingleChildScrollView agar tampilan dapat di-scroll.
  • Menggabungkan komponen dari Praktikum 1 dan Praktikum 2 menjadi sebuah dashboard sederhana.

II. Dasar Teori

Flutter merupakan framework yang digunakan untuk membangun aplikasi dengan menggunakan bahasa pemrograman Dart. Dalam Flutter, tampilan aplikasi dibangun menggunakan konsep widget.

StatelessWidget merupakan widget yang bersifat statis. Widget ini tidak memiliki state internal yang berubah setelah widget dirender. Contohnya adalah teks, ikon, maupun tampilan statis lainnya.

StatefulWidget merupakan widget yang dapat mengalami perubahan state sebagai respons terhadap interaksi pengguna atau perubahan data. StatefulWidget memiliki objek State yang digunakan untuk menyimpan data yang dapat berubah.

setState() digunakan untuk memberi tahu Flutter bahwa terdapat perubahan state sehingga tampilan dapat dirender ulang. Pada praktikum ini setState() digunakan untuk mengubah tampilan saldo pada kartu ketika ikon mata ditekan.

Row digunakan untuk menyusun widget secara horizontal, sedangkan Column digunakan untuk menyusun widget secara vertikal. Kedua widget tersebut digunakan untuk mengatur struktur tampilan aplikasi.

Container dapat digunakan untuk memberikan padding, margin, warna, dan dekorasi pada sebuah widget. Sementara itu, ListTile merupakan widget Material Design yang digunakan untuk membuat baris daftar dengan elemen leading, title, subtitle, dan trailing.

SingleChildScrollView digunakan agar konten yang memiliki ukuran lebih panjang daripada layar dapat digulir. Pada Praktikum 2, widget ini digunakan untuk membungkus seluruh isi dashboard.

III. Langkah-Langkah Praktikum

A. Praktikum 1 — Pengenalan Widgets

  1. Membuat Project Flutter
    Langkah pertama yaitu membuat project Flutter baru untuk praktikum.
    flutter create praktikum_1
                        

    Setelah project berhasil dibuat, project dapat dibuka menggunakan Visual Studio Code atau Android Studio.

  2. Membuat Widget Utama
    Aplikasi dibuat menggunakan MaterialApp sebagai root aplikasi. MaterialApp kemudian diarahkan ke DashboardScreen sebagai halaman awal.
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    // 1. WIDGET UTAMA
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Expense Tracker',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const DashboardScreen(),
        );
      }
    }
                        

    MaterialApp digunakan sebagai dasar aplikasi Flutter dan Scaffold digunakan untuk membuat struktur halaman.

  3. Membuat Halaman Utama (Scaffold)
    DashboardScreen dibuat menggunakan Scaffold. Pada halaman ini terdapat AppBar serta komponen GreetingWidget dan BalanceCardWidget.
    class DashboardScreen extends StatelessWidget {
      const DashboardScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Praktikum 1: Widgets'),
            backgroundColor: Colors.blue,
          ),
    
          body: const Padding(
            padding: EdgeInsets.all(16.0),
    
            child: Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
    
              children: [
    
                GreetingWidget(),
    
                SizedBox(height: 20),
    
                BalanceCardWidget(),
    
              ],
            ),
          ),
        );
      }
    }
                        

    Column digunakan untuk menyusun GreetingWidget dan BalanceCardWidget secara vertikal.

  4. Membuat Widget Sapaan (StatelessWidget)
    Widget sapaan dibuat menggunakan StatelessWidget karena tampilannya bersifat statis.
    class GreetingWidget extends StatelessWidget {
      const GreetingWidget({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Row(
          children: [
    
            CircleAvatar(
              radius: 24,
              backgroundColor: Colors.blueAccent,
    
              child: Icon(
                Icons.person,
                size: 30,
                color: Colors.white,
              ),
            ),
    
            SizedBox(width: 12),
    
            Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
    
              children: [
    
                Text(
                  'Halo, Budi',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight:
                        FontWeight.bold,
                  ),
                ),
    
                Text(
                  'Selamat datang kembali!',
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.grey,
                  ),
                ),
    
              ],
            ),
    
          ],
        );
      }
    }
                        

    Row digunakan untuk menempatkan avatar dan teks secara horizontal, sedangkan Column digunakan untuk menempatkan dua teks sapaan secara vertikal.

  5. Membuat Kartu Saldo (StatefulWidget)
    BalanceCardWidget dibuat menggunakan StatefulWidget karena tampilannya dapat berubah ketika pengguna menekan ikon mata.
    class BalanceCardWidget extends StatefulWidget {
      const BalanceCardWidget({super.key});
    
      @override
      State createState() =>
          _BalanceCardWidgetState();
    }
    
    class _BalanceCardWidgetState
        extends State {
    
      bool _isBalanceVisible = true;
    
      void _toggleVisibility() {
    
        setState(() {
          _isBalanceVisible =
              !_isBalanceVisible;
        });
    
      }
    
      @override
      Widget build(BuildContext context) {
    
        return Card(
          elevation: 4,
    
          shape: RoundedRectangleBorder(
            borderRadius:
                BorderRadius.circular(16),
          ),
    
          color: Colors.blueAccent,
    
          child: Padding(
            padding:
                const EdgeInsets.all(20.0),
    
            child: Column(
              crossAxisAlignment:
                  CrossAxisAlignment.start,
    
              children: [
    
                Row(
                  mainAxisAlignment:
                      MainAxisAlignment
                          .spaceBetween,
    
                  children: [
    
                    const Text(
                      'Saldo Utama',
                      style: TextStyle(
                        fontSize: 16,
                        color:
                            Colors.white70,
                      ),
                    ),
    
                    IconButton(
                      icon: Icon(
                        _isBalanceVisible
                            ? Icons.visibility
                            : Icons.visibility_off,
    
                        color: Colors.white,
                      ),
    
                      onPressed:
                          _toggleVisibility,
                    ),
    
                  ],
                ),
    
                const SizedBox(height: 8),
    
                Text(
                  _isBalanceVisible
                      ? 'Rp 5.000.000'
                      : 'Rp *********',
    
                  style: const TextStyle(
                    fontSize: 28,
                    fontWeight:
                        FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
    
              ],
            ),
          ),
        );
      }
    }
                        

    Variabel _isBalanceVisible digunakan untuk menyimpan status apakah saldo sedang ditampilkan atau disembunyikan.

    Fungsi _toggleVisibility() menggunakan setState() untuk mengubah nilai state tersebut sehingga UI dapat diperbarui.

  6. Menjalankan Program Praktikum 1
    Setelah seluruh widget selesai dibuat, aplikasi dijalankan menggunakan Chrome.
    flutter run -d chrome -t lib/main.dart
                        

    Setelah dijalankan, aplikasi menampilkan sapaan pengguna dan kartu saldo. Ikon mata pada kartu saldo dapat ditekan untuk mengubah tampilan saldo.

B. Praktikum 2 — Layouting & Styling

  1. Membuat atau Memperbarui Dashboard
    Pada Praktikum 2, dashboard dari Praktikum 1 dikembangkan dengan menambahkan widget baru. Seluruh isi dashboard dibungkus menggunakan SingleChildScrollView.
    class DashboardScreen extends StatelessWidget {
      const DashboardScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
    
        return Scaffold(
    
          appBar: AppBar(
            title:
                const Text(
                    'Praktikum 2: Layouting'),
            backgroundColor:
                Colors.blue,
          ),
    
          body:
              const SingleChildScrollView(
    
            child: Padding(
    
              padding:
                  EdgeInsets.all(16.0),
    
              child: Column(
    
                crossAxisAlignment:
                    CrossAxisAlignment.start,
    
                children: [
    
                  GreetingWidget(),
    
                  SizedBox(height: 20),
    
                  BalanceCardWidget(),
    
                  SizedBox(height: 20),
    
                  ActionButtonsWidget(),
    
                  SizedBox(height: 20),
    
                  RecentTransactionsWidget(),
    
                ],
              ),
            ),
          ),
        );
      }
    }
                        

    SingleChildScrollView digunakan agar seluruh isi dashboard dapat digulir ketika konten melebihi ukuran layar.

  2. Menyusun Tombol Aksi Menggunakan Row
    Widget ActionButtonsWidget dibuat menggunakan Row untuk meletakkan tombol Pemasukan, Pengeluaran, dan Transfer secara horizontal.
    class ActionButtonsWidget
        extends StatelessWidget {
    
      const ActionButtonsWidget({
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
    
        return Row(
    
          mainAxisAlignment:
              MainAxisAlignment.spaceEvenly,
    
          children: [
    
            _buildActionButton(
              Icons.arrow_downward,
              'Pemasukan',
              Colors.green,
            ),
    
            _buildActionButton(
              Icons.arrow_upward,
              'Pengeluaran',
              Colors.red,
            ),
    
            _buildActionButton(
              Icons.swap_horiz,
              'Transfer',
              Colors.blue,
            ),
    
          ],
        );
      }
    
      Widget _buildActionButton(
        IconData icon,
        String label,
        Color color,
      ) {
    
        return Column(
    
          children: [
    
            Container(
    
              padding:
                  const EdgeInsets.all(12),
    
              decoration:
                  BoxDecoration(
    
                color:
                    color.withOpacity(0.2),
    
                borderRadius:
                    BorderRadius.circular(12),
    
              ),
    
              child: Icon(
                icon,
                color: color,
                size: 28,
              ),
            ),
    
            const SizedBox(height: 8),
    
            Text(
              label,
              style:
                  const TextStyle(
                fontWeight:
                    FontWeight.w500,
              ),
            ),
    
          ],
        );
      }
    }
                        

    Row digunakan untuk membuat tiga tombol tersusun secara horizontal. Nilai spaceEvenly digunakan untuk membagi ruang kosong secara merata.

  3. Menyusun Riwayat Transaksi
    Daftar transaksi dibuat menggunakan Column dan beberapa ListTile. Setiap transaksi memiliki leading, title, subtitle, dan trailing.
    class RecentTransactionsWidget
        extends StatelessWidget {
    
      const RecentTransactionsWidget({
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
    
        return Column(
    
          crossAxisAlignment:
              CrossAxisAlignment.start,
    
          children: [
    
            const Text(
              'Transaksi Terakhir',
    
              style: TextStyle(
                fontSize: 18,
                fontWeight:
                    FontWeight.bold,
              ),
            ),
    
            const SizedBox(height: 12),
    
            Card(
    
              elevation: 2,
    
              shape:
                  RoundedRectangleBorder(
                borderRadius:
                    BorderRadius.circular(12),
              ),
    
              child: Column(
    
                children: [
    
                  ListTile(
    
                    leading:
                        const CircleAvatar(
    
                      backgroundColor:
                          Colors.redAccent,
    
                      child: Icon(
                        Icons.fastfood,
                        color:
                            Colors.white,
                      ),
    
                    ),
    
                    title:
                        const Text(
                            'Makan Siang'),
    
                    subtitle:
                        const Text(
                            '13 Sep 2026'),
    
                    trailing:
                        const Text(
    
                      '- Rp 50.000',
    
                      style: TextStyle(
                        color: Colors.red,
                        fontWeight:
                            FontWeight.bold,
                      ),
    
                    ),
    
                  ),
    
                  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(
                            '01 Sep 2026'),
    
                    trailing:
                        const Text(
    
                      '+ Rp 5.000.000',
    
                      style: TextStyle(
                        color: Colors.green,
                        fontWeight:
                            FontWeight.bold,
                      ),
    
                    ),
    
                  ),
    
                  const Divider(height: 1),
    
                  ListTile(
    
                    leading:
                        const CircleAvatar(
    
                      backgroundColor:
                          Colors.blueAccent,
    
                      child: Icon(
                        Icons.directions_car,
                        color:
                            Colors.white,
                      ),
    
                    ),
    
                    title:
                        const Text(
                            'Isi Bensin'),
    
                    subtitle:
                        const Text(
                            '10 Sep 2026'),
    
                    trailing:
                        const Text(
    
                      '- Rp 150.000',
    
                      style: TextStyle(
                        color: Colors.red,
                        fontWeight:
                            FontWeight.bold,
                      ),
    
                    ),
    
                  ),
    
                ],
              ),
            ),
    
          ],
        );
      }
    }
                        

    Column digunakan untuk menyusun daftar transaksi dari atas ke bawah. ListTile digunakan agar setiap transaksi memiliki struktur yang rapi.

  4. Integrasi Dashboard
    Komponen dari Praktikum 1 dan Praktikum 2 kemudian digabungkan ke dalam satu dashboard.

    Komponen yang digunakan adalah:

    • GreetingWidget
    • BalanceCardWidget
    • ActionButtonsWidget
    • RecentTransactionsWidget

    Seluruh komponen ditempatkan dalam Column dan dibungkus dengan SingleChildScrollView agar halaman dapat di-scroll.

  5. Menjalankan Program Praktikum 2
    Setelah seluruh komponen selesai dibuat, aplikasi dijalankan menggunakan Chrome.
    flutter run -d chrome -t lib/main.dart
                        

    Hasil akhir berupa dashboard sederhana yang menampilkan sapaan pengguna, kartu saldo, tombol aksi, dan daftar transaksi.

IV. Tugas Praktikum

A. Tugas Praktikum 1

  1. Mengubah Warna BalanceCard
    Warna kartu saldo yang sebelumnya menggunakan Colors.blueAccent diubah menjadi Colors.teal.
    color: Colors.teal,
                        

    Perubahan dilakukan pada property color milik Card pada BalanceCardWidget.

  2. Menambahkan Nomor Rekening
    Teks statis berupa nomor rekening ditambahkan pada bagian bawah nominal saldo.
    const Text(
      'No. Rekening: 1234-5678',
      style: TextStyle(
        color: Colors.white70,
      ),
    ),
                        

    Dengan penambahan tersebut, kartu saldo menampilkan informasi saldo sekaligus nomor rekening pengguna.

B. Tugas Praktikum 2

  1. Styling Warna Transaksi
    Pada widget daftar transaksi, warna trailing text dibuat berbeda berdasarkan jenis transaksi.

    Pengeluaran menggunakan warna merah.

    trailing: const Text(
      '- Rp 50.000',
      style: TextStyle(
        color: Colors.red,
        fontWeight: FontWeight.bold,
      ),
    ),
                        

    Pemasukan menggunakan warna hijau.

    trailing: const Text(
      '+ Rp 5.000.000',
      style: TextStyle(
        color: Colors.green,
        fontWeight: FontWeight.bold,
      ),
    ),
                        
  2. Menambahkan Dua Transaksi Fiktif
    Dua transaksi fiktif ditambahkan ke dalam daftar "Transaksi Terakhir" di bawah transaksi yang sudah tersedia.
    ListTile(
    
      leading:
          const CircleAvatar(
    
        backgroundColor:
            Colors.redAccent,
    
        child: Icon(
          Icons.shopping_cart,
          color: Colors.white,
        ),
    
      ),
    
      title:
          const Text(
              'Belanja Bulanan'),
    
      subtitle:
          const Text(
              '15 Sep 2026'),
    
      trailing:
          const Text(
    
        '- Rp 300.000',
    
        style: TextStyle(
          color: Colors.red,
          fontWeight:
              FontWeight.bold,
        ),
    
      ),
    
    ),
    
    const Divider(height: 1),
    
    ListTile(
    
      leading:
          const CircleAvatar(
    
        backgroundColor:
            Colors.green,
    
        child: Icon(
          Icons.card_giftcard,
          color: Colors.white,
        ),
    
      ),
    
      title:
          const Text(
              'Bonus'),
    
      subtitle:
          const Text(
              '16 Sep 2026'),
    
      trailing:
          const Text(
    
        '+ Rp 500.000',
    
        style: TextStyle(
          color: Colors.green,
          fontWeight:
              FontWeight.bold,
        ),
    
      ),
    
    ),
                        

    Transaksi "Belanja Bulanan" digunakan sebagai contoh pengeluaran, sedangkan "Bonus" digunakan sebagai contoh pemasukan.

V. Hasil dan Pembahasan

Berdasarkan praktikum yang telah dilakukan, pada Praktikum 1 berhasil diterapkan konsep dasar widget pada Flutter. GreetingWidget dibuat menggunakan StatelessWidget karena tampilannya bersifat statis, sedangkan BalanceCardWidget menggunakan StatefulWidget karena memiliki state yang dapat berubah.

Penggunaan setState() pada BalanceCardWidget memungkinkan tampilan saldo berubah ketika pengguna menekan ikon mata. Ketika state bernilai true, saldo ditampilkan sebagai Rp 5.000.000. Ketika state berubah, saldo ditampilkan dalam bentuk sensor.

Pada Praktikum 2, tampilan aplikasi dikembangkan menggunakan konsep layouting dan styling. Row digunakan untuk menyusun tombol aksi secara horizontal, sedangkan Column digunakan untuk menyusun daftar transaksi secara vertikal.

ListTile digunakan untuk menyusun informasi transaksi yang terdiri dari ikon, nama transaksi, tanggal, dan nominal. Penggunaan warna pada trailing membantu membedakan transaksi pengeluaran dan pemasukan.

SingleChildScrollView juga digunakan pada dashboard agar seluruh komponen tetap dapat ditampilkan dan halaman dapat digulir ketika isi dashboard melebihi ukuran layar.

Pada tugas praktikum, BalanceCardWidget berhasil dimodifikasi dengan mengganti warna kartu menjadi Colors.teal dan menambahkan informasi nomor rekening. Selain itu, dua transaksi fiktif berhasil ditambahkan ke dalam daftar transaksi pada Praktikum 2.

VI. Kesimpulan

Dari praktikum yang telah dilakukan dapat disimpulkan bahwa Flutter menggunakan widget sebagai komponen utama dalam membangun antarmuka aplikasi.

Pada Praktikum 1, dipelajari perbedaan antara StatelessWidget dan StatefulWidget. StatelessWidget digunakan untuk tampilan yang bersifat statis, sedangkan StatefulWidget digunakan ketika terdapat data atau tampilan yang dapat berubah.

Penggunaan setState() memungkinkan perubahan state ditampilkan kembali pada UI. Konsep tersebut diterapkan pada kartu saldo yang dapat menampilkan maupun menyembunyikan nominal saldo.

Pada Praktikum 2, dipelajari penggunaan Row, Column, Container, ListTile, dan SingleChildScrollView untuk membangun tampilan dashboard yang lebih terstruktur.

Dengan menggabungkan kedua praktikum, dapat dipahami dasar pembuatan UI Flutter, pengelolaan state, penyusunan layout, serta pemberian styling pada aplikasi mobile sederhana.