Mengenal Widget Flutter #5: Membuat Form Login

Mengenal Widget Flutter #5: Membuat Form Login

Pendahuluan

Setelah 4 serial berlalu dalam seri mengenal widget Flutter, maka pada kesempatan kali ini kita akan sedikit me-review apa yang telah dibahas pada materi sebelumnya dengan menyelesaikan sebuah yakni membuat form login dengan Flutter. Dalam penyelesaian case ini, seluruh widget yang sudah kita kenali, akan digunakan untuk saling menunjang ketika merangkai UI (red: user interface) login Flutter.

Skenarionya adalah membuat dua buah screen dimana screen pertama adalah welcome screen dengan tombol get started yang ketika diklik akan diarahkan ke screen login di Flutter. Adapun tampilan yang akan diperoleh kurang lebih seperti gambar berikut

membuat form login di flutter

Baca Juga: Mengenal Widget Flutter #4: Image, Flexible & Spacer

Membuat Welcome Screen Dengan Widget Column

Gambaran singkat mengenai tampilan yang dibuat pada sub-bab pertama adalah sebuah halaman yang terdiri dari 4 bagian, pertama adalah gambar, kemudian teks sambutan, lalu tombol get started dan terakhir adalah logo brand dalam hal ini adalah Daengweb.id.

ui login flutter

Sebelum memulai, Install Flutter terlebih dahulu dengan command

flutter create dw_form_login_flutter

Buat file get_started.dart di dalam folder lib dan tambahkan code berikut sebagai kerangka dasarnya.

import 'package:flutter/material.dart';

class GetStarted extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Color(0xFFF2F2F2),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
          	//KOMENTAR-1: GAMBAR
          
            //KOMENTAR-2: TEKS
          
            //KOMENTAR-3: TOMBOL
          
            //KOMENTAR-4: LOGO BRAND
          ],
        ),
      ),
    );
  }
}

Penjelasan: Sebuah Container digunakan sebagai background dimana warnanya menyesuaikan dengan background dari gambar yang akan digunakan (red: untuk memanipulasi saja karena gambar yang digunakan memiliki background, daripada harus membuang background dari image-nya jadi seluruh background app-nya saja yang di-set, jalan pintas, hehehe). Kemudian di dalam Container terdapat Column untuk menampung seluruh widget 4 component sebagaimana dijelaskan diawal sub-bab.

Setiap bagian dari ke-4 component sudah ditandai dengan komentar, replace masing-masing komentar tersebut ketika saya menyebutkan kode komentarnya untuk memudahkan proses penulisan.

Pada bagian KOMENTAR-1, masukkan code berikut yang akan memuat gambar buah-buahan

//KOMENTAR-1: GAMBAR
Container(
  margin: EdgeInsets.only(left: 20, right: 20),
  height: MediaQuery.of(context).size.height / 2.4,
  decoration: BoxDecoration(
    color: Colors.white10,
    image: DecorationImage(
      image: AssetImage('images/fruit.png'),
      fit: BoxFit.fill,
    ),
  ),
),

Penjelasan: Hal baru yang kita pelajari dari code di atas adalah MediaQuery, dimana height dari Container-nya kita set menggunakan MediaQuery dari Flutter tersebut. Maksud code MediaQuery.of(context).size.height adalah mengambil tinggi dari device yang digunakan, jadi apapun device-nya akan menyesuaikan tingginya. MediaQuery berguna untuk menentukan ukuran yang responsive sesuai dengan device pengguna. Lalu hasi dari height tersebut dibagi 2.4 karena kita akan mengatur ukuran Container hanya sebesar 2.4 dari tinggi device.

Penjelasan lain terkait code di atas sudah pernah dibahas pada materi sebelumnya. Adapun image fruit.png bisa di-download disini. Gambar yang sudah di-download, kemudian simpan di dalam folder images (red: jika folder tersebut belum ada, silahkan buat foldernya). Lompat ke sub-bab paling bawah sebelum kesimpulan untuk cara me-register gambar tersebut.

