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
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.
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.
Comments