BAB 1
Fundamental Flutter
B a b I F u n d a m e n t a l F l u tt e r 1 | 34
A. Dasar Teori
a. Anatomi Flutter
Seperti yang telah Anda pelajari atau sering Anda temui di internet, “In Flutter,
everything is a widget”. Pernyataan ini tidak sepenuhnya salah. Namun, pada kenyataannya
ada banyak komponen lain di dalam project Flutter yang tidak menggunakan Widget.
Di dalam setiap project Flutter terdapat beberapa folder dan juga berkas penting yang perlu
Anda perhatikan, antara lain:
1. Folder project android. Anda belum perlu mengubah file di
dalam folder ini sampai ada kebutuhan terkait platform
Android.
2. Folder project ios. Anda belum perlu mengubah file di
dalam folder ini sampai ada kebutuhan terkait platform
iOS.
3. Folder utama Dart/Flutter. Di sinilah Anda akan banyak
meluangkan waktu untuk pengembangan aplikasi Flutter.
4. Folder test. Anda juga perlu meluangkan sebagian waktu di
sini untuk menguji aplikasi.
5. Berkas ini diperlukan untuk mengelola dependency dan
library yang diperlukan dalam aplikasi.
Mayoritas waktu kita dalam mengembangkan Flutter akan banyak tercurah pada mengurus
berkas di dalam folder lib dan juga widget-widget di dalamnya. Setiap aplikasi Flutter terdiri
dari kumpulan widget. Teks merupakan widget; layout disusun menggunakan widget;
bahkan aplikasi itu sendiri merupakan widget. Kita ambil contoh aplikasi Counter yang
merupakan aplikasi starter setiap kita membuat project Flutter.
Jika kita breakdown tampilan aplikasi ini menjadi widget tree, maka akan menjadi seperti
berikut.
B a b I F u n d a m e n t a l F l u tt e r 2 | 34
Struktur widget ini berlaku sama ketika sudah diterjemahkan menjadi kode.
Kode di atas akan di-render oleh Flutter sehingga menghasilkan tampilan di layar perangkat
seperti berikut ini:
B a b I F u n d a m e n t a l F l u tt e r 3 | 34
b. Pengenalan Widget
Pada materi sebelumnya kita telah mempelajari bahwa hampir seluruh bagian
Flutter khususnya yang dilihat oleh pengguna merupakan widget. Jadi, sebenarnya apa itu
widget?
Penggunaan widget pada Flutter awalnya terinspirasi dari framework lain yaitu React. Ide
utamanya adalah bagaimana membuat UI dari widget. Widget menggambarkan seperti apa
tampilan ketika diberi konfigurasi tertentu. Ketika konfigurasi widget berubah, maka widget
akan dibangun ulang sesuai dengan konfigurasi yang baru.
Tujuan umum ketika mengembangkan UI dengan Flutter adalah untuk menyusun widget-
widget ini sebagai kesatuan tampilan yang juga dikenal sebagai widget tree.
Sederhananya, widget tree adalah kumpulan widget. Widget-widget tersebut saling
terhubung dengan pola parent-child. Setiap kali ada perubahan di salah satu widget, maka
widget tersebut dan seluruh widget turunannya akan dibangun ulang.
Sampai sini Anda seharusnya telah paham bahwa widget adalah kelas atau komponen yang
mendefinisikan sebuah tampilan UI, seperti Text dan Button. Namun, ada pula widget yang
bersifat lebih abstrak. Sebelum ini Anda mungkin pernah menggunakan widget seperti Row
dan Column yang berperan untuk menyusun widget lain secara horizontal atau vertikal. Jika
Anda menerapkan animasi ke dalam aplikasi, maka Anda menggunakan widget
AnimationController. Selain itu juga terdapat widget lain untuk pengolahan data yang lebih
kompleks seperti FutureBuilder dan StreamBuilder.
Secara umum widget terbagi menjadi beberapa kategori seperti berikut:
Layout: Row, Column, Stack, Scaffold.
Structures: Button, Text, Toast.
Styles: TextStyle, Color, Padding.
Animations: FadeInImage, ColorTween.
Positioning & alignment: Center, Padding.
Widget Lifecycle
Jika sebelumnya Anda pernah mengembangkan aplikasi Android atau iOS secara native, pasti
Anda familiar dengan konsep life cycle. Lalu bagaimana dengan Flutter? Apakah widget juga
memiliki siklus hidup?
Sebelum membahas tentang lifecycle, ingat bahwa widget memiliki state. Ketika state di
dalam widget tidak pernah berubah kita menggunakan StatelessWidget. Sementara apabila
B a b I F u n d a m e n t a l F l u tt e r 4 | 34
widget bersifat mutable atau state-nya dapat berubah maka yang kita gunakan adalah
StatefulWidget.
Pada dasarnya widget tidak memiliki life cycle. StatelessWidget tidak pernah berubah,
sehingga Flutter hanya akan memanggil method build untuk menampilkan widget. Sedikit
berbeda dengan StatelessWidget, lifecycle pada StatefulWidget juga tidak berada
pada widget-nya, namun milik state.
Jika digambarkan, maka lifecycle dari sebuah state adalah seperti berikut:
B a b I F u n d a m e n t a l F l u tt e r 5 | 34
B. Latihan
Anda telah mempelajari tentang anatomi aplikasi Flutter yang tersusun dari hierarki widget serta
bagaimana siklus suatu state di dalam widget. Kini seharusnya Anda telah siap menyusun aplikasi
Flutter Anda sendiri. Siap?
Namun, sebelum melangkah lebih jauh, menurut Anda bagaimana susunan widget dari
halaman login Dicoding seperti berikut? Apakah Anda dapat mengimplementasikannya menjadi
aplikasi Flutter?
Secara sekilas, ada tiga komponen Scaffold yang dapat kita gunakan untuk menyusun layout,
yaitu AppBar, Drawer, dan satu widget lagi sebagai body atau konten utama halaman.
Dari desain tersebut juga kita dapat melihat bahwa mayoritas widget disusun secara vertikal,
sehingga kita menggunakan widget SingleChildScrollView dan Column. Kemudian di
B a b I F u n d a m e n t a l F l u tt e r 6 | 34
dalam Column barulah kita menambahkan widget-widget sebagai kontennya,
misalnya Image, Text, TextField, dsb.
Beberapa widget di atas merupakan widget-widget dasar yang umum digunakan. Kita juga telah
mempelajarinya pada kelas sebelumnya. Sedangkan beberapa widget lainnya mungkin baru pertama
kali Anda temui. Tenang saja, kita akan berkenalan lagi dengan widget-widget ini pada materi-materi
berikutnya.
Note: Karena ada banyak sekali widget pada Flutter, maka kita tidak akan bisa mempelajarinya
sekaligus dalam satu kelas. Untuk itu, Anda dapat mempelajari widget Flutter selengkapnya pada
katalog berikut : https://flutter.dev/docs/development/ui/widgets. Selalu ingat bahwa dokumentasi
adalah teman terbaik Anda.
Berikut ini merupakan tampilan halaman login Dicoding yang dibuat dengan Flutter:
B a b I F u n d a m e n t a l F l u tt e r 7 | 34
B a b I F u n d a m e n t a l F l u tt e r 8 | 34
C. Navigation & Routing
Sebuah aplikasi tentunya tidak hanya memiliki satu halaman. Pengguna seharusnya dapat
berpindah-pindah halaman untuk menampilkan informasi yang berbeda. Di dalam
Flutter, screen atau halaman juga dikenal dengan istilah routes. Kembali lagi pada tagline
“everything is a widget”, begitu pula dengan routes.
Pada kelas Flutter Pemula kita telah mempelajari tentang navigasi antar route secara dasar
menggunakan Navigator.push dan Navigator.pop.
Perlu kita ingat kembali bahwa konsep navigasi pada Flutter adalah ketika
berpindah screen/activity akan menjadi tumpukan (stack). Jadi ketika berpindah ke screen lain
(push), maka screen pertama akan ditumpuk oleh screen kedua. Kemudian apabila kembali
dari screen kedua ke pertama, maka screen kedua akan dihapus (pop).
Namun, ketika Anda memerlukan navigasi ke suatu halaman yang sama dari banyak tempat, hal ini
akan menghasilkan duplikasi kode. Di sinilah Anda dapat memanfaatkan named route.
Konsep named route ini mirip dengan sebuah website di mana memiliki route atau endpoint untuk
merujuk ke suatu halaman, contohnya seperti /login atau /detail.
Mari kita langsung mengimplementasikannya ke dalam kode. Buatlah project aplikasi Flutter dengan
tampilan seperti berikut:
4.
5. @override
7. return Scaffold(
8. appBar: AppBar(
10. ),
15. ElevatedButton(
18. ),
19. ElevatedButton(
22. ),
23. ElevatedButton(
26. ),
27. ElevatedButton(
B a b I F u n d a m e n t a l F l u tt e r 10 | 34
29. onPressed: () {},
30. ),
31. ],
32. ),
33. ),
34. );
35. }
36. }
37.
41.
42. @override
49. ),
50. ),
51. );
52. }
53. }
54.
B a b I F u n d a m e n t a l F l u tt e r 11 | 34
57. const SecondScreenWithData({Key? key}) : super(key: key);
58.
59. @override
66. ElevatedButton(
68. onPressed: () {
69. Navigator.pop(context);
70. },
71. ),
72. ],
73. ),
74. ),
75. );
76. }
77. }
78.
82.
83. @override
B a b I F u n d a m e n t a l F l u tt e r 12 | 34
85. }
86.
88. @override
95. ),
96. ),
97. );
98. }
99. }
100.
104.
105. @override
112. ),
B a b I F u n d a m e n t a l F l u tt e r 13 | 34
113. ),
114. );
115. }
116. }
117.
121.
122. @override
130. ElevatedButton(
133. ),
134. ],
135. ),
136. ),
137. );
138. }
139. }
B a b I F u n d a m e n t a l F l u tt e r 14 | 34
Selanjutnya untuk mendefinisikan route temukan widget MaterialApp lalu tambahkan
properti initialRoute dan routes.
3.
4. @override
6. return MaterialApp(
8. theme: ThemeData(
9. primarySwatch: Colors.blue,
11. ),
13. routes: {
21. },
22. );
23. }
24. }
Jika Anda menggunakan initialRoute, pastikan Anda tidak menggunakan properti home.
B a b I F u n d a m e n t a l F l u tt e r 15 | 34
Normal Navigation
Untuk melakukan navigasi ke route, Anda dapat memanggil method Navigator.pushNamed dengan
dua parameter yaitu context dan nama route yang ingin dituju. Contohnya seperti berikut:
1. ElevatedButton(
3. onPressed: () {
4. Navigator.pushNamed(context, '/secondScreen');
5. },
6. ),
Untuk kembali ke halaman sebelumnya caranya masih sama, yaitu menggunakan Navigator.pop.
2. @override
4. return Scaffold(
5. body: Center(
6. child: ElevatedButton(
8. onPressed: () {
9. Navigator.pop(context);
10. },
11. ),
12. ),
13. );
14. }
15. }
B a b I F u n d a m e n t a l F l u tt e r 16 | 34
Navigation with data
Lalu bagaimana jika kita ingin mengirimkan data ke sebuah halaman? Caranya, cukup menambahkan
satu parameter atau properti lagi pada method pushNamed bernama arguments.
1. ElevatedButton(
3. onPressed: () {
5. },
6. ),
Untuk mendapatkan argumen yang dikirimkan pada route tujuan kita menggunakan
method ModalRoute.of().
3.
5.
B a b I F u n d a m e n t a l F l u tt e r 17 | 34
6. @override
8. return Scaffold(
9. body: Center(
13. Text(data),
14. ElevatedButton(
16. onPressed: () {
17. Navigator.pop(context);
18. },
19. ),
20. ],
21. ),
22. ),
23. );
24. }
25. }
B a b I F u n d a m e n t a l F l u tt e r 18 | 34
Return data from a screen
Dalam beberapa kasus, Anda mungkin ingin mengembalikan data dari halaman baru. Misalnya,
setelah pengguna memberikan sebuah input, Anda ingin mengolahnya di halaman sebelumnya.
Method Navigator.pop() selain untuk menutup halaman juga berguna untuk mengembalikan nilai.
Pada contoh ini mari kita buat halaman dengan TextField dan Button.
3.
4. @override
6. return Scaffold(
7. body: Center(
8. child: Column(
9. mainAxisAlignment: MainAxisAlignment.center,
11. Padding(
B a b I F u n d a m e n t a l F l u tt e r 19 | 34
15. decoration: const InputDecoration(labelText: 'Enter your
name'),
16. ),
17. ),
18. ElevatedButton(
20. onPressed: () {
22. },
23. ),
24. ],
25. ),
26. ),
27. );
28. }
29.
30. @override
32. _textController.dispose();
33. super.dispose();
34. }
35. }
1. onPressed: () {
2. Navigator.pop(context, _textController.text);
3. },
B a b I F u n d a m e n t a l F l u tt e r 20 | 34
Kembali ke FirstScreen, kita akan membuat Navigasi untuk menuju ke ReturnDataScreen. Jika Anda
perhatikan, method untuk push pada navigation ini sebenarnya merupakan objek Future. Artinya,
metode ini bisa mengembalikan nilai di masa depan.
Oleh karena itu, kita dapat menyimpan nilainya ke dalam suatu variabel. Jangan lupa untuk
menggunakan async dan await untuk menunggu nilai yang dikembalikan.
1. onPressed: () async {
3. final result =
6. scaffoldMessenger.showSnackBar(snackBar);
7. },
B a b I F u n d a m e n t a l F l u tt e r 21 | 34
Replace Screen
Kita telah membahas bahwa navigasi pada Flutter membuat halaman menjadi bertumpuk (stacked).
Namun, Anda juga memiliki opsi untuk membuat halaman baru dengan menggantikan stack yang
sedang terbuka. Cara ini umum digunakan pada halaman seperti splash screen atau login di mana
pengguna tidak perlu kembali ke halaman tersebut ketika menekan tombol back.
Caranya, gunakan metode pushReplacement atau pushReplacementNamed jika Anda
menggunakan named routes.
1. ElevatedButton(
3. onPressed: () {
4. Navigator.pushNamed(context, '/replacementScreen');
5. },
6. ),
7.
8. ...
9.
12. @override
B a b I F u n d a m e n t a l F l u tt e r 22 | 34
17. child: const Text('Open Another Screen'),
18. onPressed: () {
20. },
21. ),
22. ),
23. );
24. }
25. }
Halaman AnotherScreen akan me-replace stack dari ReplacementScreen. Sehinga, ketika pengguna
keluar dari AnotherScreen akan langsung diarahkan ke FirstScreen.
2. @override
4. return Scaffold(
5. body: Center(
6. child: Column(
7. mainAxisAlignment: MainAxisAlignment.center,
8. children: <Widget>[
10. ElevatedButton(
12. onPressed: () {
13. Navigator.pop(context);
14. },
15. ),
16. ],
17. ),
B a b I F u n d a m e n t a l F l u tt e r 23 | 34
18. ),
19. );
20. }
21. }
B a b I F u n d a m e n t a l F l u tt e r 24 | 34
1. Mari buat project Flutter baru dan berikan nama misalnya dicoding_news_app.
2. Sebelum mulai menyusun layout aplikasi, kita akan menyiapkan data yang akan ditampilkan
terlebih dulu. Saat ini kita masih akan menggunakan data lokal yang disimpan dalam bentuk
json. Untuk berkas json-nya sendiri dapat Anda unduh pada tautan berikut.
3. Tambahkan berkas yang telah Anda unduh ke dalam project. Buat folder baru
bernama assets pada direktori utama lalu letakkan berkas json pada folder tersebut.
4. Selanjutnya daftarkan aset json tersebut dengan menambahkan kode di bawah pada akhir
berkas pubspec.yaml.
...
Jangan lupa perhatikan indentasi karena merupakan hal yang penting dalam berkas yaml.
B a b I F u n d a m e n t a l F l u tt e r 25 | 34
5. Lalu bagaimana kita membaca berkas json tersebut? Pertama, kita perlu membuat class
yang akan menjadi blueprint dari objek artikel. Buat berkas baru bernama article.dart.
Kemudian tambahkan beberapa propertinya. Sesuaikan properti ini dengan berkas json
Anda.
1. class Article {
2. final String author;
3. final String title;
4. final String description;
5. final String url;
6. final String urlToImage;
7. final String publishedAt;
8. final String content;
9.
10. Article({
11. required this.author,
12. required this.title,
13. required this.description,
14. required this.url,
15. required this.urlToImage,
16. required this.publishedAt,
17. required this.content,
18. });
19. }
6. Selanjutnya masih di dalam kelas Article tambahkan juga named constructor untuk
mengonversi format json menjadi bentuk object Article.
2. author: article['author'],
3. title: article['title'],
4. description: article['description'],
5. url: article['url'],
6. urlToImage: article['urlToImage'],
7. publishedAt: article['publishedAt'],
8. content: article['content'],
9. );
7. Setelah object article siap, sekarang saatnya kita mulai menyusun tampilan aplikasi. Hapus
atau ganti seluruh kode aplikasi Counter dan sisakan kode berikut:
1. void main() {
2. runApp(const MyApp());
B a b I F u n d a m e n t a l F l u tt e r 26 | 34
3. }
4.
7.
8. @override
15. ),
17. routes: {
19. },
20. );
21. }
22. }
8. Buat stateless widget baru bernama NewsListPage. Di sini kita menggunakan tampilan
standar dengan Scaffold dan AppBar. Untuk menampilkan daftar artikel ke bagian body, kita
akan menggunakan widget ListView. Namun, sebelum itu kita perlu membaca aset json-nya
bukan? Lantas bagaimana caranya? Karena proses pembacaan aset dilakukan
secara asynchronous, di sini kita akan memanfaatkan widget FutureBuilder. Widget ini
membutuhkan 2 parameter, yaitu future yang berisi objek Future yang akan ditampilkan
lalu parameter builderuntuk menjalankan suatu proses ketika nilai Future telah didapatkan.
3.
6. @override
8. return Scaffold(
9. appBar: AppBar(
11. ),
13. future:
14.
DefaultAssetBundle.of(context).loadString('assets/articles.json'),
16. ),
17. );
18. }
19. }
DefaultAssetBundle pada dasarnya juga merupakan sebuah widget. Widget ini akan
membaca String dari berkas aset yang kita tentukan.
9. Karena Future kita mengembalikan String, maka kita perlu mengonversinya menjadi objek
yang kita siapkan. Proses konversi ini juga dikenal dengan json parsing. Mari buat fungsi
baru di dalam berkas article.dart.
2. if (json == null) {
3. return [];
4. }
5.
8. }
10. Kemudian kita akan panggil fungsi parseArticles() tersebut dari dalam fungsi builder.
B a b I F u n d a m e n t a l F l u tt e r 28 | 34
1. builder: (context, snapshot) {
3. },
11. Saat ini kita telah mendapatkan daftar Article. Artinya, kita dapat menampilkannya ke layar.
Tampilkan widget ListViewAnda dengan item dari list Article.
3. return ListView.builder(
4. itemCount: articles.length,
7. },
8. );
9. },
12. Jika kode Anda mengalami eror, buat method baru bernama _buildArticleItem(). Method ini
akan menampilkan widget dari masing-masing artikel.
2. return ListTile(
3. contentPadding:
5. leading: Image.network(
6. article.urlToImage,
7. width: 100,
8. ),
9. title: Text(article.title),
11. );
12. }
B a b I F u n d a m e n t a l F l u tt e r 29 | 34
Di sini kita menggunakan widget ListTile. Widget ini merupakan widget built-in dari Flutter yang
pada dasarnya merupakan susunan tiga kolom dan bisa diatur sedemikian rupa. Beberapa
kustomisasi yang mungkin dilakukan dengan ListTile adalah seperti berikut:
13. Sekarang jalankan aplikasi Anda. Seharusnya daftar artikel sudah berhasil ditampilkan.
14. Fitur terakhir yang akan kita kembangkan selanjutnya adalah menampilkan detail dari
artikel yang dipilih. Buat berkas baru bernama detail_page.dart. Di berkas ini kita akan
membuat widget baru yang berperan sebagai halaman detail. Anda dapat berkreasi sendiri
untuk menyusun halaman detail atau menggunakan kode berikut:
3.
5.
7.
8. @override
13. ),
B a b I F u n d a m e n t a l F l u tt e r 30 | 34
14. body: SingleChildScrollView(
16. children: [
17. Image.network(article.urlToImage),
18. Padding(
22. children: [
23. Text(article.description),
25. Text(
26. article.title,
31. ),
32. ),
38. Text(
39. article.content,
41. ),
B a b I F u n d a m e n t a l F l u tt e r 31 | 34
42. const SizedBox(height: 10),
43. ElevatedButton(
46. ),
47. ],
48. ),
49. ),
50. ],
51. ),
52. ),
53. );
54. }
55. }
Halaman ArticleDetailPage membutuhkan data artikel yang dipilih sehingga perlu kita
kirimkan melalui constructor.
1. routes: {
5. ),
6. },
Selanjutnya buat item yang diklik agar membuka halaman detail. ListTile telah dilengkapi
dengan parameter onTapuntuk menjalankan sebuah fungsi ketika item tersebut diklik.
1. onTap: () {
2. Navigator.pushNamed(context, ArticleDetailPage.routeName,
3. arguments: article);
4. },
B a b I F u n d a m e n t a l F l u tt e r 32 | 34
Masukkan data artikel yang dipilih sebagai argumen navigasi.
16. Terakhir, kita akan menambahkan fitur agar pengguna dapat membaca berita lebih lengkap
dengan mengarahkan ke tautan aslinya. Karena artikel kita berbasis web yang umumnya
dibuka menggunakan browser, mari memanfaatkan widget WebViewWidget.
Kemudian tambahkan package pada berkas pubspec.yaml.
1. dependencies:
2. webview_flutter: ^3.0.4
17. Setelah package terinstal, buatlah widget baru yang akan menampilkan detail
artikel. WebViewWidget adalah sebuah widget. Karena setiap komponen dalam Flutter
adalah Widget, maka kita juga dapat melakukan kustomisasi pada halaman web dengan
menambahkan widget lain seperti AppBar, FloatingActionButton, dan lainnya.
1. import 'package:webview_flutter/webview_flutter.dart';
2.
5.
7.
9.
10. @override
15. ),
B a b I F u n d a m e n t a l F l u tt e r 33 | 34
18. ),
19. );
20. }
21. }
Perhatikan bahwa WebViewController membutuhkan url dari web yang akan ditampilkan,
sehingga Anda perlu mengirimkan nilai url ke ArticleWebView.
1. ElevatedButton(
3. onPressed: () {
4. Navigator.pushNamed(context, ArticleWebView.routeName,
5. arguments: article.url);
6. },
7. ),
1. routes: {
5. ),
8. ),
9. },
19. Simpan perubahan dan biarkan Flutter menjalankan fitur hot reload-nya.
B a b I F u n d a m e n t a l F l u tt e r 34 | 34