Kemudian lanjut, replace KOMENTAR-2 dengan code berikut

//KOMENTAR-2
Text(
  "WE WILL FIND THE BEST",
  style: TextStyle(
      fontWeight: FontWeight.bold,
      color: Color(0xFF53714B),
      fontSize: 16),
),
Padding(
  padding: const EdgeInsets.only(top: 10.0),
  child: Text(
    "find the nearest places with the best organic foods and make your life healthier!",
    textAlign: TextAlign.center,
    style: TextStyle(
      color: Color(0xFF53714B),
    ),
  ),
),

Note: Tidak ada yang perlu dijelaskan karena hanya berisi teks biasa dengan style dan padding yang diatur.

Materi baru selanjutnya adalah membuat button di Flutter, tapi kali ini sedikit berbeda karena pembahasannya adalah membuat flat button di Flutter atau mungkin bisa disebut dengan membuat gradasi warna pada button di Flutter, terserah apa saja sebutannya, yang terpenting adalah mari kita praktekkan dengan menambahkan code berikut tepat di KOMENTAR-3.

//KOMENTAR-3
Padding(
  //SET PADDING UNTUK MENENTUKAN BERAPA JARAK DARI OBJEK SEKITAR
  padding: const EdgeInsets.only(left: 10, right: 10, top: 30),
  
  child: RaisedButton(
    onPressed: () {
    	//FUNGSI DARI CODE DIBAWAH ADALAH UNTUK BERPINDAH KE HALAMAN DENGAN ROUTE /LOGIN
      //pushReplacementNamed BERARTI KETIKA BERPINDAH SCREEN, MAKA SCREEN SEBELUMNYA AKAN DIHAPUS SEHINGGA USER TIDAK MEMILIKI KEMAMPUAN UNTUK KEMBALI KE HALAMAN SEBELUMNYA
    
      //Navigator.pushReplacementNamed(context, '/login');
    
      //JANGAN LUPA UNCOMMENT DI ATAS APABILA HALAMAN LOGIN SUDAH DIBUAT
    },
      
    //MODIFIKASI BUTTONNYA MENJADI ROUNDED
    shape: RoundedRectangleBorder(
      //KEMUDIAN BORDERNYA DIBUAT MELENGKUNG DENGAN BORDER RADIUS
      borderRadius: BorderRadius.circular(80.0),
    ),
    padding: const EdgeInsets.all(0.0),
    //
    child: Container(
      //MEMBUAT DEKORASI GRADIENT DENGAN CONTAINER
      decoration: const BoxDecoration(
        //GRADIENT INI BISA MENAMPUNG BANYAK WARNA UNTUK DIKOMBINASIKAN
        gradient: LinearGradient(
          //SILAHKAN MASUKKAN CODE WARNANYA, JADI FORMATNYA JIKA INGIN MENGGUNAKAN HEXA COLOR ADALAH 0XFF DITAMBAHAN CODE HEXA, CONTOH HITAM: 0XFF000000 DIMANA 6 ANGKA 0 DIBELAKANG ADALAH HEXA COLOR UNTUK HITAM
          colors: <Color>[Color(0xFF13E3D2), Color(0xFF5D74E2)],
        ),
        //DARI CONTAINER INI KITA SET BORDER RADIUSNYA DIMANA SETIAP SUDUTNYA MELENGKUNG SEBESAR 80
        borderRadius: BorderRadius.all(
          Radius.circular(80.0),
        ),
      ),
      //KEMUDIAN TENTUKAN UKURANNYA AGAR MEMENUHI BUTTON
      constraints: const BoxConstraints(
        minWidth: 150.0, //SEBENARNYA WIDTH TIDAK PERLU DISET KARENA OTOMATIS MENGIKUTI 
        minHeight: 36.0, //INI ADALAH UKURAN DEFAULT DARI RAISED BUTTON
      ),
      alignment: Alignment.center,
      //SELANJUTNYA MASUKKAN TEKS UNTUK TOMBOL TERSEBUT
      child: const Text(
        'Get Started',
        textAlign: TextAlign.center,
        style: TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.bold,
        ),
      ),
    ),
  ),
),

