Daftar Referensi
[1] wearesocial. “Digital 2020 - We Are Social” wearesocial.com
https://wearesocial.com/digital-2020 (diakses pada 23 April 2021)
[2] Flutter. “Flutter: the first UI platform designed for ambient computing”
developers.googleblog.com
https://developers.googleblog.com/2019/12/flutter-ui-ambient-computing.ht
ml (diakses pada 23 April 2021)
Framework Flutter
Teknologi sudah menjadi bagian tak terpisahkan dalam hidup kita terutama
smartphone. Menurut data dari We Are Social, pada bulan Januari 2020
pengguna internet di Indonesia mencapai sekitar 175,4 juta orang.
Terdapat peningkatan sebesar 17% atau sekitar 25 juta pengguna dari
tahun 2019. Jika dilihat dari total populasi penduduk Indonesia yang
berkisar 272,1 juta jiwa, maka 64% di antaranya sudah mengakses
internet. Dengan masifnya angka pengguna smartphone ini, kian spesifik
pula requirements alias permintaan bagi seorang pengembang aplikasi.
Termasuk requirement pada perangkat mobile dengan target platform
Android dan iOS. Masing-masing platform memiliki aplikasi native-nya
tersendiri.
Oke, sampai sini kita telah mempelajari tentang apa itu Flutter, termasuk
sejarah dan kenapa kita perlu belajar mengembangkan aplikasi dengan
Flutter. Di kelas ini Anda akan belajar dasar pengembangan aplikasi
Flutter, seperti package, widget yang umum digunakan, cara berpindah
halaman menggunakan Navigation, hingga proses build menjadi APK yang
bisa diinstal pada smartphone Anda
Untuk lulus dari kelas ini cukup dengan mengirimkan submission dari satu
platform saja, misal Anda memilih platform Android maka tidak perlu
mengirimkan versi iOS atau webnya.
Instalasi Flutter
Sebelum mulai mengembangkan aplikasi menggunakan Flutter, tentunya kita perlu
menyiapkan dan menginstal tools apa saja yang dibutuhkan untuk membuat aplikasi Flutter.
Untuk melakukan instalasi Flutter, ada perbedaan cara di setiap sistem operasi. Simak
caranya berikut ini sesuai dengan sistem operasi yang Anda gunakan:
Persyaratan Minimum
Sebelum melakukan instalasi dan menjalankan Flutter, perangkat milik Anda harus
memenuhi persyaratan minimum seperti di bawah ini:
● Windows
● Linux
Jika Git untuk Windows sudah diinstal, pastikan Anda dapat menjalankan perintah git dari
Command Prompt (CMD) atau PowerShell.
● Windows
1. Unduh paket instalasi untuk mendapatkan versi stabil terbaru dari
Flutter SDK di alamat web
https://flutter.dev/docs/development/tools/sdk/releases. Ambil versi
terbaru pada stable channel sesuai sistem operasi yang digunakan.
2. Ekstrak berkas zip dan tempatkan folder flutter pada lokasi instalasi
yang diinginkan untuk Flutter SDK. Misalnya C:\Development, jangan
pasang Flutter di direktori seperti C:\Program Files atau yang
membutuhkan hak istimewa seperti administrator.
3. Temukan berkas flutter_console.bat di dalam direktori flutter tersebut.
Mulai dengan klik dua kali atau jalankan script tersebut dan Anda
sekarang siap untuk menjalankan perintah Flutter di Flutter Console.
● MacOS
● Linux
Update Path
Langkah selanjutnya kita akan melakukan update path supaya perintah
Flutter bisa digunakan pada command prompt/terminal. Berikut
langkah-langkahnya:
● Windows
1. Dari bar pencarian di Start menu, ketik ‘env’ dan pilih Edit
Environment Variable untuk akun Anda.
2. Klik pada tombol Environment Variables.
3. Di bawah User variabel periksa apakah ada entri yang disebut PATH,
jika ada maka pilih lalu edit, jika tidak ada maka buat baru dengan
nama variabel Path.
● MacOS
● Linux
Anda dapat memperbarui variabel PATH, hanya untuk sesi command-line
saat ini (sementara). Anda mungkin ingin memperbarui variabel secara
permanen, sehingga dapat menjalankan perintah flutter di sesi terminal apa
pun.
Flutter Doctor
Flutter doctor adalah perintah untuk mengecek kelengkapan framework
flutter yang Anda gunakan, seperti versi flutter yang digunakan, Android
SDK yang digunakan, iOS SDK yang digunakan (hanya pada MacOS),
perangkat yang sudah terhubung, dan semacamnya. Periksa kembali dan
pastikan dependensi untuk pengaturan sudah lengkap. Jalankan perintah
berikut untuk membuka flutter doctor:
flutter doctor
Perintah ini memeriksa environment Anda dan menampilkan laporan ke
jendela terminal. Pada Flutter SDK sudah terdapat Dart SDK, jadi Anda
tidak perlu menginstal Dart secara terpisah. Periksa output dengan cermat
untuk perangkat lunak lain yang mungkin perlu Anda instal atau melakukan
sesuatu lebih lanjut (ditunjukkan dalam teks tebal).
Contoh :
Instalasi IDE
Anda dapat membuat aplikasi dengan Flutter menggunakan editor teks
yang dikombinasikan dengan command-line tools. Namun, sebaiknya
gunakan salah satu plugin editor yang direkomendasikan untuk
pengalaman yang lebih baik. Plugin ini memiliki fitur seperti penyelesaian
kode, penyorotan sintaks, bantuan pengeditan widget, dukungan untuk
menjalankan & debug, dan masih banyak lagi.
● Android Studio/IntelliJ
Android Studio dan IntelliJ menawarkan pengalaman IDE yang terintegrasi
lengkap dengan plugin khusus untuk Flutter. Untuk mengunduh Android
Studio, Anda dapat mengunjungi tautan
https://developer.android.com/studio. Sedangkan jika Anda memilih IntelliJ
maka dapat melalui tautan https://www.jetbrains.com/idea/download/.
5. Klik Restart saat diminta. Jika tidak muncul, silakan muat ulang
secara manual aplikasi Android Studio/IntelliJ.
4. Atau langsung klik pada tab Extensions yang ada pada bagian kiri.
Project Wizard
Setelah berhasil menginstal Flutter SDK dan IDE artinya peralatan yang
kita butuhkan telah siap. Bagian ini akan menjelaskan langkah-langkah
untuk membuat aplikasi Flutter baru, mulai dari template, cara aplikasi, dan
menggunakan "Hot Reload" setelah Anda melakukan perubahan kode
aplikasi.
Pilih alat pengembangan (IDE) favorit Anda untuk menulis baris perintah,
membangun, dan menjalankan aplikasi Flutter.
● Android Studio/IntelliJ
1. Buka Android Studio/IntelliJ.
2. Pilih New Flutter Project.
3. Pilih Flutter SDK path yang tersedia. Jika path tidak tersedia, klik
tombol titik tiga untuk cari direktori Flutter SDK. Kemudian klik tombol
Next.
Tips: Kode Dart untuk aplikasi Anda ada di lib/main.dart. Untuk deskripsi
lebih lanjut tentang apa yang dilakukan setiap blok kode, lihat komentar
yang ada di dalam file tersebut.
Menjalankan di Android
Kita akan menggunakan project Flutter yang telah dibuat sebelumnya
untuk dijalankan di emulator atau device Android. Ada beberapa hal yang
perlu diperhatikan untuk dapat menjalankan project-nya, uraiannya adalah
sebagai berikut:
Android Emulator
Sebelum menggunakan emulator, Anda perlu memastikan beberapa
syarat.
1. Virtualization
Untuk menjalankan emulator di dalam Android Studio, pastikan
aspek virtualization. Sistem Anda harus memenuhi persyaratan,
yakni ketentuan prosesor dan sistem operasi dari laptop/PC yang
Anda gunakan.
2. Processor
a. Intel: Jika Anda menggunakan processor Intel, maka pastikan
bahwa processor tersebut mendukung Intel VT-X, Intel EM64T
(Intel 64), dan Execute Disable (XD) Bit functionality.
Processor Intel mampu menjalankan emulator di sistem
operasi Windows, Linux, mau pun MacOS.
b. AMD: Jika Anda menggunakan AMD, maka pastikan bahwa ia
mendukung AMD Virtualization (AMD-V) dan Supplemental
Streaming SIMD Extensions 3 atau yang biasa disebut dengan
SSSE3.
Processor AMD hanya bisa menjalankan emulator di sistem
operasi Linux.
Persiapan Running menggunakan Emulator
1. Untuk menjalankan aplikasi, klik menu Tools, lalu pilih Device
Manager.
● Lebih cepat,
● Lebih ringan, dan
● Lebih mudah.
1. Pastikan peranti yang akan dipakai sesuai dengan target SDK atau
paling tidak mendukung versi SDK terendah yang digunakan aplikasi.
2. Buka setting dan masuk ke dalam menu About. Pada halaman
menu ini, Anda perlu menemukan informasi tentang Build number.
3. Kemudian tekan Build number sebanyak 7 kali.
Menjalankan di iOS
Kita akan menggunakan project Flutter yang telah dibuat sebelumnya
untuk dijalankan di iOS simulator. Ada beberapa hal yang perlu
diperhatikan untuk dapat menjalankan project-nya, uraiannya adalah
sebagai berikut:
iOS Simulator
iOS Simulator atau Simulator adalah aplikasi yang digunakan untuk
menjalankan aplikasi iOS. Simulator merupakan aplikasi bawaan dari
XCode.
● Android Studio/IntelliJ
1. Temukan toolbar Android Studio seperti di bawah ini:
Menjalankan di Web
Selain platform mobile, Flutter juga bisa berjalan pada web browser. Dalam
proses pengembangan, untuk keperluan debugging kita perlu
menggunakan Google Chrome sebagai browser. Sejak Flutter versi 2.0
Flutter Web sudah memasuki versi stable sehingga bisa digunakan secara
langsung. Jika menggunakan versi di bawahnya, Anda perlu mengaktifkan
channel beta terlebih dahulu. Silakan ikuti langkahnya pada blog berikut.
Untuk menambahkan dukungan web pada project Anda, jalankan perintah
berikut melalui terminal dari lokasi project:
Android Studio:
Ketika target device Chrome sudah terpilih seperti di atas, Anda dapat
menjalankan aplikasi dengan cara yang sama seperti ketika
menjalankannya pada platform mobile. Jika Anda ingin menjalankan
melalui terminal, maka perintahnya adalah seperti ini:
● HTML renderer
Renderer ini menggunakan kombinasi elemen HTML, CSS, Canvas,
dan SVG. Jenis renderer ini memiliki ukuran unduhan yang lebih
kecil.
● CanvasKit renderer
Renderer ini bekerja dengan cara yang sama dengan platform mobile
atau desktop. CanvasKit renderer memiliki performa yang lebih
tinggi, tetapi akan menambahkan ukuran hingga sekitar 2 MB.
Lebih lanjut, berikut ini cara melakukan hot reload pada aplikasi Flutter:
Setelah operasi hot reload berhasil, Anda akan melihat pesan di konsol
seperti ini:
● Android Studio/IntelliJ
Flutter menawarkan siklus pengembangan yang cepat dengan Hot Reload,
kemampuan memuat ulang kode aplikasi yang sedang berjalan tanpa
memulai ulang atau kehilangan status aplikasi. Untuk perubahan ke
sumber aplikasi, beritahu IDE atau alat baris perintah Anda bahwa Anda
ingin melakukan Hot Reload, kemudian lihat perubahan di simulator,
emulator, atau perangkat Anda.
1. Buka lib/main.dart.
2. Ubah string 'You have pushed the button this many times' menjadi
'You have clicked the button this many times'.
Penting: Jangan hentikan aplikasi Anda. Biarkan aplikasi berjalan.
3. Simpan perubahan baris perintah dengan cara : Save All, atau klik
Hot Reload.
Anda akan langsung melihat string yang sudah diperbarui di aplikasi yang
sedang berjalan.
Note: Jika fitur reload belum aktif, jalankan perintah flutter doctor dan
pastikan android licenses sudah terpasang. Perintah android licenses
sebagai berikut: flutter doctor --android-licenses
1. Buka lib/main.dart.
2. Ubah string 'You have pushed the button this many times' menjadi
'You have clicked the button this many times'.
Penting: Jangan hentikan aplikasi Anda. Biarkan aplikasi Anda
berjalan.
3. Simpan perubahan baris perintah dengan cara: Pilih File > Save All,
atau klik Hot Reload (tombol petir kuning).
Anda akan langsung melihat string yang sudah diperbarui di aplikasi yang
sedang berjalan.
1. Buka lib/main.dart.
2. Ubah string 'You have pushed the button this many times' menjadi
'You have clicked the button this many times'.
Penting: Jangan hentikan aplikasi Anda. Biarkan aplikasi Anda
berjalan.
3. Simpan perubahan baris perintah.
4. Ketik perintah r di jendela terminal, untuk melakukan Hot Reload.
Anda akan langsung melihat string yang sudah diperbarui di aplikasi yang
sedang berjalan.
Kasus spesial
Bagian berikutnya menjelaskan situasi umum di mana kode yang
dimodifikasi tidak akan berjalan lagi setelah hot reload. Dalam beberapa
kasus, perubahan kecil pada kode Dart akan memungkinkan Anda untuk
terus menggunakan hot reload untuk aplikasi Anda. Dalam beberapa
kasus, kita perlu melakukan hot restart atau full restart:
● android/
Folder ini merupakan tempat Anda untuk mengatur konfigurasi untuk
aplikasi android. Di dalamnya terdapat file gradle, AndroidManifest,
dan lain-lain. File-file tersebut sangat umum ketika Anda membuat
aplikasi android native (menggunakan bahasa pemrograman Java
atau Kotlin), nanti Anda akan melakukan beberapa setting pada
folder android seiring waktu.
● ios/
Sama halnya dengan folder android, hanya saja ini untuk iOS. Folder
ini merupakan tempat konfigurasi untuk aplikasi iOS. Ketika kita
hendak membuat project flutter yang dapat berjalan pada iPhone,
Anda akan berkutat dengan folder ini.
● build/
Ketika Anda melakukan build project flutter, hasil build akan ada
pada folder ini. Sebagai contoh, ketika Anda ingin membuat file APK
untuk project flutter, maka hasil file tersebut ada dalam folder ini.
Folder ini hanya akan ada ketika sudah pernah mem-build project,
dan akan terhapus jika menjalankan flutter clean.
● lib/
Ini merupakan folder utama ketika Anda mengerjakan project flutter.
Seluruh source code flutter Anda akan berada pada folder ini.
● test/
Folder ini tempat Anda menyimpan source code testing. Untuk
pemula tidak akan berkutat pada folder ini.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Kode di atas merupakan kode starter yang di-generate sebagai contoh
ketika Anda membuat project. Anda dapat mempelajari kode tersebut untuk
mengetahui bagaimana sebuah aplikasi Flutter disusun.
Untuk saat ini kita tidak akan menggunakan kode tersebut. Jadi, hapus
semua isi berkas main.dart tersebut dan tulis kode untuk aplikasi kita
sendiri, yaitu aplikasi sederhana untuk menampilkan teks Hello world.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Hello, world!'),
),
body: Center(
child: Text('Hello, world!'),
),
),
);
}
}
Mari kita bahas satu per satu kodenya!
import 'package:flutter/material.dart';
Import digunakan untuk memanggil fungsi-fungsi dari berkas Flutter yang
lain. Pada kode di atas, kita meng-import kode-kode yang terdapat dalam
library material bawaan Flutter. Library tersebut menyediakan widget yang
termasuk dalam material design. Pastikan kode ini ada pada bagian atas
sebelum kode lain dijalankan.
Flutter memiliki banyak sekali widget dan tentunya akan menjadi tugas
yang berat untuk menghafalkan semuanya. Namun tenang, Flutter
dilengkapi dengan dokumentasi yang cukup lengkap sebagai panduan kita
dalam mempelajari dan mengembangkan Flutter. Misalnya, penjelasan
lebih lengkap tentang widget yang kita gunakan di atas dapat dilihat pada
tautan berikut:
● https://api.flutter.dev/flutter/material/MaterialApp-class.html
● https://api.flutter.dev/flutter/material/Scaffold-class.html
● https://api.flutter.dev/flutter/widgets/Center-class.html
● https://api.flutter.dev/flutter/widgets/Text-class.html
Menggunakan Packages
Package Dependencies
Dalam pengembangan suatu aplikasi, kita tidak akan lepas dari package/library (selanjutnya
akan disebut package). Package di sini merupakan sebuah kode yang dibuat untuk
menyelesaikan suatu masalah. Contohnya ketika aplikasi yang kita buat membutuhkan fitur
kalender sementara fitur tersebut tidak di-support oleh Flutter. Alih-alih membuat fitur
kalender dari nol, kita dapat menggunakan package yang telah dibuat oleh developer lain.
Waktu pembuatan fitur menjadi lebih singkat!
Package dependencies merupakan sekumpulan package yang dibutuhkan dalam
pengembangan aplikasi. Package tersebut akan diatur oleh package manager. Setiap
bahasa pemrograman memiliki package manager-nya masing-masing, contohnya NodeJS
memiliki npm atau yarn, Java dengan maven atau gradle, PHP dengan composer. Begitu
pula dengan Flutter yang ditulis dengan bahasa dart memiliki package manager bernama
pub.
Kali ini kita akan membahas mengenai package manager pub, bagaimana menggunakan
pub, dan di mana mencari package yang dapat digunakan untuk aplikasi kita.
Dart Pub
Seperti yang telah kita singgung sebelumnya Pub merupakan sebuah package manager.
Pub memiliki tugas untuk mengatur package apa saja yang dibutuhkan dalam
pengembangan aplikasi. Pada package manager kita dapat mengatur versi package yang
ingin kita gunakan. Pengaturan versi sangat penting karena ketika versi flutter/dart yang
digunakan tidak cocok dengan package yang kita butuhkan akan berpengaruh pada
jalannya aplikasi yang kita buat. Oleh karena itu, kita harus memastikan versi yang
kompatibel dengan versi Flutter yang terinstal.
Lalu bagaimana kita menggunakan pub pada project Flutter kita? Untuk mengatur
package-package yang akan kita gunakan, cukup buka berkas pubspec.yaml yang ada
pada folder project.
Ketika membuka berkas pubspec.yaml kita akan melihat begitu banyak pengaturan tapi
tidak perlu khawatir karena yang kita bahas hanya mengenai package dependencies-nya
saja.
Coba kita fokus pada kode yang ada pada pubspec.yaml berikut:
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your
application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
Kode di atas merupakan package-package yang digunakan pada project Flutter kita. Jika
kita perhatikan, terdapat 2 jenis dependency yaitu dependencies dan dev_dependencies.
Fungsi dev_dependencies digunakan untuk package-package yang berkaitan ketika proses
pengembangan aplikasi Flutter, contohnya seperti flutter_test yang digunakan untuk testing.
Package di dalam dev_dependencies tidak akan disertakan ketika aplikasi dirilis pada play
store atau app store. Fungsi dependencies digunakan untuk package-package yang
langsung berkaitan dengan fitur aplikasi Flutter, contohnya seperti cupertino_icons yang
digunakan untuk mendapatkan ikon-ikon cupertino (icon untuk iOS) dan contoh lainnya
seperti cloud_firestore yang merupakan package untuk firebase firestore.
Sekarang kita akan fokus pada dependencies. Untuk mendaftarkan package yang
dibutuhkan kita cukup menulis seperti di bawah ini pada bagian dependencies:
nama_package: versi
nama_package merupakan nama package yang kita butuhkan, lalu disambung dengan
versinya. Penulisan versi bisa langsung seperti contoh 0.1.2, atau kita menambahkan simbol
caret (^) seperti ^0.1.2 . Simbol caret (^) artinya: gunakan versi patch terbaru dari versi yang
telah ditentukan. Jika versi nya ^0.1.2 artinya kita akan gunakan versi minimal 0.1.2 dan
maksimal versi terbaru. Karena itu, jika versi package tersebut sekarang sudah update,
maka package yang digunakan merupakan versi terbaru.
Catatan: Hanya pada versi patch atau pada angka terakhir yaitu angka 2 jika pada contoh
cupertino_icons: ^0.1.2. Atau kita juga bisa gunakan versi minimal dan maksimal seperti
contoh ‘>=0.1.2 <2.0.0’ yang artinya kita akan menggunakan versi terbaru yang ada pada
saat ini yang masih berada di dalam range tersebut yaitu lebih besar sama dengan 0.1.2
dan lebih kecil dari 2.0.0.
Okay sebagai contoh kita akan menambahkan sebuah package provider yang nantinya akan
kita gunakan.
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
provider: ^4.0.1
Yang perlu diperhatikan dalam menulis berkas .yaml adalah pada indentasinya. Indentasi
atau penggunaan spasi ini sangat penting karena menunjukkan urutan dan blok kode yaml
yang dibaca oleh komputer. Sebagai contoh, ketika kita menambahkan package provider,
maka kita harus menuliskannya sejajar dengan package lainnya dan juga lebih menjorok ke
dalam jika dibandingkan dengan dependencies. Ini menunjukkan bahwa package seperti
cupertino_icons dan provider merupakan bagian dari dependencies yang akan ditambahkan.
Setiap jaraknya adalah 2 spasi, jika dependencies menempel pada ujung kiri, maka
cupertino_icons dan provider berjarak 2 spasi dari ujung kiri.
Setelah menambahkan package yang dibutuhkan, kita dapat melakukan get package
tersebut. Jika Anda menggunakan visual studio code cukup simpan berkas pubspec.yaml,
maka nanti akan secara otomatis mensinkronisasi pubspec tersebut. Atau, bisa dengan
menekan tombol seperti gambar unduh pada pojok kanan atas paling kiri.
Bila menggunakan Android Studio Anda cukup menekan tombol " Pub get" pada Android
Studio seperti berikut:
Kita juga bisa secara manual menggunakan terminal dengan menjalankan perintah flutter
pub get di dalam project tersebut. Setelah melakukan pub get, maka package tersebut
sudah dapat digunakan.
● https://pub.dev
Website ini merupakan web official untuk Anda mencari package.
● https://flutterawesome.com
Berisi package-package yang dibuat oleh komunitas, di sini banyak sekali package
UI keren yang dapat Anda coba.
Private Packages
Selain menggunakan package yang ada pada pub.dev Anda juga bisa menggunakan
package yang tidak dipublikasikan pada pub.dev tersebut dengan cara menggunakan url git
package tersebut:
dependencies:
plugin1:
git:
url: git://github.com/flutter/plugin1.git
Atau path direktori package tersebut yang tersimpan secara offline di komputer Anda.
dependencies:
plugin1:
path: ../plugin1/
Rangkuman Materi
Mari kita ingat kembali apa saja yang telah dipelajari di modul ini.
Pada modul selanjutnya, kita akan mulai berkenalan dengan widget dan
menyusunnya menjadi sebuah tampilan halaman. Sudah siap? Sampai
bertemu di modul selanjutnya!
Pengenalan Widget
World of components
Perlu kita ketahui bahwa konsep Widget pada Flutter itu terinspirasi oleh
salah satu framework JavaScript yang digunakan untuk membangun
sebuah website yaitu ReactJS. ReactJS memiliki konsep Component. Mari
kita analogikan dengan mainan Lego! Di Lego terdapat block-block kecil
yang nantinya kita susun untuk membangun sebuah istana Lego. Berarti
component dalam programming adalah sekumpulan block-block code yang
digunakan untuk membangun sebuah aplikasi.
Center(
child: Text('Hello world!'),
)
Kode di atas merupakan contoh pemanggilan widget Center. Widget
Center ini digunakan untuk membuat widget yang ada di dalamnya berada
di posisi tengah (mirip seperti alignment center). Tinggal ketikkan Center
lalu tambahkan properti child di dalamnya.
Row(
children: <Widget>[
//di dalam children akan berisi banyak widget
]
)
Contoh di atas adalah widget Row yang memiliki children. Di dalam
children nantinya kita bisa menambahkan banyak widget. Berbeda dengan
child yang diisi langsung dengan sebuah Widget, children akan berisi
sebuah list yang di dalamnya diisi dengan banyak widget.
StatelessWidget
Setelah mengenal apa itu state, maka yang pertama kita akan bahas
adalah StatelessWidget. StatelessWidget adalah widget yang nilai
state-nya tidak dapat berubah (immutable) maka widget tersebut lebih
bersifat statis dan memiliki interaksi yang terbatas.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: Center(
child: Text("Hello world!"),
),
),
);
}
}
class Heading extends StatelessWidget {
final String text;
const Heading({Key? key, required this.text}) : super(key:
key);
@override
Widget build(BuildContext context){
return Text(
text,
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
);
}
}
Kita coba ubah widget Text yang menampilkan "Hello world!" dengan
widget Heading yang kita buat.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: Center(
child: Heading( // mengubah widget Text
text:"Hello world!",
),
),
),
);
}
}
class Heading extends StatelessWidget {
final String text;
const Heading({Key? key, required this.text}) : super(key:
key);
@override
Widget build(BuildContext context){
return Text(
text,
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
);
}
}
Maka ketika kita ubah Text dengan widget Heading, hasilnya akan
berubah. Tulisan "Hello world!" jadi lebih besar.
Sesuai definisi StatelessWidget, state-nya tidak dapat berubah
(immutable), maka state yang ada di dalam kelas tersebut harus dibuat
final. Nilainya hanya dapat diisi melalui constructor class-nya.
StatefulWidget
Kebalikan dari StatelessWidget, StatefulWidget ialah widget yang state-nya
dapat berubah-ubah nilainya, yang berarti StatefulWidget bersifat dinamis
dan memiliki interaksi yang tak terbatas.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: Center(
child: BiggerText(text:"Hello world!"), // Ubah
widget Heading ke PerubahanText
),
),
);
}
}
class Heading extends StatelessWidget {
final String text;
const Heading({Key? key, required this.text}) : super(key:
key);
@override
Widget build(BuildContext context) {
return Text(
text,
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
);
}
}
class BiggerText extends StatefulWidget {
final String text;
const BiggerText({Key? key, required this.text}) : super(key:
key);
@override
_BiggerTextState createState() => _BiggerTextState();
}
class _BiggerTextState extends State<BiggerText> {
double _textSize = 16.0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(widget.text, style: TextStyle(fontSize:
_textSize)),
ElevatedButton(
child: const Text("Perbesar"),
onPressed: () {
setState(() {
_textSize = 32.0;
});
},
)
],
);
}
}
Maka hasilnya akan seperti berikut:
Ketika tombol "Perbesar" ditekan, text "Hello world!" akan membesar
karena state _textSize diubah nilainya. Mengubah nilai state harus
dilakukan pada fungsi setState seperti berikut:
setState(() {
_textSize = 32.0; // ukuran text diubah menjadi 32
});
Anda dapat memahami lebih dalam terkait Stateless dan Stateful Widget
dengan membaca dokumentasi berikut ini:
● StatelessWidget Class
● StatefulWidget Class
Scaffold
Scaffold merupakan sebuah widget yang digunakan untuk membuat
tampilan dasar material design pada aplikasi Flutter, yang dapat disebut
juga dasar sebuah halaman pada aplikasi Flutter. Tampilan dasar tersebut
seperti berikut:
Tampilan di atas merupakan implementasi dari Scaffold. Scaffold di atas
memiliki 3 bagian yaitu AppBar, Body, dan FloatingActionButton. Ketiga
bagian tersebut diilustrasikan seperti berikut:
Pada gambar di atas kotak berwarna merah merupakan AppBar; kotak
berwarna hijau merupakan body; dan kotak berwarna biru merupakan
FloatingActionButton.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const FirstScreen(),// Panggil FirstScreen di sini
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Ketika kita menjalankan aplikasi Flutter, pada layar akan hanya muncul
tampilan berwarna putih.
AppBar
Setelah kita membuat Scaffold pertama kita pada Widget FirstScreen,
sekarang kita akan menambahkan AppBar pada Scaffold. Seperti yang kita
tahu AppBar merupakan Header (bagian paling atas) aplikasi atau biasa
dikenal dengan toolbar. Pada AppBar umumnya terdapat judul dan
ActionButton.
Hasil Akhir
Setelah kita menambahkan AppBar, body, dan FloatingActionButton maka
hasil akhirnya akan seperti berikut:
Untuk memahami Scaffold lebih dalam, Anda bisa membaca tautan berikut:
● Scaffold Class
● Scaffold Sample Apps
Pengenalan Container
Bagaimana sejauh ini? Semoga materinya dapat Anda praktikkan dengan
mulus ya.
Container(
color: Colors.blue,
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Pada kode di atas kita membuat sebuah Text "Hi" yang dibungkus oleh
widget Container dan kita beri parameter color dengan nilai Colors.blue.
Kita letakkan Container di dalam parameter body. Apa hasilnya? Text "Hi"
akan memiliki background berwarna biru. Jalankan project Anda untuk
menampilkan hasil seperti berikut:
Container(
color: Colors.blue,
padding: const EdgeInsets.all(10),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Pada kode di atas kita menambahkan padding pada semua sisi container
secara merata dengan nilai 10. Maka jika me-refresh aplikasi flutter, akan
ada jarak antara Text "Hi" dengan batas (border) dari container.
Lalu penggunaan margin pun sama seperti halnya padding, maka contoh
kodenya seperti berikut:
Container(
color: Colors.blue,
margin: const EdgeInsets.all(10),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Maka hasil dari kode di atas Container akan bergeser lebih ke dalam
karena ada jarak antara Container dengan bagian luar Container.
Dekorasi Container
Decoration merupakan bagian dari Container untuk styling. Pada
decoration kita dapat menentukan warna background (solid/gradient color),
shadow, border, border radius (membulatkan sudut), mengatur shape
(bentuk), dan lain-lain.
Color
Contoh menentukan warna background dari container dengan decoration
seperti berikut:
Container(
decoration: BoxDecoration(
color: Colors.red,
),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Ketika dijalankan maka tampilan aplikasi akan seperti berikut:
Untuk menggunakan decoration cukup menambahkan parameter
decoration pada Container lalu beri nilai BoxDecoration. Pada contoh di
atas kita merubah warna Container menjadi merah dengan memberi
parameter color pada BoxDecoration. Ada catatan penting ketika
menggunakan color pada BoxDecoration, yaitu pastikan tidak memberi
parameter color pada Container.
Shape
Contoh selanjutnya pada decoration adalah kita akan mengatur shape
(bentuk) dari Container, contohnya sebagai berikut:
Container(
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Pada kode di atas kita menambahkan parameter shape dengan nilai
BoxShape.circle. Artinya, bentuk dari Container tersebut akan berbentuk
lingkaran. BoxShape memiliki opsi circle atau rectangle.
Shadow
Untuk menambahkan shadow pada Container kita akan menambahkan
parameter boxShadow pada BoxDecoration, seperti berikut:
Container(
decoration: BoxDecoration(
color: Colors.red,
boxShadow: const [
BoxShadow(
color: Colors.black,
offset: Offset(3, 6),
blurRadius: 10,
),
],
),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Pada kode di atas parameter boxShadow merupakan sebuah Array. Di
dalamnya terdapat BoxShadow yang artinya pada Container kita dapat
memberikan banyak bayangan atau shadow.
Border
Border merupakan batas garis dengan content (child). Begini cara
menambahkan border pada container:
Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.green, width: 3),
),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Apabila Anda ingin membuat border yang tidak berujung lancip cukup
tambahkan parameter borderRadius Pada BoxDecoration seperti berikut:
Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.green,width: 3),
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'Hi',
style: TextStyle(fontSize: 40),
),
),
Kesimpulan
Dengan menggunakan Widget Container kita dapat membuat variasi
widget yang kita buat. Sebenarnya banyak sekali parameter-parameter
yang dapat digunakan pada Container dan juga pada BoxDecoration. Anda
dapat mengeksplorasi hal tersebut dengan mencarinya di Google atau
pada dokumentasi resmi flutter.
● Container Class
Padding
Sebelumnya kita telah belajar banyak hal mengenai Container. Kali ini kita
akan belajar widget Padding. Seperti namanya widget Padding merupakan
sebuah widget yang khusus untuk memberikan padding pada suatu widget.
Padding(
padding: const EdgeInsets.all(30),
child: const Text('Ini Padding')
)
Pada kode di atas widget Padding harus memiliki child. Child di sini
merupakan sebuah widget yang nantinya akan diberi padding. Parameter
padding ditambahkan untuk menentukan besaran padding yang diinginkan.
● Padding Class
Center
Widget Center merupakan sebuah widget yang digunakan untuk membuat suatu widget
berada pada posisi tengah. Penggunaan widget Center sangatlah simpel, yakni seperti
berikut:
Center(
child: const Text('Text berada di tengah'),
)
Widget Center hanya membutuhkan parameter child untuk membuat
widget di dalamnya berada pada posisi tengah. Hasil dari Center seperti
berikut:
Widget Row
Seperti yang dicontohkan sebelumnya, widget Row merupakan suatu
widget yang digunakan untuk membuat widget-widget tersusun berjajar
secara horizontal. Row memiliki sintaks seperti berikut:
Row(
children: <Widget>[
//di sini berisi widget-widget
],
)
Untuk membuat widget-widget berjajar secara horizontal kita harus
memasukkan widget-widget tersebut ke dalam parameter children.
Parameter children berisi kumpulan atau list dari widget karena kita dapat
menyusun beberapa widget sekaligus di dalamnya. Jika mengacu pada
contoh tombol-tombol di atas kodenya seperti berikut:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const <Widget>[
Icon(Icons.share),
Icon(Icons.thumb_up),
Icon(Icons.thumb_down),
],
)
Seperti yang kita lihat, kita membuat sebuah IconButton berada di dalam
parameter children. Kita menambahkan pula mainAxisAlignment yang
merupakan parameter alignment pada Row. Parameter mainAxisAlignment
yang berfungsi untuk mengatur alignment vertikal dari Row (alignment
utama). Selain itu Row juga memiliki parameter crossAxisAlignment yang
berfungsi untuk mengatur alignment secara horizontal. Kedua parameter ini
juga berlaku sebaliknya untuk widget Column.
Berikut ini adalah contoh penerapan mainAxisAlignment pada Row:
Widget Column
Kebalikan dari Row, Column merupakan suatu widget yang digunakan
untuk membuat widget-widget tersusun berjajar secara vertikal. Column
memiliki sintaks mirip dengan Row, seperti berikut:
Column(
children: <Widget>[
//di sini berisi widget-widget
]
)
Contoh penerapan Column seperti berikut:
Column(
children: const <Widget>[
Text(
'Sebuah Judul',
style: TextStyle(fontSize: 32, fontWeight:
FontWeight.bold),
),
Text('Lorem ipsum dolor sit amet'),
],
)
Maka akan menghasilkan tampilan seperti berikut:
Kesimpulan
Untuk membuat sebuah widget-widget berjajar kita dapat menggunakan
widget Row atau Column. Sebenarnya penggunaan Row dan Column
dapat dipadukan sehingga dapat membuat sebuah layout yang kompleks
seperti berikut:
● Row Class
● Column Class
● Layouts in Flutter
Codelab 1
Pada kelas ini kita akan mengembangkan sebuah aplikasi yang
menampilkan tempat-tempat wisata di Bandung. Hasil akhir dari
keseluruhan codelab akan seperti berikut:
Dalam codelab pertama ini kita akan membuat sebuah tampilan yang
menggabungkan semua widget-widget yang sebelumnya kita pelajari.
Tampilannya adalah seperti berikut:
Sebelum kita membuat tampilan di atas, kita akan bedah terlebih dahulu
layout-nya.
13. Selanjutnya, sesuai contoh kita akan membuat teks judul berada
di tengah. Tambahkan parameter atau properti textAlign pada widget
Text. Selain itu, tambahkan juga style dengan memperbesar ukuran
teks agar tulisan dapat dibaca.
Container(
margin: EdgeInsets.only(top: 16.0),
child: const Text(
'Farm House Lembang',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
14. Lakukan hot reload. Tidak ada perubahan, apa sebabnya? Jika
menggunakan Android Studio Anda dapat memanfaatkan fitur Flutter
Inspector untuk melihat layout widget di dalam aplikasi.
Dari gambar di atas bisa kita lihat ternyata layout aplikasi kita tidak
penuh hingga seluruh halaman. Ini disebabkan sisi horizontal dari
Column hanya menyesuaikan dengan konten yang ada di dalamnya.
Untuk memaksimalkan ukuran lebar dari Column, tambahkan kode
berikut:
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 16.0),
child: Text(
'Farm House Lembang',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
15. Setelah menyelesaikan judul, selanjutnya kita akan membuat
bagian kedua yaitu informasi dari tempat wisata.
● Codelab-1
Button
Kali ini kita akan belajar menggunakan widget button. Widget button ini
adalah widget yang dapat menerima trigger sentuhan atau dapat
melakukan suatu fungsi ketika disentuh, widget-widget button tersebut
antara lain:
ElevatedButton
ElevatedButton merupakan bagian dari Material Design widget dari Flutter.
Untuk menggunakan ElevatedButton caranya seperti berikut:
ElevatedButton(
child: const Text("Tombol"),
onPressed: () {
// Aksi ketika button diklik
},
),
Pada kode di atas ElevatedButton memiliki 2 parameter yaitu onPressed
dan child. Parameter onPressed merupakan sebuah function event ketika
tombol ditekan dan sebenarnya ada event lain seperti onLongPress dan
onHighlightChanged. Parameter child diisi oleh widget pada umumnya.
TextButton
TextButton merupakan widget button yang memiliki tampilan yang polos
selayaknya Text. TextButton umumnya digunakan pada toolbars, dialog,
atau bersama komponen button lain. Contoh kode dari TextButton adalah
seperti berikut:
TextButton(
child: const Text('Text Button'),
onPressed: () {
// Aksi ketika button diklik
},
),
Sama halnya ElevatedButton, TextButton juga memiliki parameter
onPressed dan child.
OutlinedButton
OutlinedButton juga merupakan bagian dari material design yang
menyediakan tampilan TextButton dengan tambahan outline.
OutlinedButton umumnya digunakan untuk tombol atau aksi yang penting,
tetapi bukan aksi utama dalam aplikasi.
IconButton
IconButton merupakan widget button dengan icon. Tak seperti widget
tombol lainnya, widget IconButton ini tidak memiliki child. Perhatikan kode
di bawah ini:
IconButton(
icon: const Icon(Icons.volume_up),
tooltip: 'Increase volume by 10',
onPressed: () {
// Aksi ketika button diklik
},
),
Seperti yang kita lihat di atas, IconButton tidak menggunakan child untuk isi
(content) melainkan menggunakan parameter icon dan tooltip (penunjuk)
untuk memberikan hint pada tombol.
DropdownButton
DropdownButton merupakan tombol yang saat diklik, akan muncul pop-up
daftar beberapa item yang dapat kita pilih salah satu. Berikut contoh
kodenya:
Input Widget
Salah satu bentuk interaksi dengan pengguna adalah dengan menerima
input. Ada beberapa input widget yang bisa digunakan supaya pengguna
bisa berinteraksi dengan aplikasi. Perhatikan bahwa input pengguna ini
berkaitan dengan state yang dapat sering berubah. Karena itu umumnya
input widget akan ditempatkan di dalam StatefulWidget.
TextField
TextField merupakan sebuah widget yang digunakan untuk menerima input
berupa teks yang berasal dari keyboard. Terdapat beberapa cara yang bisa
Anda gunakan untuk mendapatkan nilai dari TextField. Salah satunya
adalah melalui parameter onChanged.
Jika Anda tidak ingin mengambil nilai setiap perubahan, tetapi hanya ketika
seluruh input sudah selesai di-submit, Anda dapat menggunakan
parameter onSubmitted seperti berikut:
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Berikut ini adalah contoh penerapan widget TextField:
Untuk membuat TextField seperti di atas, Anda bisa menggunakan kode
seperti berikut:
● onChanged
class _FirstScreenState extends State<FirstScreen> {
String _name = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Screen'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
hintText: 'Write your name here...',
labelText: 'Your Name',
),
onChanged: (String value) {
setState(() {
_name = value;
});
},
),
const SizedBox(height: 20),
ElevatedButton(
child: const Text('Submit'),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Hello, $_name'),
);
});
},
)
],
),
),
);
}
}
● Controller
● class _FirstScreenState extends State<FirstScreen> {
● TextEditingController _controller =
TextEditingController();
●
● @override
● Widget build(BuildContext context) {
● return Scaffold(
● appBar: AppBar(
● title: const Text('First Screen'),
● ),
● body: Padding(
● padding: const EdgeInsets.all(16.0),
● child: Column(
● children: [
● TextField(
● controller: _controller,
● decoration: const InputDecoration(
● hintText: 'Write your name here...',
● labelText: 'Your Name',
● ),
● ),
● const SizedBox(height: 20),
● ElevatedButton(
● child: const Text('Submit'),
● onPressed: () {
● showDialog(
● context: context,
● builder: (context) {
● return AlertDialog(
● content: Text('Hello,
${_controller.text}'),
● );
● });
● },
● )
● ],
● ),
● ),
● );
● }
●
● @override
● void dispose() {
● _controller.dispose();
● super.dispose();
● }
● }
Switch
Switch merupakan inputan yang mengembalikan nilai boolean true atau
false. Perhatikan contoh berikut:
Radio
Radio merupakan inputan yang digunakan untuk memilih salah satu dari
beberapa pilihan dalam suatu kelompok. Berikut contohnya:
Ada beberapa tautan yang dapat Anda baca untuk memahami tentang
widget-widget input yang ada pada Flutter, antara lain:
Image.network
Untuk menampilkan gambar yang bersumber dari internet, kita akan
menggunakan method Image.network. Cara penulisan method ini sebagai
berikut:
Image.network(url)
Method ini cukup menambahkan URL gambar dari internet dan kita pun
dapat menambahkan width dan height juga. Di bawah ini adalah contoh
penggunaan Image.network:
Image.asset
Selain melalui internet, kita juga dapat menampilkan gambar yang
bersumber dari asset project.. Asset di sini berupa gambar-gambar yang
nantinya didaftarkan pada project. Untuk mendaftarkan asset gambar pada
project kita harus menambahkannya pada berkas pubspec.yaml.
Pada contoh berikut kita menambahkan folder images/ pada folder project.
...
flutter:
uses-material-design: true
# To add assets to your application, add an assets section,
like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
...
Daftarkan asset gambar seperti berikut:
...
flutter:
uses-material-design: true
assets:
- images/android.png
...
Hapus juga tanda pagar (#) atau komentar yang tidak diperlukan.
Perhatikan pula indentasi kodenya. assets: berada sejajar dengan
uses-material-design: yaitu berjarak 2 spasi dari ujung dan berada di dalam
flutter: sedangkan - images/android.png berada di dalam assets: dan
berjarak 4 spasi dari ujung.
Pada contoh di atas kita telah menambahkan asset yang berisi lokasi
gambar atau aset yang ingin kita gunakan. Karena kita menambahkan
gambar android.png pada folder images, maka lokasi gambar tersebut
adalah images/android.png.
Apabila ada banyak gambar yang kita masukkan ke dalam lokasi folder,
dibandingkan menuliskan lokasi gambar satu per satu, kita bisa langsung
menuliskan folder images/ seperti berikut:
...
flutter:
uses-material-design: true
assets:
- images/
...
Setelah menambahkan assets, kita harus me-refresh pubspec.yaml
dengan cara save file pubspec.yaml bila menggunakan Visual Studio Code
atau menekan 'Packages get' yang ada di pojok kanan atas untuk Android
Studio.
Setelah kita menambahkan asset ke dalam pubspec.yaml kita perlu
melakukan full restart agar asset yang baru dapat digunakan dalam
aplikasi.
Kita telah mendaftarkan suatu asset. Sekarang kita akan panggil asset
tersebut pada kode kita dengan method Image.asset. Cara penulisannya
seperti berikut:
Image.asset(lokasi_asset)
Contoh dalam kodenya akan seperti berikut:
Font
Dalam pengembangan suatu aplikasi, seorang User Interface desainer
dapat menggunakan font berbeda dengan default font yang ada. Sebagai
pengembang aplikasi kita diharuskan menambahkan font pada aplikasi
yang dirancang oleh desainer agar sesuai dengan desain User Interface.
Pada pembelajaran kali ini kita akan belajar bagaimana menambahkan font
pada Flutter. Sebelum kita memulai pembelajaran, kita akan mengunduh
font yang ada di internet atau menggunakan font yang telah dimiliki. Pada
contoh ini kita akan mengunduh salah satu font dari Google Fonts yaitu
Oswald.
flutter:
uses-material-design: true
assets:
- images/
fonts:
- family: Oswald
fonts:
- asset: fonts/Oswald/Oswald-Regular.ttf
Sama halnya dengan gambar, font ada dalam bagian flutter. Untuk
mendaftarkan font, kita membuat bagian fonts yang ada dalam blok flutter.
Untuk mendaftarkan font Oswald kita tuliskan Oswald pada bagian family
yang nantinya akan menjadi nama font yang kita panggil pada kode dart.
Lalu dalam family kita masukkan fonts yang di dalamnya terdapat asset
yang nanti akan mengarah pada file font.ttf. Contoh di atas kita
menambahkan asset fonts/oswald/Oswald-Regular.ttf.
Text(
'Custom Font',
style: TextStyle(
fontFamily: 'Oswald',
fontSize: 30,
),
),
Pada kode di atas kita menambahkan fontFamily pada TextStyle. Kita
cukup panggil nama font family yang telah kita daftarkan pada
pubspec.yaml. Hasilnya akan seperti berikut:
Tulisan "Custom Font" akan berubah menjadi font Oswald sesuai dengan
yang telah kita daftarkan.
Jangan lupa! Setelah kita menambahkan package atau pun asset ke dalam
pubspec.yaml kita perlu melakukan full restart agar asset yang baru
dapat digunakan dalam aplikasi.
10. Sebagai solusi, tentunya kita bisa mengubah ukuran dari gambar,
namun tentunya tidak praktis jika kita harus mengubah ukuran setiap
gambar yang ditampilkan. Tentu ada banyak sekali ukuran layar yang
tersedia, bukan? Solusi lainnya yaitu dengan menerapkan scrolling.
Salah satu widget scrolling yang bisa kita manfaatkan adalah
SingleChildScrollView. Widget ini membutuhkan satu child yang
nantinya bisa di-scroll pada layar. Pindahkan widget Column ke
dalam SingleChildScrollView supaya nantinya bisa di-scroll.
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset('images/farm-house.jpg'),
Container(...),
Container(...),
Container(...),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/5
9/70/farmhouse-lembang.jpg'),
],
),
),
),
);
}
}
11.Jalankan hot reload. Seharusnya masalah overflow sudah teratasi
dengan adanya scrolling.
12. Selanjutnya kita akan menambahkan beberapa gambar lagi yang
disusun secara horizontal. Anda mungkin mengira untuk
menggunakan widget Row supaya gambar bisa tersusun secara
horizontal. Namun, perlu diingat bahwa kita juga memerlukan fitur
scrolling agar tidak terjadi overflow. Oleh karena itu, kita akan
menggunakan ListView. Widget ini memungkinkan kita untuk
menerapkan scrolling terhadap beberapa item (children).
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset('images/farm-house.jpg'),
Container(...),
Container(...),
Container(...),
ListView(
children: [
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/59
/70/farmhouse-lembang.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/22
/f6/photo3jpg.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16/
a9/33/43/liburan-di-farmhouse.jpg'),
],
),
],
),
),
),
);
}
}
13. Jika Anda menjalankan aplikasi atau melakukan hot reload,
aplikasi Anda akan menjadi blank dan muncul pesan eror pada log.
Kenapa ya? ListView diletakkan di dalam Column, di mana keduanya
sama-sama memiliki atribut height yang memakan space di
sepanjang layar. Sebagai solusi kita perlu memberikan ukuran tinggi
yang statis terhadap ListView. Namun ListView tidak memiliki
parameter height, lantas bagaimana nih? Caranya, gunakan widget
lain yang memiliki parameter height. Anda dapat membungkus
widget ListView ke dalam Container atau pun SizedBox. Ukuran
tinggi ini nantinya juga digunakan sebagai tinggi Image yang tampil.
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset('images/farm-house.jpg'),
Container(...),
Container(...),
Container(...),
SizedBox(
height: 150,
child: ListView(
children: [
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/5
9/70/farmhouse-lembang.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/2
2/f6/photo3jpg.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16
/a9/33/43/liburan-di-farmhouse.jpg'),
],
),
),
],
),
),
),
);
}
}
14. Secara default arah scroll dari ListView adalah vertikal. Untuk
mengubahnya menjadi horizontal kita cukup menambahkan
parameter scrollDirection bernilai Axis.horizontal.
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset('images/farm-house.jpg'),
Container(...),
Container(...),
Container(...),
SizedBox(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/5
9/70/farmhouse-lembang.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/2
2/f6/photo3jpg.jpg'),
Image.network(
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16
/a9/33/43/liburan-di-farmhouse.jpg'),
],
),
),
],
),
),
),
);
}
}
15. Selanjutnya, kita akan sedikit merapikan tampilan gambar supaya
terlihat lebih rapi dan menarik. Tambahkan Padding pada
masing-masing Image supaya antar gambar tidak terlalu rapat.
SizedBox(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4.0),
child: Image.network(
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/59
/70/farmhouse-lembang.jpg'),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Image.network(
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/22
/f6/photo3jpg.jpg'),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Image.network(
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16/
a9/33/43/liburan-di-farmhouse.jpg'),
),
],
),
),
16. Bagaimana membuat gambar memiliki sudut yang membulat
seperti pada contoh? Sekali lagi, dokumentasi adalah sahabat
terbaik Anda dalam mengembangkan aplikasi Flutter. Anda dapat
memanfaatkan mesin pencari untuk menemukan widget sesuai
keinginan. Misalnya, dengan memanfaatkan Google Anda dapat
menemukan bahwa ada widget yang memungkinkan gambar
memiliki radius, yaitu ClipRRect. Masukkan widget Image Anda
sebagai child dari ClipRRect dan berikan borderRadius, maka
Anda akan mendapatkan Image dengan sudut yang tak bersiku.
● Codelab-2
ListView
Pada Codelab kedua kita telah menggunakan dan menyinggung sedikit
tentang widget ListView. Widget ini digunakan untuk menampilkan
beberapa item dalam bentuk baris atau kolom dan bisa di-scroll.
Cara penggunaan ListView ini mirip dengan Column atau Row di mana
Anda memasukkan widget yang ingin disusun sebagai children dari
ListView.
Menggunakan ListView.builder
Selain mengisi parameter children dari ListView seperti sebelumnya, kita
juga bisa memanfaatkan method ListView.builder. ListView.builder lebih
cocok digunakan pada ListView dengan jumlah item yang cukup besar. Ini
karena Flutter hanya akan merender tampilan item yang terlihat di layar
dan tidak me-render seluruh item ListView di awal.
ListView.builder(
itemCount: numberList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 250,
decoration: BoxDecoration(
color: Colors.grey,
border: Border.all(color: Colors.black),
),
child: Center(
child: Text(
'${numberList[index]}',
style: const TextStyle(fontSize: 50),
),
),
);
},
),
ListView.separated
Cara lain untuk membuat ListView adalah dengan metode
ListView.separated. ListView jenis ini akan menampilkan daftar item yang
dipisahkan dengan separator. Penggunaan ListView.separated mirip
dengan builder, yang membedakan adalah terdapat satu parameter
tambahan wajib yaitu separatorBuilder yang mengembalikan Widget yang
akan berperan sebagai separator.
ListView.separated(
itemCount: numberList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 250,
decoration: BoxDecoration(
color: Colors.grey,
border: Border.all(color: Colors.black),
),
child: Center(
child: Text(
'${numberList[index]}',
style: const TextStyle(fontSize: 50),
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
Jika kode di atas dijalankan, maka tampilan aplikasi adalah seperti ini:
Expanded
Flutter memiliki widget Expanded yang dapat mengembangkan child dari
Row atau Column sesuai dengan ruang yang tersedia. Cara
menggunakannya Anda cukup membungkus masing-masing child ke
dalam Expanded.
Bisa kita lihat seluruh container menempati ruang dengan ukuran yang
sama. Ini disebabkan Expanded memiliki parameter flex yang memiliki nilai
default 1. Anda dapat mengubah nilai flex ini sesuai perbandingan yang
diinginkan. Misalnya Anda memberikan nilai flex 2 pada salah satu
container.
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
),
),
Maka container berwarna biru ini akan menjadi lebih besar dengan
perbandingan 2/(1 + 1 + 1 + 1 + 2 + 1 + 1) atau 2/8 dari halaman.
Flexible
Sama seperti Expanded, widget Flexible digunakan untuk mengatur ukuran
widget di dalam Row atau Column secara fleksibel. Perbedaan Flexible
dan Expanded adalah widget Flexible memungkinkan child widget-nya
berukuran lebih kecil dibandingkan ukuran ruang yang tersisa. Sementara,
child widget dari Expanded harus menempati ruang yang tersisa dari
Column atau Row.
● Expanded Class
● Flexible Class
Navigation
Kita telah bisa membuat satu tampilan screen (layar/page) pada
pembelajaran sebelumnya. Namun, pada saat membangun sebuah
aplikasi kita akan membuat banyak sekali screen dan kita akan berpindah
dari satu screen ke screen lainnya.
Navigator.push
Untuk berpindah ke screen kedua kita akan menggunakan sebuah method
Navigator.push, method tersebut ditulis seperti berikut:
Navigator.pop
Setelah dapat berpindah ke screen lain maka kita akan belajar
menggunakan Navigator.pop untuk kembali ke screen sebelumnya.
Penulisan Navigator.pop seperti berikut.
Navigator.pop(context)
Pada Navigator.pop kita hanya cukup menambahkan parameter context
yang merupakan variabel dari method build.
Untuk kembali dari screen kedua kita dapat menambahkan event
onPressed pada OutlinedButton yang ada pada screen kedua dan kita
masukkan Navigator.pop pada event, seperti berikut:
Sebagai contoh kita memiliki pesan yang akan dikirimkan dari First Screen
menuju Second Screen.
Responsive Layout
Seperti yang kita tahu, Flutter merupakan framework untuk
mengembangkan aplikasi pada berbagai platform. Pada platform mobile
sendiri tersedia banyak ukuran layar dari ukuran jam hingga tablet.
Ditambah Flutter baru saja mengumumkan dukungan untuk platform web
dan desktop. Itu artinya, satu hal yang penting untuk kita pahami adalah
bagaimana menerapkan layout yang mampu beradaptasi dengan berbagai
ukuran layar yang berbeda.
Dengan mendapatkan ukuran lebar dan tinggi layar seperti di atas, kita
bisa menentukan tampilan konten berdasarkan ukuran layar yang
digunakan. Dalam responsive design, terdapat breakpoint yang merupakan
“titik” di mana layout akan beradaptasi untuk memberikan pengalaman
pengguna sebaik mungkin.
Dengan kode di bawah ini berarti akan terdapat tiga model tampilan
berdasarkan ukuran layar:
1. Pertama kali yang kita lakukan adalah membuat halaman baru untuk
menampilkan daftar tempat wisata. Buat berkas baru
main_screen.dart lalu buat widget untuk halaman MainScreen.
import 'package:flutter/material.dart';
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
2. Jangan lupa untuk mengganti halaman utama yang ditampilkan pada
berkas main.dart.
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wisata Bandung',
theme: ThemeData(),
home: const MainScreen(),
);
}
}
3. Pada MainScreen tambahkan AppBar untuk judul halaman.
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wisata Bandung'),
),
);
}
}
4. Sebagai body dari Scaffold kita akan menggunakan widget Card.
Widget ini adalah widget material design yang menghasilkan
tampilan seperti kartu dengan ujung yang membulat dan bayangan di
belakang. Kemudian susun Row dan Column seperti contoh untuk
menyusun child dari Card. Kodenya akan seperti berikut:
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wisata Bandung'),
),
body: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset('images/farm-house.jpg'),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Farm House Lembang',
style: const TextStyle(fontSize:
16.0),
),
const SizedBox(
height: 10,
),
Text('Lembang'),
],
),
)
],
),
),
);
}
}
5. Jalankan aplikasinya. Aplikasi akan mengalami overflow karena aset
gambar yang terlalu besar. Kita bisa saja mengatur tinggi gambar
secara manual, namun kali ini kita akan memanfaatkan widget
Expanded agar tampilan juga dapat menyesuaikan di perangkat
yang lebih besar atau kecil. Bungkus masing-masing item dari widget
Row ke dalam Expanded. Berikan parameter flexyang menurut
Anda cocok.
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wisata Bandung'),
),
body: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 1,
child:
Image.asset('images/farm-house.jpg'),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Farm House Lembang',
style: const TextStyle(fontSize:
16.0),
),
const SizedBox(
height: 10,
),
Text('Lembang'),
],
),
),
),
],
),
),
);
}
}
6. Item pertama Anda sudah siap. Selanjutnya kita akan membuat kartu
ini bisa diklik untuk berpindah ke halaman detail. Kita bisa
menggunakan widget InkWell yang menyediakan parameter onTap.
Pindahkan widget Card Anda menjadi child dari InkWell.
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wisata Bandung'),
),
body: InkWell(
onTap: () {},
child: Card(...),
),
);
}
}
7. Parameter onTapmenerima argumen berupa sebuah fungsi lambda.
Di sini kita akan menambahkan Navigator untuk berpindah ke
halaman detail.
onTap: () {
Navigator.push(context, MaterialPageRoute(builder:
(context) {
return DetailScreen();
}));
},
8. Jalankan aplikasi. Seharusnya sampai langkah ini aplikasi Anda
sudah dapat berpindah halaman ketika item diklik.
9. Selanjutnya kita akan menampilkan beberapa item ke MainScreen.
Di kelas ini kita masih menggunakan data statis dan lokal yang
disimpan pada objek List. Sebelumnya, buatlah kelas sebagai
blueprint untuk menyimpan objek tempat wisata kita. Buat folder baru
di dalam folder lib dengan cara klik kanan folder lib -> New ->
Package, lalu berikan nama model. Di dalam folder model buat
berkas dart bernama tourism_place.dart.
10. Di dalam tourism_place.dart buat data class yang akan menjadi
blueprint objek tempat wisata.
class TourismPlace {
String name;
String location;
String description;
String openDays;
String openTime;
String ticketPrice;
String imageAsset;
List<String> imageUrls;
TourismPlace({
required this.name,
required this.location,
required this.description,
required this.openDays,
required this.openTime,
required this.ticketPrice,
required this.imageAsset,
required this.imageUrls,
});
}
11.Siapkan data statis yang ingin ditampilkan Anda dapat menyalin kode
berikut dan taruh di berkas tourism_place.dart paling bawah.
var tourismPlaceList = [
TourismPlace(
name: 'Farm House Lembang',
location: 'Lembang',
description:
'Berada di jalur utama Bandung-Lembang, Farm
House menjadi objek wisata yang tidak pernah sepi
pengunjung. Selain karena letaknya strategis, kawasan
ini juga menghadirkan nuansa wisata khas Eropa. Semua
itu diterapkan dalam bentuk spot swafoto
Instagramable.',
openDays: 'Open Everyday',
openTime: '09:00 - 20:00',
ticketPrice: 'Rp 25000',
imageAsset: 'images/farm-house.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/5
9/70/farmhouse-lembang.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/2
2/f6/photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16
/a9/33/43/liburan-di-farmhouse.jpg'
],
),
TourismPlace(
name: 'Observatorium Bosscha',
location: 'Lembang',
description:
'Memiliki beberapa teleskop, antara lain,
Refraktor Ganda Zeiss, Schmidt Bimasakti, Refraktor
Bamberg, Cassegrain GOTO, dan Teleskop Surya. Refraktor
Ganda Zeiss adalah jenis teleskop terbesar untuk
meneropong bintang. Benda ini diletakkan pada atap kubah
sehingga saat teropong digunakan, atap tersebut harus
dibuka. Observatorium Bosscha boleh dikunjungi oleh
siapa pun, tanpa tiket. Namun, bagi yang ingin
menggunakan teleskop Zeiss, wajib mendaftarkan diri.
Untuk instansi atau lembaga pendidikan, diberikan jadwal
hari Selasa sampai Jumat. Sementara itu, kunjungan
individu dibuka setiap hari Sabtu.',
openDays: 'Open Tuesday - Saturday',
openTime: '09:00 - 14:30',
ticketPrice: 'Rp 20000',
imageAsset: 'images/bosscha.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/12/6b/6
3/0b/bosscha-observatory.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0d/6a/8
8/9b/photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/3f/0
4/39/p-20171111-110220-largejpg.jpg',
],
),
TourismPlace(
name: 'Jalan Asia Afrika',
location: 'Kota Bandung',
description:
'Jalan Asia Afrika di Bandung memiliki kaitan
yang sangat erat dengan pendirian kota Kembang ini.
Karena pada saat itu, Gubernur Jenderal Herman Willem
Deaendels dari Belanda menancapkan tongkatnya saat
memerintahkan pendirian kota ini, yang kemudian
diabadikan menjadi tugu Bandung Nol Kilometer.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/jalan-asia-afrika.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0d/c2/e
7/e6/quotes-kota-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/17/f4/4
4/01/jalan-asia-afrika.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/0a/ef/3
6/e2/jalan-asia-afrika.jpg',
],
),
TourismPlace(
name: 'Stone Garden',
location: 'Padalarang',
description:
'Stone Garden atau Taman Batu di Padalarang –
Bandung ini adalah nama secara harafiah untuk apa yang
akan kita lihat jika berada di sana. Hamparan batu yang
artistik membuat kita merasa tidak sedang berada di
Bandung, apalagi di Padalarang. Hamparan batu yang
dimaksud bukan terhampar begitu saja di atas tanah luas
yang menjadi permukaannya. Batu-batu besar yang ukuran
pastinya bervariasi tersusun seperti memiliki suatu
formasi matematis.',
openDays: 'Open Everyday',
openTime: '06:00 - 17:00',
ticketPrice: 'Rp 3000',
imageAsset: 'images/stone-garden.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/15/01/d
7/4b/p-20180510-153310-01.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/15/68/0
0/32/stone-garden-citatah.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0d/a2/c
b/05/stone-garden-citatah.jpg',
],
),
TourismPlace(
name: 'Taman Film Pasopati',
location: 'Kota Bandung',
description:
'Menjadi salah satu tempat wisata di Bandung
yang favorit, tentu Taman Film ini memiliki fasilitas
cukup memadai. Pemberian fasilitas ini memiliki harapan
para pengunjung akan merasa nyaman dan tak segan2 untuk
kembali berkunjung terus menerus kesini. Beberapa
fasilitas taman yang bisa kamu nikmati diantaranya
seperti layar videotron besar berukuran 4×8 untuk
memutar berbagai macam pilihan film seperti Film
Indonesia, Bollywood, Korea, ataupun Indie Bandung.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/taman-film.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/08/8b/8
7/50/bandung-movie-park.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/17/67/d
5/53/img-20190505-114509-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/09/73/3
3/05/taman-film-pasopati.jpg',
],
),
TourismPlace(
name: 'Museum Geologi',
location: 'Kota Bandung',
description:
'Museum Geologi didirikan pada tanggal 16 Mei
1929. Museum ini telah direnovasi dengan dana bantuan
dari JICA (Japan International Cooperation Agency).
Setelah mengalami renovasi, Museum Geologi dibuka
kembali dan diresmikan oleh Wakil Presiden RI, Megawati
Soekarnoputri pada tanggal 23 Agustus 2000. Sebagai
salah satu monumen bersejarah, museum berada di bawah
perlindungan pemerintah dan merupakan peninggalan
nasional. Dalam Museum ini, tersimpan dan dikelola
materi-materi geologi yang berlimpah, seperti fosil,
batuan, mineral. Kesemuanya itu dikumpulkan selama kerja
lapangan di Indonesia sejak 1850.',
openDays: 'Open Saturday - Thursday',
openTime: '09:00 - 15:30',
ticketPrice: 'Rp 3000',
imageAsset: 'images/museum-geologi.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-w/19/1c/8
e/f7/geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/a7/3
5/b7/geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/1a/55/e
0/dc/geology-museum.jpg',
],
),
TourismPlace(
name: 'Floating Market',
location: 'Lembang',
description:
'Tempat wisata ini sepertinya memang ditujukan
untuk wisata keluarga di Bandung. Di sini kita bisa
menikmati suasana kawasan yang tertata rapi dan alami.
Pada awalnya, floating market Lembang tidak begitu luas.
Tapi sekarang sudah ekspansi dan memiliki banyak objek
menarik baru. Nama floating market ini sepertinya
merujuk pada stand tempat jualan makanan yang berada
dalam perahu.',
openDays: 'Open Everyday',
openTime: '09:00 - 17:00',
ticketPrice: 'Rp 20000',
imageAsset: 'images/floating-market.png',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/17/f9/f
f/f8/floating-market-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/1a/86/d
3/cd/20200103-125059-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/19/ce/b
4/9b/img20181224120857-largejpg.jpg',
],
),
TourismPlace(
name: 'Kawah Putih',
location: 'Ciwidey',
description:
'Kawah Putih adalah tempat wisata di Bandung
yang paling terkenal. Berlokasi di Ciwidey, Jawa Barat,
kurang lebih sekitar 50 KM arah selatan kota Bandung,
Kawah Putih adalah sebuah danau yang terbentuk akibat
dari letusan Gunung Patuha. Sesuai dengan namanya, tanah
yang ada di kawasan ini berwarna putih akibat dari
pencampuran unsur belerang.',
openDays: 'Open Everyday',
openTime: '07:00 - 17:00',
ticketPrice: 'Rp 15000',
imageAsset: 'images/kawah-putih.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0b/6e/7
c/ce/rocks-sticking-out-of.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0b/35/3
0/14/white-crater.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0a/8b/9
a/79/2945-t00572-www-initempatwisat.jpg',
],
),
TourismPlace(
name: 'Ranca Upas',
location: 'Ciwidey',
description:
'Ranca Upas Ciwidey adalah kawasan bumi
perkemahan di bawah pengelolaan perhutani. Tempat ini
berada di kawasan wisata Bandung Selatan, satu lokasi
dengan kawah putih, kolam Cimanggu dan situ Patenggang.
Banyak hal yang bisa dilakukan di kawasan wisata ini,
seperti berkemah, berinteraksi dengan rusa, sampai
bermain di water park dan mandi air panas.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Rp 20000',
imageAsset: 'images/ranca-upas.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/1a/e0/7
f/9c/kampung-cai-ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/2
f/87/ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/2
7/0a/ranca-upas.jpg',
],
),
];
12. Jangan lupa untuk menambahkan import berkas
tourism_place.dart pada file main_screen.dart.
import 'package:wisatabandung/model/tourism_place.dart';
13. Untuk berkas aset yang digunakan dapat Anda unduh pada tautan
berikut.
14. Sesuai yang telah kita pelajari pada materi ListView, kita akan
menampilkan variabel tourismPlaceList di atas menjadi item card
yang dapat diklik. Tambahkan widget ListView sebagai body dari
Scaffold. Pindahkan widget FlatButton dan seluruh konten di
dalamnya sebagai widget dari setiap item di dalam tourismPlaceList.
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wisata Bandung'),
),
body: ListView.builder(
itemBuilder: (context, index) {
final TourismPlace place =
tourismPlaceList[index];
return InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return DetailScreen();
}));
},
child: Card(
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 1,
child: Image.asset(place.imageAsset),
),
Expanded(
flex: 2,
child: Padding(
padding: const
EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
place.name,
style: const
TextStyle(fontSize: 16.0),
),
const SizedBox(
height: 10,
),
Text(place.location),
],
),
),
)
],
),
),
);
},
itemCount: tourismPlaceList.length,
),
);
}
}
15. Jangan lupa untuk mengganti tampilan item secara dinamis
sesuai data dari objek TourismPlace. Jalankan aplikasi untuk
melihat hasil perubahan.
16. Agar halaman detail bisa menampilkan informasi sesuai tempat
wisata yang dipilih, kita perlu mengirimkan data TourismPlace melalui
constructor. Buka berkas detail_screen.dart lalu tambahkan variabel
serta constructor-nya.
class DetailScreen extends StatelessWidget {
final TourismPlace place;
const DetailScreen({Key? key, required this.place}) :
super(key: key);
@override
Widget build(BuildContext context) {...}
}
17. Tambahkan keyword required agar parameter place wajib
disertakan ketika membuat objek DetailScreen. Sesuaikan juga
informasi yang ditampilkan dengan property yang didapat dari
constructor.
class DetailScreen extends StatelessWidget {
final TourismPlace place;
const DetailScreen({Key? key, required this.place}) :
super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset(place.imageAsset),
Container(
margin: const EdgeInsets.only(top: 16.0),
child: Text(
place.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontFamily: 'Staatliches',
),
),
),
Container(
margin: const
EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
children: <Widget>[
const Icon(Icons.calendar_today),
const SizedBox(height: 8.0),
Text(
place.openDays,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
const Icon(Icons.access_time),
const SizedBox(height: 8.0),
Text(
place.openTime,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
const Icon(Icons.monetization_on),
const SizedBox(height: 8.0),
Text(
place.ticketPrice,
style: informationTextStyle,
),
],
),
],
),
),
Container(
padding: const EdgeInsets.all(16.0),
child: Text(
place.description,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16.0,
fontFamily: 'Oxygen',
),
),
),
Container(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: place.imageUrls.map((url) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: Image.network(url),
),
);
}).toList(),
),
),
],
),
),
);
}
}
18. Jangan lupa untuk menambahkan data place pada constructor.
Navigator.push(context, MaterialPageRoute(builder:
(context) {
return DetailScreen(place: place);
}));
19. Selanjutnya, kita akan menambahkan tombol navigasi untuk
kembali ke halaman daftar tempat wisata. Tombol ini akan kita taruh
di atas gambar utama atau gambar dari aset. Kita akan
menggunakan widget Stack. Widget ini digunakan untuk menyusun
widget seperti Column atau Row, bedanya widget pada Stack
disusun secara bertumpuk (stacked). Ubah kode Anda menjadi
seperti berikut:
Stack(
children: <Widget>[
Image.asset(place.imageAsset),
IconButton(icon: const Icon(Icons.arrow_back),
onPressed: () {})
],
),
20. Tambahkan fungsionalitas agar ketika icon back ini diklik akan
kembali ke halaman sebelumnya.
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
21. Jika Anda jalankan aplikasi, ikon ini akan sedikit menabrak
notification bar pada perangkat Android. Hal ini akan semakin terlihat
apabila Anda menggunakan perangkat yang memiliki notch.
Lalu bagaimana mengatasinya? Masih ingat dengan SafeArea? Kita
akan memanfaatkan widget SafeArea yang akan memberikan
padding sesuai dengan sistem operasi yang digunakan sehingga
widget akan berada di area yang aman. Buat widget SafeArea lalu
pindahkan IconButton ke dalamnya.
SafeArea(
child: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
22. Lakukan juga beberapa perubahan tampilan supaya ikon navigasi
tidak bertabarakan dengan latar belakangnya.
SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
backgroundColor: Colors.grey,
child: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
},
),
),
),
),
23. Terakhir, kita akan membuat fitur untuk menambahkan favorit.
Fitur favorit ini memang belum lengkap, namun setidaknya cukup
memberikan Anda gambaran bagaimana mengubah state aplikasi
dan bagaimana widget dapat tampil sesuai dengan state yang ada.
detail_screen.dart
import 'package:flutter/material.dart';
import 'package:wisatabandung/model/tourism_place.dart';
var informationTextStyle = const TextStyle(fontFamily:
'Oxygen');
class DetailScreen extends StatelessWidget {
final TourismPlace place;
const DetailScreen({Key? key, required this.place}) :
super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Stack(
children: <Widget>[
Image.asset(place.imageAsset),
SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
CircleAvatar(
backgroundColor: Colors.grey,
child: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
},
),
),
const FavoriteButton(),
],
),
),
),
],
),
Container(
margin: const EdgeInsets.only(top: 16.0),
child: Text(
place.name,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 30.0,
fontFamily: 'Staatliches',
),
),
),
Container(
margin: const EdgeInsets.symmetric(vertical:
16.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
children: <Widget>[
const Icon(Icons.calendar_today),
const SizedBox(height: 8.0),
Text(
place.openDays,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
const Icon(Icons.access_time),
const SizedBox(height: 8.0),
Text(
place.openTime,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
const Icon(Icons.monetization_on),
const SizedBox(height: 8.0),
Text(
place.ticketPrice,
style: informationTextStyle,
),
],
),
],
),
),
Container(
padding: const EdgeInsets.all(16.0),
child: Text(
place.description,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16.0,
fontFamily: 'Oxygen',
),
),
),
SizedBox(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: place.imageUrls.map((url) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: Image.network(url),
),
);
}).toList(),
),
),
],
),
),
);
}
}
class FavoriteButton extends StatefulWidget {
const FavoriteButton({Key? key}) : super(key: key);
@override
_FavoriteButtonState createState() =>
_FavoriteButtonState();
}
class _FavoriteButtonState extends State<FavoriteButton> {
bool isFavorite = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: Colors.red,
),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
);
}
}
tourism_place.dart
class TourismPlace {
String name;
String location;
String description;
String openDays;
String openTime;
String ticketPrice;
String imageAsset;
List<String> imageUrls;
TourismPlace({
required this.name,
required this.location,
required this.description,
required this.openDays,
required this.openTime,
required this.ticketPrice,
required this.imageAsset,
required this.imageUrls,
});
}
var tourismPlaceList = [
TourismPlace(
name: 'Farm House Lembang',
location: 'Lembang',
description:
'Berada di jalur utama Bandung-Lembang, Farm House
menjadi objek wisata yang tidak pernah sepi pengunjung.
Selain karena letaknya strategis, kawasan ini juga
menghadirkan nuansa wisata khas Eropa. Semua itu diterapkan
dalam bentuk spot swafoto Instagramable.',
openDays: 'Open Everyday',
openTime: '09:00 - 20:00',
ticketPrice: 'Rp 25000',
imageAsset: 'images/farm-house.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/59/70/
farmhouse-lembang.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/22/f6/
photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16/a9/3
3/43/liburan-di-farmhouse.jpg'
],
),
TourismPlace(
name: 'Observatorium Bosscha',
location: 'Lembang',
description:
'Memiliki beberapa teleskop, antara lain, Refraktor
Ganda Zeiss, Schmidt Bimasakti, Refraktor Bamberg, Cassegrain
GOTO, dan Teleskop Surya. Refraktor Ganda Zeiss adalah jenis
teleskop terbesar untuk meneropong bintang. Benda ini
diletakkan pada atap kubah sehingga saat teropong digunakan,
atap tersebut harus dibuka. Observatorium Bosscha boleh
dikunjungi oleh siapa pun, tanpa tiket. Namun, bagi yang
ingin menggunakan teleskop Zeiss, wajib mendaftarkan diri.
Untuk instansi atau lembaga pendidikan, diberikan jadwal hari
Selasa sampai Jumat. Sementara itu, kunjungan individu dibuka
setiap hari Sabtu.',
openDays: 'Open Tuesday - Saturday',
openTime: '09:00 - 14:30',
ticketPrice: 'Rp 20000',
imageAsset: 'images/bosscha.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/12/6b/63/0b/
bosscha-observatory.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0d/6a/88/9b/
photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/3f/04/39/
p-20171111-110220-largejpg.jpg',
],
),
TourismPlace(
name: 'Jalan Asia Afrika',
location: 'Kota Bandung',
description:
'Jalan Asia Afrika di Bandung memiliki kaitan yang
sangat erat dengan pendirian kota Kembang ini. Karena pada
saat itu, Gubernur Jenderal Herman Willem Deaendels dari
Belanda menancapkan tongkatnya saat memerintahkan pendirian
kota ini, yang kemudian diabadikan menjadi tugu Bandung Nol
Kilometer.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/jalan-asia-afrika.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0d/c2/e7/e6/
quotes-kota-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/17/f4/44/01/
jalan-asia-afrika.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/0a/ef/36/e2/
jalan-asia-afrika.jpg',
],
),
TourismPlace(
name: 'Stone Garden',
location: 'Padalarang',
description:
'Stone Garden atau Taman Batu di Padalarang – Bandung
ini adalah nama secara harafiah untuk apa yang akan kita
lihat jika berada di sana. Hamparan batu yang artistik
membuat kita merasa tidak sedang berada di Bandung, apalagi
di Padalarang. Hamparan batu yang dimaksud bukan terhampar
begitu saja di atas tanah luas yang menjadi permukaannya.
Batu-batu besar yang ukuran pastinya bervariasi tersusun
seperti memiliki suatu formasi matematis.',
openDays: 'Open Everyday',
openTime: '06:00 - 17:00',
ticketPrice: 'Rp 3000',
imageAsset: 'images/stone-garden.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/15/01/d7/4b/
p-20180510-153310-01.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/15/68/00/32/
stone-garden-citatah.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0d/a2/cb/05/
stone-garden-citatah.jpg',
],
),
TourismPlace(
name: 'Taman Film Pasopati',
location: 'Kota Bandung',
description:
'Menjadi salah satu tempat wisata di Bandung yang
favorit, tentu Taman Film ini memiliki fasilitas cukup
memadai. Pemberian fasilitas ini memiliki harapan para
pengunjung akan merasa nyaman dan tak segan2 untuk kembali
berkunjung terus menerus kesini. Beberapa fasilitas taman
yang bisa kamu nikmati diantaranya seperti layar videotron
besar berukuran 4×8 untuk memutar berbagai macam pilihan film
seperti Film Indonesia, Bollywood, Korea, ataupun Indie
Bandung.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/taman-film.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/08/8b/87/50/
bandung-movie-park.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/17/67/d5/53/
img-20190505-114509-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/09/73/33/05/
taman-film-pasopati.jpg',
],
),
TourismPlace(
name: 'Museum Geologi',
location: 'Kota Bandung',
description:
'Museum Geologi didirikan pada tanggal 16 Mei 1929.
Museum ini telah direnovasi dengan dana bantuan dari JICA
(Japan International Cooperation Agency). Setelah mengalami
renovasi, Museum Geologi dibuka kembali dan diresmikan oleh
Wakil Presiden RI, Megawati Soekarnoputri pada tanggal 23
Agustus 2000. Sebagai salah satu monumen bersejarah, museum
berada di bawah perlindungan pemerintah dan merupakan
peninggalan nasional. Dalam Museum ini, tersimpan dan
dikelola materi-materi geologi yang berlimpah, seperti fosil,
batuan, mineral. Kesemuanya itu dikumpulkan selama kerja
lapangan di Indonesia sejak 1850.',
openDays: 'Open Saturday - Thursday',
openTime: '09:00 - 15:30',
ticketPrice: 'Rp 3000',
imageAsset: 'images/museum-geologi.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-w/19/1c/8e/f7/
geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/a7/35/b7/
geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/1a/55/e0/dc/
geology-museum.jpg',
],
),
TourismPlace(
name: 'Floating Market',
location: 'Lembang',
description:
'Tempat wisata ini sepertinya memang ditujukan untuk
wisata keluarga di Bandung. Di sini kita bisa menikmati
suasana kawasan yang tertata rapi dan alami. Pada awalnya,
floating market Lembang tidak begitu luas. Tapi sekarang
sudah ekspansi dan memiliki banyak objek menarik baru. Nama
floating market ini sepertinya merujuk pada stand tempat
jualan makanan yang berada dalam perahu.',
openDays: 'Open Everyday',
openTime: '09:00 - 17:00',
ticketPrice: 'Rp 20000',
imageAsset: 'images/floating-market.png',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/17/f9/ff/f8/
floating-market-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/1a/86/d3/cd/
20200103-125059-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/19/ce/b4/9b/
img20181224120857-largejpg.jpg',
],
),
TourismPlace(
name: 'Kawah Putih',
location: 'Ciwidey',
description:
'Kawah Putih adalah tempat wisata di Bandung yang
paling terkenal. Berlokasi di Ciwidey, Jawa Barat, kurang
lebih sekitar 50 KM arah selatan kota Bandung, Kawah Putih
adalah sebuah danau yang terbentuk akibat dari letusan Gunung
Patuha. Sesuai dengan namanya, tanah yang ada di kawasan ini
berwarna putih akibat dari pencampuran unsur belerang.',
openDays: 'Open Everyday',
openTime: '07:00 - 17:00',
ticketPrice: 'Rp 15000',
imageAsset: 'images/kawah-putih.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0b/6e/7c/ce/
rocks-sticking-out-of.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0b/35/30/14/
white-crater.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0a/8b/9a/79/
2945-t00572-www-initempatwisat.jpg',
],
),
TourismPlace(
name: 'Ranca Upas',
location: 'Ciwidey',
description:
'Ranca Upas Ciwidey adalah kawasan bumi perkemahan di
bawah pengelolaan perhutani. Tempat ini berada di kawasan
wisata Bandung Selatan, satu lokasi dengan kawah putih, kolam
Cimanggu dan situ Patenggang. Banyak hal yang bisa dilakukan
di kawasan wisata ini, seperti berkemah, berinteraksi dengan
rusa, sampai bermain di water park dan mandi air panas.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Rp 20000',
imageAsset: 'images/ranca-upas.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/1a/e0/7f/9c/
kampung-cai-ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/2f/87/
ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/27/0a/
ranca-upas.jpg',
],
),
];
● Codelab-3
flutter create .
4. Karena kita telah membuat aplikasi mobile lalu ingin membuat versi
webnya, maka secara tidak langsung kita telah menerapkan
mobile-first design. Sekarang kita perlu menentukan pada ukuran
berapa layout mobile ini sudah tidak sesuai dan perlu diubah
tampilannya. Untuk memudahkan, mari kita tampilkan lebar browser
atau layar ke dalam teks AppBar.
AppBar(
title:
Text('Wisata Bandung. Size:
${MediaQuery.of(context).size.width}'),
),
5. Cobalah mengubah ukuran window browser dan cari breakpoint atau
titik di mana layout sudah tidak sesuai.
Sebagai contoh, pada ukuran lebar di atas 600 sudah banyak ruang
kosong yang lebih baik ditempati oleh widget lain. Maka, kita akan
gunakan ukuran lebar 600 sebagai batas breakpoint ukuran mobile.
6. Pada ukuran lebar di atas 600 kita akan buat tampilan yang berbeda
menggunakan grid. Sebelumnya, mari pindahkan tampilan ListView
menjadi widget tersendiri.
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Text('Wisata Bandung. Size:
${MediaQuery.of(context).size.width}'),
),
body: TourismPlaceList(),
);
}
}
class TourismPlaceList extends StatelessWidget {
const TourismPlaceList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(...);
}
}
7. Kemudian buat widget baru untuk menampilkan GridView.
class TourismPlaceGrid extends StatelessWidget {
const TourismPlaceGrid({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: GridView.count(
crossAxisCount: 4,
children: [],
),
);
}
}
8. Selanjutnya ubah body dari MainScreen untuk menampilkan
TourismPlaceList atau TourismPlaceGrid. Kita akan
memanfaatkan widget LayoutBuilderuntuk mendapatkan ukuran
layar.
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Text('Wisata Bandung. Size:
${MediaQuery.of(context).size.width}'),
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints
constraints) {
if (constraints.maxWidth <= 600) {
return TourismPlaceList();
} else {
return TourismPlaceGrid();
}
},
),
);
}
}
Tampilan aplikasi di atas lebar 600 akan kosong karena kita belum
mendefinisikan item untuk ditampilkan.
9. Tampilan item grid yang akan kita buat akan seperti di bawah ini.
Cobalah untuk membuat tampilan Card seperti ini dulu sebelum
melihat kode pada langkah berikutnya.
15. Selanjutnya mari tambahkan jarak antara item supaya tidak terlalu
rapat.
GridView.count(
crossAxisCount: gridCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: tourismPlaceList.map((place) {
return InkWell(...);
}).toList(),
),
16. Jangan lupa untuk menghapus kembali teks ukuran pada AppBar.
AppBar(
title: Text('Wisata Bandung'),
),
17. Selanjutnya kita akan mulai memodifikasi tampilan halaman detail.
Seperti yang Anda lihat ketika menjalankan aplikasi, halaman detail
tampak kurang estetik untuk ukuran resolusi yang lebar. Untuk
ukuran lebar di atas 800, kita akan membuat tampilannya seperti ini:
20. Mari kita mulai dengan Column pertama. Tambahkan Text untuk
judul, lalu Row untuk struktur layout di bawahnya. Tambahkan jarak
menggunakan widget SizedBox.
Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Wisata Bandung',
style: TextStyle(
fontFamily: 'Staatliches',
fontSize: 32,
),
),
const SizedBox(height: 32),
Row(
children: [],
),
],
),
);
21. Widget Row akan berisi children seperti berikut:
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
children: [],
),
),
const SizedBox(width: 32),
Expanded(
child: Card(...),
),
],
),
Card(
child: Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
child: Text(
place.name,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 30.0,
fontFamily: 'Staatliches',
),
),
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: <Widget>[
const Icon(Icons.calendar_today),
const SizedBox(width: 8.0),
Text(
place.openDays,
style: informationTextStyle,
),
],
),
const FavoriteButton(),
],
),
Row(
children: <Widget>[
const Icon(Icons.access_time),
const SizedBox(width: 8.0),
Text(
place.openTime,
style: informationTextStyle,
),
],
),
const SizedBox(height: 8.0),
Row(
children: <Widget>[
const Icon(Icons.monetization_on),
const SizedBox(width: 8.0),
Text(
place.ticketPrice,
style: informationTextStyle,
),
],
),
Container(
padding: const EdgeInsets.symmetric(vertical:
16.0),
child: Text(
place.description,
textAlign: TextAlign.justify,
style: const TextStyle(
fontSize: 16.0,
fontFamily: 'Oxygen',
),
),
),
],
),
),
),
22. Widget Column terakhir kita gunakan untuk menampilkan gambar
dari aset dan daftar gambar dari internet.
Column(
children: [
ClipRRect(
child: Image.asset(place.imageAsset),
borderRadius: BorderRadius.circular(10),
),
const SizedBox(height: 16),
Container(
height: 150,
padding: const EdgeInsets.only(bottom: 16),
child: ListView(
scrollDirection: Axis.horizontal,
children: place.imageUrls.map((url) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(url),
),
);
}).toList(),
),
),
],
),
Sekarang tampilan aplikasi akan seperti ini:
25. Satu hal yang penting untuk kita perhatikan adalah menghadirkan
pengalaman pengguna yang nyaman saat menggunakan platform
apa pun. Untuk itu, tambahkan Scrollbar pada daftar gambar. Selain
itu, tambahkan juga widget Container untuk memberikan ukuran
tinggi pada ListView.
Scrollbar(
child: Container(
height: 150,
padding: const EdgeInsets.only(bottom: 16),
child: ListView(
scrollDirection: Axis.horizontal,
children: widget.place.imageUrls.map((url) {
return Padding(...);
}).toList(),
),
),
),
26. Jika Anda menggunakan touchpad pada laptop, gestur swipe
pada scrollbar akan berjalan dengan normal. Namun, ketika
melakukan drag menggunakan mouse, fitur scrollbar tidak berjalan.
Solusi yang bisa Anda lakukan adalah menggunakan
ScrollController. Inisialisasikan objek ScrollController di atas method
build().
final _scrollController = ScrollController();
Scrollbar(
controller: _scrollController,
child: Container(
height: 150,
padding: const EdgeInsets.only(bottom: 16),
child: ListView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
children: widget.place.imageUrls.map((url) {
return Padding(...);
}).toList(),
),
),
),
27. Seperti halnya TextEditingController, ScrollController juga harus
di-dispose ketika widget sudah tidak lagi digunakan. Ubahlah
DetailWebPage menjadi StatefulWidget supaya kita bisa memanggil
method dispose.
Kode selengkapnya untuk codelab ini dapat Anda temukan pada tautan:
● Codelab-4
Rangkuman Materi
Selamat! Anda telah belajar banyak di modul ini. Ayo kita ingat dan ulas
kembali apa saja yang sudah kita pelajari.
Deployment
Setelah melalui tahapan pengembangan aplikasi, salah satu tahapan
terakhir yang perlu dilakukan adalah deployment. Tahapan ini dimulai dari
proses build yang membungkus project aplikasi kita menjadi berkas yang
bisa dieksekusi pada masing-masing platform, misalnya format APK
(Android Application Package) atau AAB (Android App Bundle) untuk
Android dan IPA (iOS App Store Package) untuk iOS. Berkas ini yang
nantinya akan didistribusikan ke Google Play Store atau pun App Store.
Jika Anda belum memahami berkas APK atau IPA, Anda dapat
menyamakannya dengan berkas EXE (Executable) pada sistem operasi
Windows.
Cara melakukan build ini terbilang mudah. Akan kami jelaskan pada
pembahasan selanjutnya.
Build APK
Project Flutter yang telah dibuat dapat kita build menjadi berkas .apk yang
dapat berjalan di Android. Build APK adalah suatu proses membungkus
aplikasi flutter menjadi format .apk yang nantinya untuk diinstal pada
perangkat Android. Berikut tahapan ketika build aplikasi Flutter ke APK.
AndroidManifest.xml
Sebelum mem-build APK, kita akan mengatur berkas
android/app/src/main/AndroidManifest.xml. AndroidManifest.xml
merupakan sebuah berkas yang berisikan informasi mengenai aplikasi
Android yang akan di-build. Informasi-informasi tersebut berupa nama
aplikasi, ikon, permission, screen orientation, dan lain-lain. Isi dari
AndroidManifest.xml seperti berikut:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="id.eudeka.samples">
<application
android:name="io.flutter.app.FlutterApplication"
android:label="samples"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|sc
reenSize|smallestScreenSize|locale|layoutDirection|fontScale|
screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawab
le"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action
android:name="android.intent.action.MAIN"/>
<category
android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="Nama Aplikasi"
android:icon="@mipmap/ic_launcher">
Isikan android:label dengan nama aplikasi yang diinginkan. Atau Anda bisa
gunakan library berikut untuk menghasilkan nama aplikasi dari
pubspec.yaml.
Hal yang pertama kita lakukan adalah membuat ikon aplikasi dengan
Android Asset Studio.
Dengan Android Asset Studio, kita dapat membuat ikon aplikasi dengan
mudah dan nantinya akan terbuat dalam berbagai resolusi (mipmap).
Setelah membuat ikon sesuai dengan keinginan, tekan tombol download
yang ada di kanan atas.
<uses-permission android:name="android.permission.INTERNET"/>
Melakukan Build APK
Setelah kita mengatur nama dan ikon aplikasi, langkah selanjutnya adalah
melakukan build aplikasi menjadi APK. Sebelumnya terdapat tiga (3) jenis
mode aplikasi yang perlu diketahui, yaitu debug, profile, dan release. APK
debug umumnya digunakan untuk pengujian dan penggunaan aplikasi
secara internal. Mode debug digunakan secara default ketika menjalankan
aplikasi menggunakan perintah flutter run. Sementara untuk bisa dirilis
melalui Google Play Store, Anda perlu membuat APK release. Sedangkan
mode profile sama hal nya dengan release hanya saja tetap dapat
di-debug menggunakan tools seperti DevTools dan tidak dapat dijalankan
di emulator atau simulator.
Pada kelas ini kita akan mempelajari bagaimana membuat APK debug.
Caranya ialah menggunakan terminal pada Android Studio. Tekan tombol
Terminal yang ada pada pojok kiri bawah.
Bila menggunakan Visual Studio Code pilih menu terminal yang ada pada
menu kiri atas. Lalu pilih new terminal.
Jika terminal telah muncul, tuliskan perintah berikut:
Build IPA
Catatan: Build .IPA hanya bisa dijalankan dengan mendaftar akun Apple
Developer Program. Silakan baca informasi tentang Apple Developer
Program di sini https://developer.apple.com/programs/.
Untuk lulus dari kelas ini, tidak diwajibkan untuk bisa build .IPA.
Pada materi ini kita akan mempelajari bagaimana melakukan build aplikasi
Flutter menjadi berkas .ipa yang dapat dijalankan pada perangkat iOS.
Sebelum melakukan build, ada beberapa hal yang perlu kita atur seperti
nama dan ikon aplikasi.
<key>CFBundleName</key>
<string>builder</string>
Atau Anda bisa gunakan library berikut untuk men-generate nama aplikasi
melalui pubspec.yaml.
Selamat Anda telah berhasil membuat dan build project Flutter di Android
atau iOS. Selanjutnya Anda diharuskan untuk mengirim submission project
Flutter. Silakan lanjut ke materi berikutnya untuk menuju halaman
submission.
Web Deployment
Pada materi ini kita akan mempelajari bagaimana melakukan build aplikasi
flutter untuk platform web. Sama seperti platform android dan ios, informasi
untuk pengaturan aplikasi berada pada folder web.
{
"name": "wisata_bandung",
"short_name": "wisata_bandung"
}
{
...
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Hasil build akan Anda temukan pada folder /build/web. Folder inilah yang
nantinya bisa Anda deploy ke sebuah web hosting atau web server.
● Firebase Hosting
● GitHub Pages
● Google Cloud Hosting
Rangkuman Materi
Kita telah mempelajari praktik deployment Flutter untuk beberapa platform.
Mari kita ulas kembali apa saja yang sudah kita pelajari.
Selamat! Anda telah menyelesaikan modul terakhir pada kelas ini. Anda
telah belajar banyak mulai dari proses instalasi hingga ke tahap
deployment aplikasi. Untuk bisa dinyatakan lulus dari kelas ini, Anda perlu
mengirimkan submission berupa aplikasi Flutter dengan tema bebas.
Manfaatkan materi yang sudah Anda pelajari dan kirimkan aplikasi terbaik
Anda ya! Good luck!