Skip to main content

Lab 7: Flutter Navigation, Input, dan Form

Pemrograman Berbasis Platform (CSGE602022) - diselenggarakan oleh Fakultas Ilmu Komputer, Universitas Indonesia, Semester Ganjil 2022/2023


Tujuan Pembelajaran

Setelah menyelesaikan tutorial ini, mahasiswa diharapkan untuk:

  • Memahami elemen input dan form pada Flutter.
  • Memahami navigasi dan routing dasar pada Flutter.
  • Memahami alur pembuatan form dan data pada Flutter.

Pada saat belajar pengembangan web, kalian pasti sudah belajar bahwa dalam sebuah website kita dapat berpindah-pindah halaman sesuai dengan url yang diakses. Begitu juga pada sebuah aplikasi, kita dapat melakukan perpindahan dari satu halaman ke halaman lain. Bedanya, pada sebuah aplikasi, yang kita gunakan untuk berpindah bukanlah dengan mengakses url.

Flutter menyediakan sistem yang cukup lengkap untuk melakukan navigasi antar halaman. Salah satu cara yang dapat kita gunakan untuk berpindah-pindah halaman adalah dengan menggunakan widget Navigator. Widget Navigator menampilkan layar seakan sebagai sebuah tumpukan (stack). Untuk menavigasi sebuah halaman baru, kita dapat mengakses Navigator melalui BuildContext dan memanggil fungsi push() atau pop(). Berikut contoh penggunaan Navigator.

...
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MyNewScreen(myProp: prop),
),
);
},
child: Text(myProp.someValue),
...

Untuk mengetahui lebih dalam terkait Navigator, dapat dibaca pada tautan berikut: https://api.flutter.dev/flutter/widgets/Navigator-class.html

Input dan Form pada Flutter

Sama halnya dengan sebuah web, sebuah aplikasi juga dapat berinteraksi dengan pengguna melalui input dan form. Flutter memiliki widget Form yang dapat kita manfaatkan untuk menjadi wadah bagi beberapa input field widget yang kita buat. Sama hal nya dengan input field pada web, Flutter juga memiliki banyak tipe input field, salah satunya widget TextField.

Untuk mencoba-coba, jalankan perintah berikut:

flutter create --sample=widgets.Form.1 formSample

Untuk mengetahui lebih lanjut terkait widget Form, dapat dibaca pada tautan berikut: https://api.flutter.dev/flutter/widgets/Form-class.html

Tutorial: Menambahkan Drawer Menu dan Navigasi

  1. Buka proyek yang sebelumnya telah dibuat pada tutorial 6 dengan menggunakan IDE favoritmu.

  2. Ubahlah kode pada lib/main.dart agar menjadi seperti berikut.

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

    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: const MyHomePage(),
    );
    }
    }

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

    final String title = 'Flutter Demo Home Page';

    @override
    State<MyHomePage> createState() => _MyHomePageState();
    }
    ...
  3. Tambahkan potongan kode berikut pada file lib/main.dart.

      ...
    appBar: AppBar(
    title: Text(widget.title),
    ),
    // Menambahkan drawer menu
    drawer: Drawer(
    child: Column(
    children: [
    // Menambahkan clickable menu
    ListTile(
    title: const Text('Counter'),
    onTap: () {
    // Route menu ke halaman utama
    Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (context) => const MyHomePage()),
    );
    },
    ),
    ListTile(
    title: const Text('Form'),
    onTap: () {
    // Route menu ke halaman form
    Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (context) => const MyFormPage()),
    );
    },
    ),
    ],
    ),
    ),
    body: Center(
    ...
  4. Jangan jalankan aplikasi terlebih dahulu, karena akan muncul error akibat tidak ditemukannya halaman MyFormPage. Lakukan tutorial di bawah terlebih dahulu untuk membuat halaman tersebut.

Tutorial: Membuat Halaman Baru

  1. Buatlah file baru pada folder lib dengan nama form.dart.

  2. Tambahkan boilerplate berikut ke dalam file tersebut.

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

    @override
    State<MyFormPage> createState() => _MyFormPageState();
    }

    class _MyFormPageState extends State<MyFormPage> {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: Text('Form'),
    ),
    body: Center(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    Text('Hello World!'),
    ],
    ),
    ),
    );
    }
    }
  3. Tambahkan ulang kode drawer yang sebelumnya ditambahkan pada halaman utama ke dalam halaman yang baru saja kamu buat.

  4. Cek apakah setiap widget antar file telah diimpor. Jika belum, impor file ke dalam file lainnya. Berikut adalah contohnya.

    /lib/main.dart

    import 'package:flutter_app/form.dart';
    import 'package:flutter/material.dart';
    ...

    /lib/form.dart

    import 'package:flutter_app/main.dart';
    import 'package:flutter/material.dart';
    ...
  5. Coba jalankan aplikasi untuk melihat perubahan yang baru saja kamu buat. Seharusnya terdapat drawer atau hamburger menu pada pojok kiri atas dan halaman form berisi teks “Hello World!”.