Perfect, sebuah tombol dengan gradient berhasil dibuat yang memiliki perpaduan dua buah warna. Langkah terakhir adalah menampilkan logo brand, replace KOMENTAR-4 dengan code berikut

//KOMENTAR-4
Spacer(), //DIGUNAKAN UNTUK MEMBUAT JARAK DENGAN WIDGET LAIN
Container(
  width: double.infinity, //LEBAR CONTAINERNYA DIBUAT SELEBAR MUNGKIN
  decoration: BoxDecoration(
    //KEMUDIAN DIBUAT MELENGKUNG PADA SUDUT ATAS KANAN-KIRI
    borderRadius: BorderRadius.only(
      topLeft: Radius.circular(30),
      topRight: Radius.circular(30),
    ),
    color: Colors.white,
  ),
  child: Image.network(
    'https://daengweb.id/front/dw-theme/images/logo-head.jpg',
  ),
)

Sebentar, untuk melihat hasil dari design di atas bagaimana? Eh iya hampir lupa, buka file lib/main.dart dan modifikasi menjadi:

import 'package:flutter/material.dart';
import './get_started.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: GetStarted(),
    );
  }
}

Note: Codingan di atas bersifat sementara karena pada akhirnya kita akan mengubahnya dengan menggunakan routes.

Membuat Form Login Cantik di Flutter

Cantik memang selalu menarik dan mengundang mata untuk nyaman memandangnya. Sub-bab kali ini kita akan membuat tampilan yang menarik dari sebuah form login menggunakan Flutter seperti gambar yang disematkan pada pendahuluan.

Bagian ini juga memiliki 4 components, pertama adalah sebuah gambar yang menjadi header, kemudian diikuti dengan form input-an untuk email dan password, bagian ketiga adalah tombol login dan yang terakhir adalah social button.

Buat file login.dart di dalam folder lib dan masukkan kerangka code berikut

import 'package:flutter/material.dart';

class Login extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //KITA DETECT MENGGUNAKAN MEDIAQUERY, JIKA SCREEN LANDSCAPE
      //MAKAN AKAN DITAMBAHKAN WIDGET SCROLLING AGAR BISA DISCROLL
      //SELAIN ITU TANPA WIDGET SCROLLING.
      //ContentArea() ADALAH NAMA SEBUAH CLASS BARU
      body: MediaQuery.of(context).orientation == Orientation.landscape
          ? SingleChildScrollView(child: ContentArea())
          : ContentArea(),
    );
  }
}

class ContentArea extends StatelessWidget {
  const ContentArea({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        //KOMENTAR-1
        FormLogin(),
      
        //KOMENTAR-2
      ],
    );
  }
}

//CLASS BARU DISINI

Note: Apabila sebuah code itu sudah terlalu panjang, maka kita bisa memisahkannya ke dalam sebuah class baru sesuai fungsinya masing-masing agar kita lebih mudah dalam me-maintenance-nya

Disebabkan karena KOMENTAR-1 adalah sebuah class baru maka langsung saya tuliskan pada code sebelumnya, jadi kita tinggal membuat class baru tersebut, yakni FormLogin(). Tambahkan code berikut setelah class ContentArea().

