1. Pengantar
Flutter adalah toolkit UI Google untuk membangun aplikasi untuk perangkat seluler, web, dan desktop dari codebase tunggal. Dalam codelab ini, Anda akan membangun aplikasi Flutter berikut:
Aplikasi ini menghasilkan nama yang terkesan keren, seperti "newstay", "lightstream", "mainbrake", atau "graypine". Pengguna dapat meminta nama berikutnya, memfavoritkan nama saat ini, dan meninjau daftar nama yang difavoritkan di halaman terpisah. Aplikasi ini responsif terhadap berbagai ukuran layar.
Yang akan Anda pelajari
- Dasar-dasar cara kerja Flutter
- Membuat tata letak di Flutter
- Menghubungkan interaksi pengguna (seperti penekanan tombol) dengan perilaku aplikasi
- Menjaga kode Flutter Anda tetap tertata
- Membuat aplikasi Anda menjadi responsif (untuk berbagai layar)
- Mencapai tampilan & nuansa yang konsisten untuk aplikasi Anda
Anda akan memulai dengan scaffold dasar agar Anda dapat langsung memasuki bagian-bagian yang menarik.
Inilah Filip yang akan memandumu menelusuri seluruh codelab!
Klik berikutnya untuk memulai lab.
2. Menyiapkan lingkungan Flutter Anda
Editor
Untuk menjadikan codelab ini sesederhana mungkin, kami berasumsi Anda akan menggunakan Visual Studio Code (VS Code) sebagai lingkungan pengembangan Anda. VS Code ini gratis dan dapat digunakan di semua platform utama.
Tentu saja Anda boleh menggunakan editor apa pun yang Anda suka: Android Studio, Intellij IDE lainnya, Emacs, Vim, atau Notepad++. Semua editor tersebut dapat digunakan dengan Flutter.
Sebaiknya Anda menggunakan VS Code untuk codelab ini karena instruksi ini menggunakan default untuk pintasan khusus VS Code. Lebih mudah untuk mengatakan "klik di sini" atau "tekan tombol ini" daripada mengatakan "lakukan tindakan yang sesuai pada editor Anda untuk melakukan X".
Memilih target pengembangan
Flutter adalah toolkit multi-platform. Aplikasi Anda dapat berjalan pada setiap sistem operasi berikut:
- iOS
- Android
- Windows
- macOS
- Linux
- web
Namun, memilih sistem operasi tunggal yang utamanya akan Anda kembangkan adalah praktik yang umum. Sistem operasi ini adalah "target pengembangan" Anda—tempat aplikasi Anda berjalan selama pengembangan.
Misalnya, anggaplah Anda menggunakan laptop Windows untuk mengembangkan aplikasi Flutter. Jika Anda memilih Android sebagai target pengembangan, Anda biasanya memasang perangkat Android ke laptop Windows menggunakan kabel USB, dan aplikasi yang Anda kembangkan berjalan di perangkat Android yang terpasang. Namun, Anda juga dapat memilih Windows sebagai target pengembangan, artinya aplikasi yang Anda kembangkan berjalan sebagai aplikasi Windows bersama editor Anda.
Anda mungkin berpikir untuk memilih web sebagai target pengembangan Anda. Kelemahan dari pilihan ini adalah Anda kehilangan salah satu fitur pengembangan Flutter yang paling berguna: Stateful Hot Reload. Flutter tidak dapat melakukan hot-reload pada aplikasi web.
Tentukan pilihan Anda sekarang. Ingat: Anda dapat menjalankan aplikasi Anda pada sistem operasi lainnya kapan saja setelahnya. Hanya saja memiliki target pengembangan yang jelas mempermudah langkah berikutnya.
Menginstal Flutter
Instruksi terkini mengenai cara menginstal Flutter SDK selalu dapat ditemukan di docs.flutter.dev.
Instruksi di situs Flutter tidak hanya membahas penginstalan SDK itu sendiri, tetapi juga alat terkait target pengembangan dan plugin editor. Harap diingat bahwa untuk codelab ini, Anda hanya perlu menginstal program-program berikut:
- Flutter SDK
- Visual Studio Code dengan plugin Flutter
- Software yang diperlukan oleh target pengembangan pilihan Anda (misalnya: Visual Studio untuk target Windows, atau Xcode untuk target macOS)
Di bagian berikutnya, Anda akan membuat proyek Flutter pertama Anda.
Jika Anda mengalami masalah sejauh ini, Anda mungkin merasa beberapa pertanyaan dan jawaban ini (dari StackOverflow) berguna untuk pemecahan masalah.
Pertanyaan Umum (FAQ)
- Bagaimana cara saya menemukan jalur Flutter SDK?
- Apa yang harus saya lakukan saat perintah Flutter tidak ditemukan?
- Bagaimana cara memperbaiki masalah "Menunggu perintah flutter lainnya untuk melepaskan kunci startup"?
- Bagaimana cara memberi tahu Flutter letak penginstalan Android SDK berada?
- Bagaimana cara menangani kesalahan Java saat menjalankan
flutter doctor --android-licenses
? - Bagaimana cara menangani masalah alat
sdkmanager
Android tidak ditemukan? - Bagaimana cara menangani kesalahan "komponen
cmdline-tools
tidak ditemukan"? - Bagaimana cara menjalankan CocoaPods pada Apple Silicon (M1)?
- Bagaimana cara menonaktifkan pemformatan otomatis pada file yang disimpan di VS Code?
3. Membuat proyek
Membuat proyek Flutter pertama Anda
Luncurkan Visual Studio Code dan buka palet perintah (dengan F1
atau Ctrl+Shift+P
atau Shift+Cmd+P
). Ketik "flutter new". Pilih perintah Flutter: New Project.
Berikutnya, pilih Application lalu folder tempat proyek akan dibuat. Folder ini dapat berupa direktori utama Anda, atau direktori seperti C:\src\
.
Terakhir, beri nama proyek Anda. Beri nama seperti namer_app
atau my_awesome_namer
.
Flutter kini membuat folder proyek Anda dan VS Code membuka folder tersebut.
Anda sekarang akan menimpa isi 3 file dengan scaffold dasar aplikasi.
Menyalin & Menempelkan aplikasi awal
Pada panel sebelah kiri VS Code, pastikan bahwa Penjelajah dipilih lalu buka file pubspec.yaml
.
Ganti konten file ini dengan kode berikut:
pubspec.yaml
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: '>=2.19.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
File pubspec.yaml
menentukan informasi dasar tentang aplikasi Anda, seperti versi aplikasi saat ini, dependensi aplikasi, dan aset yang digunakan oleh aplikasi untuk pengiriman.
Berikutnya, buka file konfigurasi lainnya dalam proyek tersebut, analysis_options.yaml
.
Ganti konten file tersebut dengan kode berikut:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: false
prefer_final_fields: false
use_key_in_widget_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_const_constructors_in_immutables: false
avoid_print: false
File ini menentukan seberapa ketat Flutter saat menganalisis kode Anda. Karena percobaan ini adalah percobaan pertama Anda menggunakan Flutter, Anda memberi tahu penganalisis agar tidak terlalu ketat. Anda dapat mengatur ini kapan saja. Bahkan, seiring mendekati pemublikasian aplikasi produksi sebenarnya, Anda kemungkinan besar akan ingin membuat penganalisis lebih ketat dari ini.
Terakhir, buka file main.dart
pada direktori lib/
.
Ganti konten file ini dengan kode berikut:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
50 baris kode ini adalah keseluruhan aplikasi sejauh ini.
Pada bagian berikutnya, jalankan aplikasi dalam mode debug dan mulai kembangkan aplikasi Anda.
4. Menambahkan tombol
Langkah ini menambahkan tombol Next untuk menghasilkan pasangan kata baru.
Meluncurkan aplikasi
Pertama, buka lib/main.dart
dan pastikan Anda memilih perangkat target. Di bagian pojok kanan bawah VS Code, Anda akan menemukan tombol yang menampilkan perangkat target saat ini. Klik tombol untuk mengubahnya.
Selagi lib/main.dart
terbuka, temukan tombol "play" di pojok kanan atas jendela VS Code lalu klik tombol tersebut.
Setelah beberapa saat, aplikasi Anda diluncurkan dalam mode debug. Tampilannya masih terlihat biasa saja:
Hot Reload Pertama
Di bagian bawah lib/main.dart
, tambahkan sesuatu pada string di objek Text
pertama, dan simpan file tersebut (dengan Ctrl+S
atau Cmd+S
). Misalnya:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Perhatikan bagaimana aplikasi segera berubah tetapi kata yang acak tetap sama. Situasi ini menunjukkan fitur stateful Hot Reload Flutter terkenal yang sedang bekerja. Hot reload dipicu saat Anda menyimpan perubahan untuk file sumber.
Pertanyaan Umum (FAQ)
- Bagaimana jika Hot Reload tidak berfungsi pada VSCode?
- Apakah saya harus menekan ‘r' untuk hot reload di VSCode?
- Apakah Hot Reload berfungsi untuk web?
- Bagaimana cara menghapus banner "Debug"?
Menambahkan tombol
Berikutnya, tambahkan tombol di bagian bawah Column
, tepat di bawah instance Text
kedua.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
Saat Anda menyimpan perubahan, aplikasi diperbarui kembali: Sebuah tombol muncul dan, saat Anda mengklik tombol tersebut, Konsol Debug di VS Code menampilkan pesan button pressed!.
Kursus singkat Flutter 5 menit
Meskipun menyenangkan melihat Konsol Debug, Anda ingin tombol tersebut melakukan sesuatu yang lebih berguna. Namun, sebelum mencapai ke sana, perhatikan kode pada lib/main.dart
lebih dekat, untuk memahami cara kerjanya.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
Di bagian paling atas file tersebut, Anda akan menemukan fungsi main()
. Dalam wujudnya saat ini, fungsi ini hanya memberi tahu Flutter untuk menjalankan aplikasi yang ditentukan pada MyApp
.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
Class MyApp
memperluas StatelessWidget
. Widget adalah elemen tempat Anda membangun setiap aplikasi Flutter. Seperti yang dapat Anda lihat, bahkan aplikasi itu sendiri adalah widget.
Kode pada MyApp
menyusun keseluruhan aplikasi. Kode ini membuat status seluruh aplikasi (Anda akan mempelajari hal ini lebih lanjut nanti), memberi nama aplikasi, menentukan tema visual, dan mengatur widget "home"—titik awal aplikasi Anda.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Berikutnya, class MyAppState
menentukan...yah...status aplikasi. Percobaan ini adalah percobaan pertama Anda menggunakan Flutter, jadi codelab ini akan menjaga status aplikasi tetap sederhana dan terpusat. Ada banyak cara ampuh untuk mengelola status aplikasi di Flutter. Salah satu yang paling mudah untuk dijelaskan adalah ChangeNotifier
, pendekatan yang diambil oleh aplikasi ini.
MyAppState
menjelaskan data yang diperlukan oleh aplikasi ini agar berjalan dengan baik. Saat ini, kode ini hanya berisi variabel tunggal dengan pasangan kata acak saat ini. Anda akan menambahkannya nanti.- Class status memperluas
ChangeNotifier
, yang artinya kode ini dapat memberi tahu kode lain tentang perubahannya sendiri. Misalnya, jika pasangan kata saat ini berubah, beberapa widget dalam aplikasi perlu mengetahuinya. - Status dibuat dan disediakan untuk seluruh aplikasi menggunakan
ChangeNotifierProvider
(lihat kode di atas padaMyApp
). Hal ini memungkinkan widget mana pun pada aplikasi untuk mendapatkan status.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
Terakhir, ada MyHomePage
, widget yang telah Anda modifikasi. Setiap baris bernomor di bawah memetakan ke komentar nomor baris pada kode di atas:
- Setiap widget menentukan metode
build()
yang dipanggil secara otomatis setiap kali kondisi widget berubah agar widget selalu dalam kondisi terbaru. MyHomePage
melacak perubahan terhadap status aplikasi saat ini menggunakan metodewatch
.- Setiap metode
build
harus menampilkan widget atau (yang lebih umum) pohon widget bertingkat. Dalam hal ini, widget tingkat tertinggi adalahScaffold
. Anda tidak akan bekerja denganScaffold
dalam codelab ini, tetapi ini adalah widget yang berguna dan dapat ditemukan di sebagian besar aplikasi Flutter di dunia nyata. Column
adalah salah satu widget tata letak paling dasar pada Flutter. Widget tata letak ini mengambil sejumlah turunan dan menempatkannya pada kolom dari atas ke bawah. Secara default, kolom menempatkan turunan-turunannya secara visual di bagian atas. Anda akan segera mengubah ini agar kolom terpusat di tengah.- Anda mengubah widget
Text
ini pada langkah pertama. - Widget
Text
kedua ini mengambilappState
, dan mengakses satu-satunya anggota dari class tersebut,current
(yang merupakanWordPair
).WordPair
menyediakan beberapa pengambil yang berguna, sepertiasPascalCase
atauasSnakeCase
. Di sini, kita menggunakanasLowerCase
, tetapi Anda dapat mengubah ini sekarang jika Anda lebih menyukai salah satu alternatif yang ada. - Perhatikan bagaimana kode Flutter banyak menggunakan koma di akhir. Koma ini tidak harus ada, karena
children
adalah anggota terakhir (dan juga satu-satunya) dari daftar parameterColumn
ini. Namun, menggunakan koma di akhir umumnya adalah ide yang bagus: koma di akhir membuat penambahan anggota menjadi mudah, dan koma di akhir juga berfungsi sebagai petunjuk bagi pemformat otomatis Dart untuk meletakkan baris baru. Untuk informasi lebih lanjut, lihat panduan Pemformatan kode.
Berikutnya, Anda akan menghubungkan tombol dengan status.
Perilaku pertama Anda
Scroll ke MyAppState
lalu tambahkan metode getNext
.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
Metode getNext()
baru menetapkan ulang current
dengan WordPair
acak baru. Metode ini juga memanggil notifyListeners()
(metode ChangeNotifier)
yang memastikan bahwa semua orang yang melihat MyAppState
diberi tahu.
Tindakan terakhir adalah memanggil metode getNext
dari callback tombol tersebut.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Simpan dan uji coba aplikasi sekarang. Aplikasi akan menghasilkan pasangan kata acak baru setiap kali Anda menekan tombol Next.
Pada bagian berikutnya, Anda akan memperindah tampilan antarmuka pengguna.
5. Memperindah tampilan aplikasi
Gambar berikut menunjukkan tampilan aplikasi saat ini.
Tidak bagus. Bagian utama aplikasi—pasangan kata yang dihasilkan secara acak—harus lebih terlihat. Lagipula, bagian ini adalah alasan utama pengguna kita menggunakan aplikasi ini! Selain itu, konten aplikasi terlihat melenceng sekaligus aneh, dan tampilan hitam & putih aplikasi terkesan membosankan.
Bagian ini membahas masalah ini melalui pengerjaan desain aplikasi. Tujuan akhir bagian ini adalah sesuatu seperti berikut:
Mengekstrak widget
Baris yang bertanggung jawab untuk menampilkan pasangan kata saat ini kini tampak seperti berikut: Text(appState.current.asLowerCase)
. Untuk mengubahnya menjadi sesuatu yang lebih kompleks, disarankan untuk mengekstrak baris ini ke widget terpisah. Memiliki beberapa widget untuk beberapa bagian logis dari UI Anda adalah cara penting dalam mengelola kompleksitas pada Flutter.
Flutter menyediakan pembantu pemfaktoran ulang untuk mengekstrak widget, tetapi sebelum Anda menggunakannya, pastikan bahwa baris yang akan diekstrak hanya mengakses yang diperlukan. Sekarang baris tersebut mengakses appState
, tetapi sebenarnya baris tersebut hanya perlu mengetahui apa pasangan kata saat ini.
Oleh karena itu, tulis ulang widget MyHomePage
sebagai berikut:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Bagus. Widget Text
tidak lagi merujuk kepada keseluruhan appState
.
Sekarang, panggil menu Refactor. Pada VS Code, Anda melakukan ini melalui salah satu dari dua cara:
- Klik kanan potongan kode yang ingin Anda faktorkan ulang (dalam hal ini
Text
) dan pilih Refactor... dari menu drop-down,
ATAU
- Pindahkan kursor Anda ke potongan kode yang ingin Anda faktorkan ulang (dalam hal ini,
Text
), lalu tekanCtrl+.
(Win/Linux) atauCmd+.
(Mac).
Pada menu Refactor, pilih Extract Widget. Tetapkan nama, seperti BigCard, lalu klik Enter
.
Tindakan ini secara otomatis membuat class baru, BigCard
, di akhir file saat ini. Class tersebut akan terlihat seperti berikut:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
Perhatikan bagaimana aplikasi tetap berjalan meskipun pemfaktoran ulang sedang berlangsung.
Menambahkan Kartu
Sekarang saatnya membuat widget baru ini menjadi UI tebal yang kita bayangkan di awal bagian ini.
Temukan class BigCard
dan metode build()
yang berada di dalamnya. Sama seperti sebelumnya, panggil menu Refactor pada widget Text
. Namun, kali ini Anda tidak akan mengekstrak widget.
Sebagai gantinya, pilih Wrap with Padding. Tindakan ini menciptakan widget induk baru di sekitar widget Text
bernama Padding
. Setelah menyimpannya, Anda akan melihat bahwa kata acak tersebut telah memiliki ruang yang lebih luas.
Tingkatkan padding dari nilai default 8.0
. Misalnya, gunakan 20
untuk padding yang lebih luas.
Berikutnya, mari kita naik satu tingkat lebih tinggi. Tempatkan kursor Anda pada widget Padding
, buka menu Refactor, lalu pilih Wrap with widget....
Tindakan ini memungkinkan Anda untuk menentukan widget induk. Ketik "Card" dan tekan Enter.
Kode ini menggabungkan widget Padding
, dan juga Text
, dengan widget Card
.
Tema dan gaya
Untuk membuat kartu menjadi lebih menarik, beri warna yang lebih kaya pada kartu tersebut. Karena ada baiknya untuk menjaga skema warna yang konsisten, gunakan Theme
aplikasi untuk memilih warna.
Buat perubahan berikut untuk metode build()
BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
Kedua baris baru ini melakukan banyak hal:
- Pertama, kode ini meminta tema aplikasi saat ini dengan
Theme.of(context)
. - Kemudian, kode ini menentukan warna kartu agar sama dengan properti
colorScheme
dari tema. Skema warna menampung banyak warna, danprimary
adalah warna aplikasi yang paling terlihat dan mencolok.
Kini, kartu telah diwarnai dengan warna primer aplikasi:
Anda dapat mengubah warna ini serta skema warna keseluruhan aplikasi dengan men-scroll ke atas ke MyApp
dan mengubah warna seed untuk ColorScheme
di sana.
Perhatikan bagaimana warna berubah dengan halus. Perubahan ini disebut animasi implisit. Banyak widget Flutter akan berinterpolasi antarnilai dengan lancar agar UI tidak hanya "berpindah" antarstatus.
Tombol timbul di bawah kartu juga berubah warna. Itulah kelebihan dalam menggunakan Theme
seluruh aplikasi dibandingkan dengan nilai hard-code.
TextTheme
Kartu tersebut masih memiliki masalah: ukuran teks terlalu kecil dan warnanya membuat teks sulit dibaca. Untuk memperbaiki masalah ini, buat perubahan berikut pada metode build()
BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
Yang ada di balik perubahan ini:
- Dengan menggunakan
theme.textTheme,
, Anda mengakses tema font aplikasi. Class ini mencakup anggota sepertibodyMedium
(untuk teks standar ukuran medium),caption
(untuk teks dari gambar), atauheadlineLarge
(untuk judul berukuran besar). - Properti
displayMedium
adalah gaya font besar yang dimaksudkan untuk teks tampilan. Kata tampilan digunakan dalam artian tipografi di sini, seperti pada jenis huruf tampilan. Dokumentasi untukdisplayMedium
menyatakan bahwa, "gaya tampilan ditujukan untuk teks yang penting dan singkat"—tepat dengan kasus penggunaan kita. - Properti
displayMedium
tema secara teori dapat berupanull
. Dart, bahasa pemrograman yang Anda gunakan untuk menulis aplikasi ini, aman dari null, sehingga bahasa pemrograman ini tidak akan mengizinkan Anda memanggil metode objek yang berpotensinull
. Namun, dalam hal ini, Anda dapat menggunakan operator!
("bang operator") untuk meyakinkan Dart bahwa Anda memahami tindakan Anda. (displayMedium
pasti tidak null dalam kasus ini. Namun, alasan kami mengetahui hal ini berada di luar cakupan codelab ini.) - Memanggil
copyWith()
padadisplayMedium
menampilkan salinan gaya teks dengan perubahan yang Anda tentukan. Dalam hal ini, Anda hanya mengubah warna teks. - Untuk mendapatkan warna baru, Anda mengakses tema aplikasi sekali lagi. Properti
onPrimary
skema warna menentukan warna yang cocok digunakan untuk warna primer aplikasi.
Kini, aplikasi akan terlihat seperti berikut:
Jika Anda mau, ubah kartu lebih jauh. Berikut beberapa idenya:
copyWith()
memungkinkan Anda mengubah lebih banyak tentang gaya teks daripada hanya warna. Untuk mendapatkan daftar lengkap properti yang dapat Anda ubah, letakkan kursor di dalam tanda kurungcopyWith()
, lalu tekanCtrl+Shift+Space
(Win/Linux) atauCmd+Shift+Space
(Mac).- Anda juga dapat mengubah lebih banyak tentang widget
Card
. Misalnya, Anda dapat memperbesar bayangan kartu dengan meningkatkan nilai parameterelevation
. - Coba bereksperimen dengan warna. Selain
theme.colorScheme.primary
, ada juga.secondary
,.surface
, dan berbagai pilihan lainnya. Semua warna ini memilikionPrimary
padanannya masing-masing.
Meningkatkan aksesibilitas
Flutter membuat aplikasi dapat diakses secara default. Misalnya, setiap aplikasi Flutter menampilkan semua teks dan elemen interaktif di aplikasi dengan tepat untuk pembaca layar, seperti TalkBack dan VoiceOver.
Namun, terkadang diperlukan pengerjaan tambahan. Dalam kasus aplikasi ini, pembaca layar mungkin mengalami masalah dalam melafalkan beberapa pasangan kata yang dihasilkan. Meskipun manusia tidak mengalami masalah dalam mengidentifikasi kedua kata pada pasangan kata cheaphead, pembaca layar dapat melafalkan ph di tengah kata sebagai f.
Solusi sederhananya adalah dengan mengganti pair.asLowerCase
dengan "${pair.first} ${pair.second}"
. Kode kedua menggunakan jenis interpolasi string untuk membuat string (seperti "cheap head"
) dari kedua kata yang tercakup dalam pair
. Menggunakan dua kata terpisah sebagai ganti kata majemuk memastikan bahwa pembaca layar mengidentifikasi setiap kata dengan tepat, dan menyediakan pengalaman yang lebih baik untuk pengguna penyandang gangguan penglihatan.
Namun, Anda mungkin ingin mempertahankan kesederhanaan visual pair.asLowerCase
. Gunakan properti semanticsLabel
Text
untuk mengganti konten visual widget teks dengan konten semantik yang lebih sesuai untuk pembaca layar:
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
Kini, pembaca layar melafalkan setiap pasangan kata yang dihasilkan dengan tepat, namun UI tidak berubah. Coba praktikkan ini dengan menggunakan pembaca layar di perangkat Anda.
Menempatkan UI di tengah
Setelah pasangan kata acak dihadirkan dengan gaya visual yang cukup, saatnya menempatkan UI di tengah jendela/layar aplikasi.
Pertama, ingatlah bahwa BigCard
adalah bagian dari Column
. Secara default, kolom menggabungkan turunan kolom di bagian atas, tetapi kita dapat mengganti ini dengan mudah. Buka metode build()
MyHomePage
, dan buat perubahan berikut:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Tindakan ini menempatkan turunan dalam Column
di tengah pada sumbu utamanya (vertikal).
Turunan UI telah ditempatkan di tengah pada sumbu silang kolom (dengan kata lain, turunan UI telah ditempatkan di tengah secara horizontal). Namun, Column
itu sendiri tidak ditempatkan di tengah dalam Scaffold
. Kita dapat memverifikasi ini menggunakan Widget Inspector.
Widget Inspector itu sendiri berada di luar cakupan codelab ini, tetapi Anda dapat melihat bahwa ketika Column
ditandai, kode ini tidak menghabiskan keseluruhan lebar aplikasi. Kode ini hanya menghabiskan ruang horizontal sebanyak yang diperlukan oleh turunan UI.
Anda dapat menempatkan kolom itu sendiri di tengah. Letakkan kursor Anda di Column
, buka menu Refactor (dengan Ctrl+.
atau Cmd+.
), lalu pilih Wrap with Center.
Kini, aplikasi akan terlihat seperti berikut:
Jika mau, Anda dapat menyesuaikan tampilan ini lebih lanjut.
- Anda dapat menghapus widget
Text
di atasBigCard
. Dapat dipastikan bahwa teks deskriptif ("Ide LUAR BIASA acak:") tidak lagi diperlukan karena UI tersebut sudah jelas meskipun tanpa teks deskriptif. Selain itu, dengan begitu UI terlihat lebih bersih. - Anda juga dapat menambahkan widget
SizedBox(height: 10)
di antaraBigCard
danElevatedButton
. Dengan begitu, ada sedikit pemisah di antara kedua widget tersebut. WidgetSizedBox
hanya mengambil ruang dan tidak merender apa pun dengan sendirinya. Widget ini biasa digunakan untuk membuat "jarak" visual.
Dengan perubahan opsional, MyHomePage
mencakup kode berikut:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
Aplikasinya akan terlihat seperti berikut:
Pada bagian berikutnya, Anda akan menambahkan kemampuan untuk memfavoritkan (atau ‘menyukai') kata-kata yang dihasilkan.
6. Menambahkan fungsi
Aplikasi berjalan dengan baik, dan bahkan terkadang menyediakan pasangan kata yang menarik. Namun, setiap kali pengguna mengklik Next, setiap pasangan kata hilang selamanya. Akan lebih baik untuk memiliki cara "mengingat" saran terbaik: seperti tombol 'Like'.
Menambahkan logika bisnis
Scroll ke MyAppState
dan tambahkan kode berikut:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
Periksa perubahannya:
- Anda menambahkan properti baru pada
MyAppState
yang bernamafavorites
. Properti ini diinisialisasi dengan daftar kosong:[]
. - Anda juga menentukan bahwa daftar tersebut hanya dapat berisi pasangan kata:
<WordPair>[]
, menggunakan generik. Hal ini membantu membuat aplikasi Anda menjadi lebih lengkap—Dart bahkan menolak menjalankan aplikasi jika Anda mencoba menambahkan apa pun selainWordPair
. Oleh karena itu, Anda dapat menggunakan daftarfavorites
karena tidak boleh ada objek yang tidak diinginkan (sepertinull
) yang bersembunyi di dalamnya.
- Anda juga menambahkan metode baru,
toggleFavorite()
, yang menghapus pasangan kata saat ini dari daftar favorit (jika sudah ada), atau menambahkannya (jika belum ada). Dalam kedua kasus tersebut, kode memanggilnotifyListeners();
setelahnya.
Menambahkan tombol
Dengan terselesaikannya "logika bisnis", saatnya untuk mengerjakan antarmuka pengguna kembali. Meletakkan tombol ‘Like' di sebelah kiri tombol ‘Next' memerlukan Row
. Widget Row
adalah padanan horizontal dari Column
, yang telah Anda lihat sebelumnya.
Pertama, gabungkan tombol yang ada pada Row
. Buka metode build()
MyHomePage
, letakkan kursor pada ElevatedButton
, buka menu Refactor dengan Ctrl+.
atau Cmd+.
, lalu pilih Wrap with Row.
Saat menyimpan, Anda akan menyadari bahwa Row
bertindak mirip dengan Column
—secara default, kode ini mengumpulkan turunannya ke sebelah kiri. (Column
mengumpulkan turunannya ke atas.) Untuk memperbaiki masalah ini, Anda dapat menggunakan pendekatan yang sama seperti sebelumnya, tetapi dengan mainAxisAlignment
. Namun, untuk tujuan mendidik (pembelajaran), gunakan mainAxisSize
. Kode ini memberi tahu Row
agar tidak mengambil semua ruang horizontal yang tersedia.
Buat perubahan berikut:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
UI kembali ke tempat sebelumnya.
Berikutnya, tambahkan tombol Like dan hubungkan ke toggleFavorite()
. Sebagai tantangan, coba lakukan sendiri untuk pertama kali, tanpa melihat blok kode di bawah.
Tidak apa-apa jika Anda tidak melakukannya dengan cara yang sama seperti yang ditunjukkan di bawah. Bahkan, jangan pikirkan tentang ikon hati kecuali Anda benar-benar menginginkan tantangan yang besar.
Tidak apa-apa jika Anda gagal—lagipula, ini pertama kalinya Anda menggunakan Flutter.
Berikut satu cara untuk menambahkan tombol kedua untuk MyHomePage
. Kali ini, gunakan konstruktor ElevatedButton.icon()
untuk membuat tombol dengan ikon. Di bagian atas metode build
, pilih ikon yang sesuai tergantung pada apakah pasangan kata saat ini sudah berada di favorit atau tidak. Selain itu, perhatikan penggunaan SizedBox
lagi, untuk menjaga jarak antara kedua tombol.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
Aplikasi akan terlihat seperti berikut:
Sayangnya, pengguna tidak dapat melihat favorit. Saatnya menambahkan layar yang sepenuhnya terpisah untuk aplikasi kita. Sampai jumpa di bagian berikutnya!
7. Menambahkan kolom samping navigasi
Sebagian besar aplikasi tidak dapat memuat semuanya ke dalam satu layar. Aplikasi ini mungkin dapat melakukannya, tetapi untuk tujuan pembelajaran, Anda akan membuat layar terpisah untuk bagian favorit pengguna. Untuk beralih di antara dua layar, Anda akan menerapkan StatefulWidget
pertama Anda.
Untuk mencapai inti dari langkah ini secepat mungkin, pisahkan MyHomePage
menjadi 2 widget terpisah.
Pilih keseluruhan MyHomePage
, hapus, dan gantikan dengan kode berikut:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
Saat disimpan, Anda akan melihat sisi visual UI telah siap—tetapi tidak bekerja. Mengklik ♥︎ (hati) pada kolom samping navigasi tidak melakukan apa pun.
Periksa perubahannya.
- Pertama, perhatikan bahwa seluruh konten
MyHomePage
diekstrak ke dalam widget baru,GeneratorPage
. Satu-satunya bagian dari widgetMyHomePage
lama yang tidak diekstrak adalahScaffold
. MyHomePage
baru berisiRow
dengan dua turunan. Widget pertama adalahSafeArea
, dan yang kedua adalah widgetExpanded
.SafeArea
memastikan bahwa turunannya tidak terhalang oleh notch hardware atau status bar. Dalam aplikasi ini, widget mengemasNavigationRail
untuk mencegah tombol navigasi terhalang oleh status bar perangkat seluler, misalnya.- Anda dapat mengubah baris
extended: false
pada NavigationRail menjaditrue
. Kode ini menampilkan label di samping ikon. Pada langkah mendatang, Anda akan mempelajari cara melakukan ini secara otomatis saat aplikasi memiliki ruang horizontal yang cukup. - Kolom samping navigasi memiliki dua tujuan (Beranda dan Favorit), dengan ikon dan label masing-masing. Kolom samping navigasi juga menentukan
selectedIndex
saat ini. Indeks pilihan nol memilih tujuan pertama, indeks pilihan satu memilih tujuan kedua, dan seterusnya. Untuk saat ini, kolom samping navigasi di-hard code ke nol. - Kolom samping navigasi juga menentukan apa yang terjadi saat pengguna memilih salah satu tujuan dengan
onDestinationSelected
. Saat ini, aplikasi hanya menghasilkan nilai indeks yang diminta denganprint()
. - Turunan kedua
Row
adalah widgetExpanded
. Widget yang diperluas sangat berguna dalam baris dan kolom—widget tersebut memungkinkan Anda mengekspresikan tata letak tempat beberapa turunan hanya mengambil ruang sebanyak yang diperlukan (dalam hal ini,NavigationRail
) dan widget lainnya harus mengambil ruang yang tersisa sebanyak mungkin (dalam hal ini,Expanded
). Satu sudut pandang tentang widgetExpanded
adalah bahwa widget ini "serakah". Jika Anda ingin lebih memahami peran widget ini, coba gabungkan widgetNavigationRail
denganExpanded
lainnya. Tata letak yang dihasilkan terlihat seperti berikut:
- Dua widget
Expanded
saling berbagi semua ruang horizontal yang tersedia, meskipun kolom samping navigasi hanya memerlukan sepotong kecil ruang di sisi kiri. - Di dalam widget
Expanded
, adaContainer
berwarna, dan adaGeneratorPage
di dalam container.
Widget stateless versus stateful
Sampai sekarang, MyAppState
telah memenuhi semua kebutuhan status Anda. Itulah mengapa semua widget yang telah Anda tulis sejauh ini adalah stateless. Widget-widget tersebut tidak memiliki status yang dapat diubah. Tidak ada widget yang dapat mengubah widget itu sendiri—widget tersebut harus melalui MyAppState
.
Hal ini akan segera berubah.
Anda memerlukan suatu cara untuk menyimpan nilai selectedIndex
kolom samping navigasi. Anda juga ingin dapat mengubah nilai ini dari dalam callback onDestinationSelected
.
Anda dapat menambahkan selectedIndex
sebagai properti tambahan MyAppState
. Kode tersebut akan berfungsi. Namun, Anda dapat membayangkan bahwa status aplikasi akan tumbuh dengan cepat di luar kendali jika setiap widget menyimpan nilai masing-masing di dalamnya.
Sebagian status hanya relevan untuk satu widget, sehingga status tersebut harus tetap dengan widget tersebut.
Masukkan StatefulWidget
, jenis widget yang memiliki State
. Pertama, konversi MyHomePage
menjadi widget stateful.
Tempatkan kursor Anda di baris pertama MyHomePage
(baris yang diawali dengan class MyHomePage...
), lalu buka menu Refactor menggunakan Ctrl+.
atau Cmd+.
. Kemudian, pilih Convert to StatefulWidget.
IDE membuat class baru untuk Anda, _MyHomePageState
. Class ini memperluas State
sehingga dapat mengelola nilainya sendiri. (Class ini dapat mengubah dirinya sendiri.) Perhatikan juga bahwa metode build
dari widget stateless yang lama telah berpindah ke _MyHomePageState
(bukannya tetap di widget). Metode berpindah secara bertahap—tidak ada yang diubah dalam metode build
. Metode ini sekarang menetap di tempat lain.
setState
Widget stateful baru hanya perlu melacak satu variabel: selectedIndex
. Buat 3 perubahan berikut untuk _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
Periksa perubahannya:
- Anda memperkenalkan variabel baru,
selectedIndex
, dan melakukan inisialisasi menjadi0
. - Anda menggunakan variabel baru ini dalam definisi
NavigationRail
sebagai ganti0
yang di-hard-code dan ada di sana sampai sekarang. - Saat callback
onDestinationSelected
dipanggil, sebagai ganti hanya mencetak nilai baru ke konsol, Anda menetapkan nilai tersebut keselectedIndex
di dalam panggilansetState()
. Panggilan ini mirip dengan metodenotifyListeners()
yang digunakan sebelumnya—metode ini memastikan bahwa UI selalu diupdate.
Kolom samping navigasi kini merespons interaksi pengguna. Namun, area yang diperluas di sebelah kanan tetap sama. Hal itu karena kode tidak menggunakan selectedIndex
untuk menentukan apa yang ditampilkan di layar.
Menggunakan selectedIndex
Tempatkan kode berikut di bagian atas metode build
_MyHomePageState
, tepat sebelum return Scaffold
:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
Periksa potongan kode berikut:
- Kode tersebut mendeklarasikan variabel baru,
page
, dari jenisWidget
. - Kemudian, pernyataan switch menetapkan layar untuk
page
, berdasarkan nilai saat ini padaselectedIndex
. - Karena belum ada
FavoritesPage
, gunakanPlaceholder
; sebuah widget praktis yang menggambar kotak silang di tempat yang Anda pilih, menandai bagian UI tersebut sebagai tidak tuntas.
- Dengan menerapkan prinsip gagal cepat, pernyataan switch juga memastikan untuk menampilkan kesalahan jika
selectedIndex
bukan 0 atau 1. Hal ini membantu mencegah munculnya bug. Jika Anda menambahkan tujuan baru ke kolom samping navigasi dan lupa mengupdate kode ini, program akan mengalami error dalam pengembangan (bukan membiarkan Anda menebak kenapa tidak bekerja, atau membiarkan Anda menerbitkan kode berisi bug ke dalam produksi).
Kini, setelah page
berisi widget yang ingin Anda tampilkan di sebelah kanan, Anda mungkin dapat menebak perubahan apa lagi yang diperlukan.
Berikut tampilan _MyHomePageState
setelah satu perubahan tersebut:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
Aplikasi sekarang beralih di antara GeneratorPage
kita dan placeholder yang akan segera menjadi halaman Favorites.
Tingkat respons
Berikutnya, buat kolom samping navigasi menjadi responsif. Dengan kata lain, buat agar kolom samping navigasi menampilkan label secara otomatis (menggunakan extended: true
) saat ada ruang yang cukup.
Flutter menyediakan berbagai widget yang membantu membuat aplikasi Anda menjadi responsif secara otomatis. Misalnya, Wrap
adalah widget yang mirip dengan Row
atau Column
yang secara otomatis menggabungkan turunan ke "baris" berikutnya (yang disebut "run") saat ruang vertikal atau horizontal tidak mencukupi. Ada FittedBox
, sebuah widget yang secara otomatis memasukkan turunannya ke dalam ruang yang tersedia berdasarkan spesifikasi Anda.
Namun, NavigationRail
tidak secara otomatis menampilkan label saat ruang tidak cukup karena kode tersebut tidak dapat mengetahui apa sebenarnya yang dimaksud dengan ruang yang cukup dalam setiap konteks. Pengambilan keputusan itu tergantung pada Anda sebagai developer.
Misalnya, Anda memutuskan untuk menampilkan label hanya jika lebar MyHomePage
setidaknya 600 piksel.
Dalam hal ini, widget yang digunakan adalah LayoutBuilder
. Widget ini memungkinkan Anda mengubah pohon widget tergantung pada seberapa banyak ruang yang tersedia yang dimiliki.
Sekali lagi, gunakan menu Refactor Flutter di VS Code untuk membuat perubahan yang diperlukan. Namun, proses kali ini sedikit lebih rumit:
- Dalam metode
build
_MyHomePageState
, letakkan kursor Anda padaScaffold
. - Buka menu Refactor dengan
Ctrl+.
(Windows/Linux) atauCmd+.
(Mac). - Pilih Wrap with Builder dan tekan Enter.
- Modifikasi nama
Builder
yang baru ditambahkan menjadiLayoutBuilder
. - Modifikasi daftar parameter callback dari
(context)
menjadi(context, constraints)
.
Callback builder
LayoutBuilder
dipanggil setiap kali batasan berubah. Misalnya, hal ini terjadi saat:
- Pengguna mengubah ukuran jendela aplikasi
- Pengguna memutar ponsel mereka dari mode potret menjadi mode lanskap, atau sebaliknya
- Beberapa widget di samping
MyHomePage
membesar, sehingga membuat batasanMyHomePage
mengecil - Dan seterusnya
Sekarang kode Anda dapat memutuskan untuk menampilkan label dengan membuat kueri constraints
saat ini atau tidak. Buat perubahan baris tunggal berikut untuk metode build
_MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
Sekarang aplikasi Anda merespons lingkungannya, seperti ukuran layar, orientasi, dan platform. Dengan kata lain, aplikasi Anda sudah responsif.
Pekerjaan yang tersisa hanya mengganti Placeholder
itu dengan layar Favorites yang sebenarnya. Pembahasan itu dibahas di bagian berikutnya.
8. Menambahkan halaman baru
Anda ingat widget Placeholder
yang kita gunakan sebagai ganti halaman Favorites?
Saatnya memperbaiki hal ini.
Jika Anda ingin mencoba hal baru, coba lakukan langkah ini sendiri. Tujuan Anda adalah menampilkan daftar favorites
dalam widget stateless baru, FavoritesPage
, lalu menampilkan widget tersebut, bukan Placeholder
.
Berikut beberapa petunjuk untuk Anda:
- Jika Anda menginginkan
Column
yang dapat di-scroll, gunakan widgetListView
. - Ingat, akses instans
MyAppState
dari widget apa pun menggunakancontext.watch<MyAppState>()
. - Jika Anda juga ingin mencoba widget baru,
ListTile
memiliki properti sepertititle
(umumnya untuk teks),leading
(untuk ikon atau avatar), danonTap
(untuk interaksi). Namun, Anda dapat mencapai efek serupa dengan widget yang sudah Anda ketahui. - Dart memungkinkan penggunaan loop
for
dalam literal koleksi. Misalnya, jikamessages
berisi daftar string, Anda dapat memiliki kode seperti berikut:
Di sisi lain, jika Anda lebih terbiasa dengan pemrograman fungsional, Dart juga memungkinkan Anda menulis kode seperti messages.map((m) => Text(m)).toList()
. Tentu saja Anda selalu dapat membuat daftar widget dan mengisinya secara imperatif di dalam metode build
.
Keuntungan menambahkan sendiri halaman Favorites adalah Anda belajar lebih banyak dengan membuat keputusan sendiri. Kekurangannya adalah Anda mungkin menemui masalah yang belum dapat Anda pecahkan sendiri. Ingat: tidak apa-apa untuk gagal, dan kegagalan adalah salah satu elemen terpenting pembelajaran. Tidak ada yang mengharapkan Anda berhasil dalam pengembangan Flutter pertama Anda, dan Anda pun seharusnya begitu.
Berikut ini hanyalah salah satu cara untuk menerapkan halaman favorit. Bagaimana halaman ini diterapkan (semoga) akan menginspirasi Anda untuk bermain dengan kode—meningkatkan UI dan membuat UI sesuai keinginan Anda.
Berikut class FavoritesPage
baru:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
Inilah fungsi widget tersebut:
- Widget ini mendapatkan status aplikasi saat ini.
- Jika daftar favorit kosong, pesan terpusat berikut akan ditampilkan: No favorites yet*.*
- Jika tidak, daftar (dapat di-scroll) akan ditampilkan.
- Daftar tersebut dimulai dengan ringkasan (misalnya, You have 5 favorites*.*).
- Kode tersebut kemudian melakukan iterasi di seluruh favorit dan membuat widget
ListTile
untuk masing-masing favorit.
Yang tersisa sekarang adalah mengganti widget Placeholder
dengan FavoritesPage
. Dan selesai!
Anda bisa mendapatkan kode terakhir aplikasi ini melalui repo codelab di GitHub.
9. Langkah berikutnya
Selamat!
Luar biasa! Anda mengubah scaffold nonfungsional dengan satu widget Column
dan dua widget Text
menjadi aplikasi kecil yang responsif dan menyenangkan.
Yang telah kita bahas
- Dasar-dasar cara kerja Flutter
- Membuat tata letak di Flutter
- Menghubungkan interaksi pengguna (seperti penekanan tombol) dengan perilaku aplikasi
- Menjaga kode Flutter Anda tetap tertata
- Membuat aplikasi Anda menjadi responsif
- Mencapai tampilan & nuansa yang konsisten untuk aplikasi Anda
Bagaimana selanjutnya?
- Bereksperimenlah lebih lanjut dengan aplikasi yang Anda buat selama menjalani lab ini.
- Lihat kode dari versi lanjutan aplikasi yang sama ini, untuk mengetahui bagaimana cara menambahkan daftar animasi, gradien, cross-fade, dan banyak lagi.
- Ikuti perjalanan pembelajaran Anda dengan membuka flutter.dev/learn.