Tutorial: Menambahkan Form dan Elemen Input

Kita akan mencoba untuk menambahkan lima tipe input form yang ada di Flutter, yaitu TextFormField, CheckboxListTile, SwitchListTile, DropdownButton, dan Slider.

  1. Ubah widget Center menjadi Form.

      ...
    body: Form(),
    ...
  2. Tambahkan form key sebagai handle dari form state, validasi form, and penyimpanan form.

    ...
    class _MyFormPageState extends State<MyFormPage> {
    final _formKey = GlobalKey<FormState>();

    @override
    ...
    body: Form(
    key: _formKey,
    ),
    ...
  3. Buatlah widget SingleChildScrollView sebagai child dari widget Form.

      ...
    body: Form(
    key: _formKey,
    child: SingleChildScrollView(),
    ),
    ...
  4. Buatlah widget Container sebagai child dari widget SingleChildScrollView.

        ...
    child: SingleChildScrollView(
    child: Container(),
    ),
    ...
  5. Tambahkan padding pada widget Container agar tampilan rapi. Sebagai contoh, kita akan memakai padding sebesar 20 pixels.

          ...
    child: Container(
    padding: const EdgeInsets.all(20.0),
    ),
    ...
  6. Buatlah widget Column sebagai child dari widget Container.

          ...
    child: Container(
    padding: const EdgeInsets.all(20.0),
    child: Column(),
    ),
    ...
  7. Buatlah widget TextFormField yang dibungkus oleh Padding sebagai salah satu children dari widget Column. Selain itu, tambahkan variabel baru sebagai placeholder dari value yang diketik pada TextFormField nantinya. Buatlah TextFormField sebagai penampung variabel nama lengkap. Berikut adalah contohnya.

    ...
    class _MyFormPageState extends State<MyFormPage> {
    final _formKey = GlobalKey<FormState>();
    String _namaLengkap = "";

    @override
    ...
    child: Column(
    children: [
    Padding(
    // Menggunakan padding sebesar 8 pixels
    padding: const EdgeInsets.all(8.0),
    child: TextFormField(
    decoration: InputDecoration(
    hintText: "Contoh: Pak Dengklek",
    labelText: "Nama Lengkap",
    // Menambahkan icon agar lebih intuitif
    icon: const Icon(Icons.people),
    // Menambahkan circular border agar lebih rapi
    border: OutlineInputBorder(
    borderRadius: BorderRadius.circular(5.0),
    ),
    ),
    // Menambahkan behavior saat nama diketik
    onChanged: (String? value) {
    setState(() {
    _namaLengkap = value!;
    });
    },
    // Menambahkan behavior saat data disimpan
    onSaved: (String? value) {
    setState(() {
    _namaLengkap = value!;
    });
    },
    // Validator sebagai validasi form
    validator: (String? value) {
    if (value == null || value.isEmpty) {
    return 'Nama lengkap tidak boleh kosong!';
    }
    return null;
    },
    ),
    ),
    ],
    ...
  8. Buatlah beberapa widget CheckboxListTile sebagai children dari widget Column. Selain itu, tambahkan beberapa variabel baru sebagai placeholder dari value setiap CheckboxListTile nantinya. CheckboxListTile akan menjadi penampung pilihan jenjang Sarjana, Diploma, Magister, atau Doktor. Berikut adalah contohnya.

    ...
    bool jenjangSarjana = false;
    bool jenjangDiploma = false;
    bool jenjangMagister = false;
    bool jenjangDoktor = false;

    @override
    ...
    Container(
    margin: const EdgeInsets.all(8),
    decoration: BoxDecoration(
    border: Border.all(color: Colors.grey),
    borderRadius: BorderRadius.circular(5),
    ),
    child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
    const ListTile(
    leading: Icon(Icons.school),
    title: Text("Jenjang"),
    ),
    CheckboxListTile(
    title: const Text('Sarjana'),
    value: jenjangSarjana,
    onChanged: (bool? value) {
    setState(() {
    jenjangSarjana = value!;
    if (value){
    jenjangMagister = jenjangDiploma = jenjangDoktor = false;
    }
    });
    },
    ),
    CheckboxListTile(
    title: const Text('Diploma'),
    value: jenjangDiploma,
    onChanged: (bool? value) {
    setState(() {
    jenjangDiploma = value!;
    if (value){
    jenjangMagister = jenjangSarjana = jenjangDoktor = false;
    }
    });
    },
    ),
    CheckboxListTile(
    title: const Text('Magister'),
    value: jenjangMagister,
    onChanged: (bool? value) {
    setState(() {
    jenjangMagister = value!;
    if (value){
    jenjangDiploma = jenjangSarjana = jenjangDoktor = false;
    }
    });
    },
    ),
    CheckboxListTile(
    title: const Text('Doktor'),
    value: jenjangDoktor,
    onChanged: (bool? value) {
    setState(() {
    jenjangDoktor = value!;
    if (value){
    jenjangMagister = jenjangSarjana = jenjangDiploma = false;
    }
    });
    },
    ),
    ],
    ),
    ),
    ...
  9. Buatlah widget Slider sebagai salah satu children dari widget Column. Selain itu, tambahkan variabel baru sebagai placeholder dari value Slider nantinya. Slider akan menjadi penampung pilihan umur. Berikut adalah contohnya.

    ...
    double umur = 0;

    @override
    ...
    ListTile(
    leading: const Icon(Icons.co_present),
    title: Row(
    children: [
    Text('Umur: ${umur.round()}'),
    ],
    ),
    subtitle: Slider(
    value: umur,
    max: 100,
    divisions: 100,
    label: umur.round().toString(),
    onChanged: (double value) {
    setState(() {
    umur = value;
    });
    },
    ),
    ),
    ...
  10. Buatlah widget DropdownButton sebagai salah satu children dari widget Column. Tambahkan variabel baru sebagai placeholder dari value Slider nantinya. Selain itu, tambahkan pula List of String yang menampung opsi yang akan ditampilkan pada dropdown. Slider akan menjadi penampung pilihan kelas PBP. Berikut adalah contohnya.

    ...
    String kelasPBP = 'A';
    List<String> listKelasPBP = ['A', 'B', 'C', 'D', 'E', 'F', 'KI'];

    @override
    ...
    ListTile(
    leading: const Icon(Icons.class_),
    title: const Text(
    'Kelas PBP',
    ),
    trailing: DropdownButton(
    value: kelasPBP,
    icon: const Icon(Icons.keyboard_arrow_down),
    items: listKelasPBP.map((String items) {
    return DropdownMenuItem(
    value: items,
    child: Text(items),
    );
    }).toList(),
    onChanged: (String? newValue) {
    setState(() {
    kelasPBP = newValue!;
    });
    },
    ),
    ),
    ...
  11. Buatlah widget SwitchListTile yang dibungkus oleh Padding sebagai salah satu children dari widget Column. Selain itu, tambahkan variabel baru sebagai placeholder dari value dari SwitchListTile nantinya. Buatlah SwitchListTile sebagai penampung variabel status apakah sedang pada Practice Mode atau tidak. Berikut adalah contohnya.

    ...
    bool _nilaiSwitch = false;

    @override
    ...
    SwitchListTile(
    title: const Text('Practice Mode'),
    value: _nilaiSwitch,
    onChanged: (bool value) {
    setState(() {
    _nilaiSwitch = value;
    });
    },
    secondary: const Icon(Icons.run_circle_outlined),
    ),
    ...
  12. Buatlah tombol yang akan menyimpan data yang ada di setiap elemen input. Kali ini kita tidak akan menyimpan data ke dalam database, namun kita akan memunculkannya pada popup yang akan muncul setelah tombol ditekan.

                ...
    TextButton(
    child: const Text(
    "Simpan",
    style: TextStyle(color: Colors.white),
    ),
    style: ButtonStyle(
    backgroundColor: MaterialStateProperty.all(Colors.blue),
    ),
    onPressed: () {
    if (_formKey.currentState!.validate()) {}
    },
    ),
    ...

Setelah semua input dan logika form dibuat, maka kita akan membuat popup yang akan memunculkan data yang ada pada input form saat tombol Simpan ditekan.

Tutorial: Memunculkan Data

  1. Tambahkan fungsi showDialog() pada bagian onPressed() dan munculkan widget Dialog pada fungsi tersebut. Berikut adalah contoh potongan kodenya.

              ...
    onPressed: () {
    if (_formKey.currentState!.validate()) {
    showDialog(
    context: context,
    builder: (context) {
    return Dialog(
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(10),
    ),
    elevation: 15,
    child: Container(
    child: ListView(
    padding: const EdgeInsets.only(top: 20, bottom: 20),
    shrinkWrap: true,
    children: <Widget>[
    Center(child: const Text('Informasi Data')),
    SizedBox(height: 20),
    // TODO: Munculkan informasi yang didapat dari form
    TextButton(
    onPressed: () {
    Navigator.pop(context);
    },
    child: Text('Kembali'),
    ),
    ],
    ),
    ),
    );
    },
    );
    }
    },
    ...
  2. Tambahkan informasi yang didapat dari form. Kamu dapat menggunakan widget Text dan melakukan string concatenation agar keterangan data dan isi data dapat disajikan dalam satu widget. Contohnya adalah Text('Judul: ' + _namaLengkap).

  3. Coba jalankan program kamu, gunakan form yang telah dibuat, dan lihat hasilnya.

Akhir Kata

Selamat, kamu telah mempelajari navigasi dasar dan pembuatan form dasar pada Flutter!

Setelah kamu menyelesaikan seluruh tutorial di atas, kamu dapat mencoba widget input lainnya yang ada di Flutter. Kamu juga dapat mencoba untuk membuat halaman baru dengan opsi navigasi yang berbeda, seperti Navigator.push() dan Navigator.pop().

Jika kamu ingin mencoba tantangan, maka cobalah untuk menerapkan hal berikut pada tutorial ini.

  • Modifikasi navigasi pada Drawer agar melakukan Navigator.pop() apabila halaman yang ingin dibuka adalah halaman yang sedang dibuka, alih-alih mengunakan Navigator.pushReplacement() untuk semua navigasi.
  • Kustomisasi widget-widget yang telah kamu buat sebelumnya (styling), seperti warna, ikon, dll.

Referensi Tambahan

Kontributor

  • Muhammad Athallah
  • Brandon Ivander
  • Sabyna Maharani
  • Muhammad Azis Husein