class FormLogin extends StatelessWidget {
  const FormLogin({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //JADI KITA MENGGUNAKAN FLEXIBLE UNTUK MEMISAHKANNYA MENJADI DUA BAGIAN,
    //PERTAMA ADALAH HEADER DAN FORM, KEDUA ADALAH TOMBOL-TOMBOL NANTINYA
    //KITA KERJAKAN BAGIAN PERTAMA DENGAN FLEX 2 BERARTI PORSINYA LEBIH BESAR
    return Flexible(
      flex: 2,
      //KEMUDIAN MENGGUNAKAN STACK AGAR MUDAH MENGATUR LETAKNNYA SESUAI KEINGINAN
      //KARENA KITA INGIN GAMBAR HEADER DAN FORM LOGIN SALING MEMBELAH
      child: Stack(
        children: <Widget>[
          //SET CONTAINER SEBAGAI BACKGROUND
          Container(
            height: MediaQuery.of(context).size.height,
          ),
          //CONTAINER KEDUA BERISI IMAGE SEPERTI CASE SEBELUMNYA PENJELASANNYA
          Container(
            height: MediaQuery.of(context).size.height / 2.4,
            decoration: BoxDecoration(
              color: Colors.white10,
              image: DecorationImage(
                image: AssetImage('images/header.jpg'),
                fit: BoxFit.cover,
              ),
            ),
          ),
          //BAGIAN INI KITA GUNAKAN POSITIONED UNTUK MENGATUR SUDUTNYA, JIKA MASIH BINGUNG SOAL MATERI INI BACA NOTE DIBAWAH.
          Positioned(
            //JARAK DARI ATAS KITA AMBIL DARI HEIGHT DIBAGI 3.6
            top: MediaQuery.of(context).size.height / 3.6,
            //ISINYA ADALAH CONTAINER YANG WIDTHNYA SELEBAR MUNGKIN
            child: Container(
              width: MediaQuery.of(context).size.width,
              child: Card(
                //LALU CARD KITA SET MARGINNYA 20 DARI CONTAINER
                margin: const EdgeInsets.all(20.0),
                elevation: 8, //KETEBALANNYA
                child: Padding(
                  //KEMUDIAN COLUMN KITA SET LAGI PADDINGNYA DARI CARD
                  padding: const EdgeInsets.all(15.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      //BAGIAN PERTAMA ADALAH TEKS FORM LOGIN
                      Padding(
                        padding: const EdgeInsets.only(top: 10),
                        child: Text(
                          "Login Form",
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 25,
                          ),
                        ),
                      ),
                      //KEMUDIAN SEBUAH TEKS FIELD UNTUK MENGINPUT EMAIL
                      TextField(
                        //DIMANA DEKORASINYA PADA SIFFUX BERARTI AKHIR BERISI ICON EMAIL BERWARNA PINK
                        decoration: InputDecoration(
                          suffixIcon: Icon(
                            Icons.email,
                            color: Colors.pink[200],
                          ),
                          //KETIKA INPUTAN TERSEBUT DIKLIK MAKA AKAN MEMBUAT UNDERLINE
                          focusedBorder: UnderlineInputBorder(
                            //DENGAN BORDER BERWARNA PINK
                            borderSide: BorderSide(
                              color: Colors.pinkAccent,
                            ),
                          ),
                          labelText: "Email: ", //SET LABELNYA
                          //DAN SET STYLE DARI LABEL, CARA KERJANYA SAMA DENGNA TEXT STYLE KETIKA DISEMATKAN PADA TEXT() WIDGET
                          labelStyle: TextStyle(
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                      //TEXT FIELD UNTUK PASSWORD, ADAPUN PENJELASANNYA SAMA DENGAN CODE DIATAS
                      TextField(
                        decoration: InputDecoration(
                          suffixIcon: Icon(
                            Icons.security,
                            color: Colors.pink[200],
                          ),
                          focusedBorder: UnderlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.pinkAccent,
                            ),
                          ),
                          labelText: "Password: ",
                          labelStyle: TextStyle(
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                      //TERAKHIR ADALAH TEKS UNTUK FORGOT PASSWORD
                      Padding(
                        //PADDINGNYA KITA SET UNTUK JARAK DARI ATAS BAWAH DAN KANAN
                        padding: const EdgeInsets.only(
                            top: 20, bottom: 5, right: 15.0),
                        //KITA GUNAKAN ALIGN UNTUK MENGATUR POSISINYA
                        child: Align(
                          alignment: Alignment.bottomRight,
                          child: Text(
                            "Forgot Password?",
                            style: TextStyle(
                              color: Colors.blue[400],
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

Note: Kamu bisa membaca artikel Stack & Positioned apabila masih bingung mengenai kedua widget tersebut.

Selanjutnya adalah tambahkan code berikut pada bagian KOMENTAR-2 untuk membuat tombol login dan social login.

//KOMENTAR-2
Flexible(
  flex: 1, //KARENA KOMENTAR-1 INGIN KITA BERIKAN PORSI LEBIH BESAR, MAKA BAGIAN INI KITA SET 1
  child: Container(
    margin: const EdgeInsets.all(20),
    //KITA GUNAKAN COLUMN KARENA TERBAGI MENJADI 3 BAGIAN, PERTAMA ADALAH DERETA TOMBOL LOGIN & REMEMBER ME
    //KEDUA ADALAH TEKS SOCIAL LOGIN
    //DAN YANG KETIGA TOMBOL SOCIAL LOGIN
    child: Column(
      children: <Widget>[
        //DERETA PERTAMA TERDIRI DARI 2 BAGIAN, 1 REMEMBER ME DAN SELANJUTNYA ADALAH TOMBOL LOGIN MAKA KITA GUNAKAN ROW UNTUK MENGURUTKANNYA SECARA HORIZONTAL
        Row(
          children: <Widget>[
            //BUAT CHECKBOX
            Checkbox(
              onChanged: (_) {},
              value: false,
            ),
            //DIIKUTI DENGAN TEKSNYA
            Text(
              "Remember Me",
              style: TextStyle(),
            ),
            //ANTARA REMEMBER ME DAN TOMBOL LOGIN KITA BERI JARAK DENGAN SPACER
            Spacer(
              flex: 2,
            ),
            //KEMUDIAN BUAT RAISED BUTTON DENGAN GRADIENT, PENJELASANNYA ADA PADA SUB-BAB PERTAMA 
            RaisedButton(
              onPressed: () {},
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(80.0),
              ),
              padding: const EdgeInsets.all(0.0),
              child: Ink(
                decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    colors: <Color>[
                      Color(0xFF13E3D2),
                      Color(0xFF5D74E2)
                    ],
                  ),
                  borderRadius: BorderRadius.all(
                    Radius.circular(80.0),
                  ),
                ),
                child: Container(
                  constraints: const BoxConstraints(
                    minWidth: 150.0,
                    minHeight: 36.0,
                  ),
                  alignment: Alignment.center,
                  child: const Text(
                    'Login',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
        //MENAMPILKAN TEKS SOCIAL BUTTON DIBAWAH DERETAN TOMBOL LOGIN
        Padding(
          padding: EdgeInsets.only(top: 15),
          child: Text(
            "Social Login",
            style: const TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.grey,
            ),
            textAlign: TextAlign.center,
          ),
        ),
        //MEMBUAT GARIS
        Divider(),
        //DAN YANG TERAKHIR ADALAH TOMBOL SOCIAL BUTTON YANG DIPISAHKAN DENGAN CLASS BARU LAGI
        SocialButton()
      ],
    ),
  ),
)

//CLASS BARU DISINI

Melengkapi permintaan sebuah class baru, maka tambahkan class berikut setelah class FormLogin()

class SocialButton extends StatelessWidget {
  const SocialButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //TERDAPAT 4 TOMBOL SOCIAL LOGIN YANG BERDERET SECARA HORIZONTAL
    //MAKA KITA GUNAKAN ROW
    return Row(
      mainAxisAlignment: MainAxisAlignment.center, //SET POSISINYA DITENGAH
      children: <Widget>[
        //MASING-MASING TOMBOL MENGGUNAKAN FLATACTIONBUTTON
        //DENGAN CHILD IMAGE DIDALAMNYA
        //KEMUDIAN MARGIN KANANNYA DISET SEBESAR 10 UNTUK MEMBERI JARAK ANTAR TOMBOL
        Container(
          margin: EdgeInsets.only(right: 10),
          child: FloatingActionButton(
            heroTag: null,
            onPressed: () {},
            child: Image.asset(
              'images/facebook.png',
            ),
            backgroundColor: Color(0xFF5D74E2),
          ),
        ),
        Container(
          margin: EdgeInsets.only(right: 10),
          child: FloatingActionButton(
            heroTag: null,
            onPressed: () {},
            child: Image.asset(
              'images/twitter.png',
            ),
            backgroundColor: Colors.white,
          ),
        ),
        Container(
          margin: EdgeInsets.only(right: 10),
          child: FloatingActionButton(
            heroTag: null,
            onPressed: () {},
            child: Image.asset(
              'images/google.png',
            ),
            backgroundColor: Colors.white,
          ),
        ),
        FloatingActionButton(
          heroTag: null,
          onPressed: () {},
          child: Image.asset(
            'images/linkedin.png',
          ),
          backgroundColor: Color(0xFF5D74E2),
        ),
      ],
    );
  }
}

Membuat Navigasi Antar Screen di Flutter

Seperti yang saya sampaikan sebelumnya bahwa file main.dart akan dimodifkasi lagi dengan menggunakan routes untuk mendefinisikan name yang akan digunakan untuk berpindah screen menggunakan Navigator. Buka file tersebut dan modifikasi menjadi.

import 'package:flutter/material.dart';

//IMPORT KEDUA FILE BARU
import './get_started.dart';
import './login.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      //DEFINISIKAN ROUTENYA
      routes: {
        // ROUTE / BERARTI SECARA OTOMATIS AKAN DI-LOAD KETIKA FLUTTER DIJALANKAN
        //MAKA KITA MASUKKAN GetStarted()
        '/': (context) => GetStarted(),
        //ROUTE DENGAN NAME /LOGIN ADALAH HALAMAN SELANJUTNYA
        //JADI JIKA KITA INGIN MEMANGGIL HALAMAN INI, MAKA GUNAKAN NAME /LOGIN
        '/login': (context) => Login()
      },
    );
  }
}

Masih ingat code Navigator.pushReplacementNamed(context, '/login'); yang di-comment? Uncomment kode tersebut karena routing-nya sudah tersedia.

Register Local Image di Flutter

Ada 5 buah gambar yang di-load pada halaman login di atas, download semua gambar tersebut dan simpan di dalam folder images.

Berbeda halnya dengan gambar yang di-load dari internet karena kita hanya perlu memasukkan url-nya saja, maka untuk local file ada sedikit pendekatan untuk me-register gambar tersebut. Buka file pubspec.yaml, kemudian cari komentar ini: To add assets to your application, add an assets section, like this: dan tambahkan code berikut tepat dibawahnya

assets:
   - images/header.jpg
   - images/twitter.png
   - images/google.png
   - images/linkedin.png
   - images/facebook.png
   - images/fruit.png

Note: Perhatikan order line-nya, jangan dibuat sejajar dengan assets karena tidak akan dikenali.

Baca Juga: Mengenal Widget Flutter #3: List View & Text Style

Kesimpulan

Artikel yang cukup panjang untuk sekedar me-review widget yang telah kita pelajari pada seri-seri sebelumnya, dimana case-nya adalah membuat form login menggunakan Flutter. Jika ada pertanyaan, fungsi untuk login-nya mana? Tenang, jangan terburu-buru karena kita belum membahas tentang HTTP Request secara spefisik, juga belum belajar tentang state management secara spesifik.

Materi ini hanya membahas bagiamana membuat UI (user interface) form login flutter dan pastinya pada review selanjutnya akan dibuat lebih kompleks dengan melibatkan seluruh widget yang telah dipelajari.

Category:
Share:

Comments