Anda di halaman 1dari 34

aftar

BAB 1
Fundamental Flutter

Setelah menyelesaikan modul ini, kita diharapkan mampu:


 Memahami anatomi flutter.
 Mengenal dan memahami widget.
 Membuat Login page
 Memahami dan membuat navigation & routing.

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:

Method initState hanya akan dijalankan satu kali


ketika state dibuat. Kemudian widget akan
ditampilkan melalui method build. Perubahan
state dilakukan dengan memanggil method
setState. Setelah itu widget akan dibangun
kembali dengan state yang baru. Method
didUpdateWidget akan dipanggil ketika
widget berubah dan memiliki jenis
runtime atau key yang sama.
Dalam kasus aplikasi Counter, alur
perubahan state-nya akan 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:

Kode untuk tampilan di atas adalah seperti berikut:


B a b I F u n d a m e n t a l F l u tt e r 9 | 34
1. /// first_screen.dart

2. class FirstScreen extends StatelessWidget {

3. const FirstScreen({Key? key}) : super(key: key);

4.

5. @override

6. Widget build(BuildContext context) {

7. return Scaffold(

8. appBar: AppBar(

9. title: const Text('Navigation & Routing'),

10. ),

11. body: Center(

12. child: Column(

13. mainAxisAlignment: MainAxisAlignment.center,

14. children: <Widget>[

15. ElevatedButton(

16. child: const Text('Go to Second Screen'),

17. onPressed: () {},

18. ),

19. ElevatedButton(

20. child: const Text('Navigation with Data'),

21. onPressed: () {},

22. ),

23. ElevatedButton(

24. child: const Text('Return Data from Another Screen'),

25. onPressed: () {},

26. ),

27. ElevatedButton(

28. child: const Text('Replace Screen'),

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.

38. /// second_screen.dart

39. class SecondScreen extends StatelessWidget {

40. const SecondScreen({Key? key}) : super(key: key);

41.

42. @override

43. Widget build(BuildContext context) {

44. return Scaffold(

45. body: Center(

46. child: ElevatedButton(

47. child: const Text('Back'),

48. onPressed: () {},

49. ),

50. ),

51. );

52. }

53. }

54.

55. /// second_screen_with_data.dart

56. class SecondScreenWithData extends StatelessWidget {

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

60. Widget build(BuildContext context) {

61. return Scaffold(

62. body: Center(

63. child: Column(

64. mainAxisAlignment: MainAxisAlignment.center,

65. children: <Widget>[

66. ElevatedButton(

67. child: const Text('Back'),

68. onPressed: () {

69. Navigator.pop(context);

70. },

71. ),

72. ],

73. ),

74. ),

75. );

76. }

77. }

78.

79. /// return_data_screen.dart

80. class ReturnDataScreen extends StatefulWidget {

81. const ReturnDataScreen({Key? key}) : super(key: key);

82.

83. @override

84. State<ReturnDataScreen> createState() => _ReturnDataScreenState();

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.

87. class _ReturnDataScreenState extends State<ReturnDataScreen> {

88. @override

89. Widget build(BuildContext context) {

90. return Scaffold(

91. body: Center(

92. child: Column(

93. mainAxisAlignment: MainAxisAlignment.center,

94. children: <Widget>[],

95. ),

96. ),

97. );

98. }

99. }

100.

101. /// replacement_screen.dart

102. class ReplacementScreen extends StatelessWidget {

103. const ReplacementScreen({Key? key}) : super(key: key);

104.

105. @override

106. Widget build(BuildContext context) {

107. return Scaffold(

108. body: Center(

109. child: ElevatedButton(

110. child: const Text('Open Another Screen'),

111. onPressed: () {},

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.

118. /// another_screen.dart

119. class AnotherScreen extends StatelessWidget {

120. const AnotherScreen({Key? key}) : super(key: key);

121.

122. @override

123. Widget build(BuildContext context) {

124. return Scaffold(

125. body: Center(

126. child: Column(

127. mainAxisAlignment: MainAxisAlignment.center,

128. children: <Widget>[

129. const Text('Back to First Screen'),

130. ElevatedButton(

131. child: const Text('Back'),

132. onPressed: () {},

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.

1. class MyApp extends StatelessWidget {

2. const MyApp({Key? key}) : super(key: key);

3.

4. @override

5. Widget build(BuildContext context) {

6. return MaterialApp(

7. title: 'Flutter Demo',

8. theme: ThemeData(

9. primarySwatch: Colors.blue,

10. visualDensity: VisualDensity.adaptivePlatformDensity,

11. ),

12. initialRoute: '/',

13. routes: {

14. '/': (context) => const FirstScreen(),

15. '/secondScreen': (context) => const SecondScreen(),

16. '/secondScreenWithData': (context) => SecondScreenWithData(

17. ModalRoute.of(context)?.settings.arguments as String),

18. '/returnDataScreen': (context) => const ReturnDataScreen(),

19. '/replacementScreen': (context) => const ReplacementScreen(),

20. '/anotherScreen': (context) => const AnotherScreen(),

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(

2. child: Text('Go to Second Screen'),

3. onPressed: () {

4. Navigator.pushNamed(context, '/secondScreen');

5. },

6. ),

Untuk kembali ke halaman sebelumnya caranya masih sama, yaitu menggunakan Navigator.pop.

1. class SecondScreen extends StatelessWidget {

2. @override

3. Widget build(BuildContext context) {

4. return Scaffold(

5. body: Center(

6. child: ElevatedButton(

7. child: const Text('Back'),

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(

2. child: Text('Navigation with Data'),

3. onPressed: () {

4. Navigator.pushNamed(context, '/secondScreenWithData', arguments:


'Hello from First Screen!');

5. },

6. ),

Untuk mendapatkan argumen yang dikirimkan pada route tujuan kita menggunakan
method ModalRoute.of().

1. class SecondScreenWithData extends StatelessWidget {

2. final String data;

3.

4. const SecondScreenWithData(this.data, {Key? key}) : super(key: key);

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

7. Widget build(BuildContext context) {

8. return Scaffold(

9. body: Center(

10. child: Column(

11. mainAxisAlignment: MainAxisAlignment.center,

12. children: <Widget>[

13. Text(data),

14. ElevatedButton(

15. child: Text('Back'),

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.

1. class _ReturnDataScreenState extends State<ReturnDataScreen> {

2. final _textController = TextEditingController();

3.

4. @override

5. Widget build(BuildContext context) {

6. return Scaffold(

7. body: Center(

8. child: Column(

9. mainAxisAlignment: MainAxisAlignment.center,

10. children: <Widget>[

11. Padding(

12. padding: const EdgeInsets.symmetric(horizontal: 16.0),

13. child: TextField(

14. controller: _textController,

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(

19. child: const Text('Send'),

20. onPressed: () {

21. Navigator.pop(context, _textController.text);

22. },

23. ),

24. ],

25. ),

26. ),

27. );

28. }

29.

30. @override

31. void dispose() {

32. _textController.dispose();

33. super.dispose();

34. }

35. }

Selanjutnya kita akan mengembalikan nilai dari input pengguna.

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 {

2. final scaffoldMessenger = ScaffoldMessenger.of(context);

3. final result =

4. await Navigator.pushNamed(context, '/returnDataScreen');

5. SnackBar snackBar = SnackBar(content: Text('$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(

2. child: Text('Replace Screen'),

3. onPressed: () {

4. Navigator.pushNamed(context, '/replacementScreen');

5. },

6. ),

7.

8. ...

9.

10. /// replacement_screen.dart

11. class ReplacementScreen extends StatelessWidget {

12. @override

13. Widget build(BuildContext context) {

14. return Scaffold(

15. body: Center(

16. child: ElevatedButton(

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: () {

19. Navigator.pushReplacementNamed(context, '/anotherScreen');

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.

1. class AnotherScreen extends StatelessWidget {

2. @override

3. Widget build(BuildContext context) {

4. return Scaffold(

5. body: Center(

6. child: Column(

7. mainAxisAlignment: MainAxisAlignment.center,

8. children: <Widget>[

9. Text('Back to First Screen'),

10. ElevatedButton(

11. child: const Text('Back'),

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

Latihan: News App


Setelah mempelajari tentang widget dan navigasi, sekarang kita akan memulai praktik
mengembangkan aplikasi pertama kita di kelas ini. Aplikasi yang akan kita buat adalah sebuah
aplikasi portal berita. Aplikasi ini akan menampilkan beberapa berita dari berbagai sumber.
Beberapa poin yang akan dipelajari pada materi ini, antara lain:
 Mengambil data dari berkas JSON lalu ditampilkan sebagai list.
 Mengimplementasikan named routes untuk navigasi halaman.
 Menambahkan packagewebview untuk menampilkan konten web.
Hasil dari aplikasi yang dibuat akan jadi seperti ini:

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.

...

# To add assets to your application, add an assets section, like this:


assets:
- assets/articles.json

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.

1. factory Article.fromJson(Map<String, dynamic> article) => 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.

5. class MyApp extends StatelessWidget {

6. const MyApp({Key? key}) : super(key: key);

7.

8. @override

9. Widget build(BuildContext context) {

10. return MaterialApp(

11. title: 'News App',

12. theme: ThemeData(

13. primarySwatch: Colors.blue,

14. visualDensity: VisualDensity.adaptivePlatformDensity,

15. ),

16. initialRoute: NewsListPage.routeName,

17. routes: {

18. NewsListPage.routeName: (context) => const NewsListPage(),

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.

1. class NewsListPage extends StatelessWidget {

2. static const routeName = '/article_list';

3.

4. const NewsListPage({Key? key}) : super(key: key);


B a b I F u n d a m e n t a l F l u tt e r 27 | 34
5.

6. @override

7. Widget build(BuildContext context) {

8. return Scaffold(

9. appBar: AppBar(

10. title: const Text('News App'),

11. ),

12. body: FutureBuilder<String>(

13. future:

14.
DefaultAssetBundle.of(context).loadString('assets/articles.json'),

15. builder: (context, snapshot) {},

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.

1. List<Article> parseArticles(String? json) {

2. if (json == null) {

3. return [];

4. }

5.

6. final List parsed = jsonDecode(json);

7. return parsed.map((json) => Article.fromJson(json)).toList();

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) {

2. final List<Article> articles = parseArticles(snapshot.data);

3. },

11. Saat ini kita telah mendapatkan daftar Article. Artinya, kita dapat menampilkannya ke layar.
Tampilkan widget ListViewAnda dengan item dari list Article.

1. builder: (context, snapshot) {

2. final List<Article> articles = parseArticles(snapshot.data);

3. return ListView.builder(

4. itemCount: articles.length,

5. itemBuilder: (context, index) {

6. return _buildArticleItem(context, articles[index]);

7. },

8. );

9. },

12. Jika kode Anda mengalami eror, buat method baru bernama _buildArticleItem(). Method ini
akan menampilkan widget dari masing-masing artikel.

1. Widget _buildArticleItem(BuildContext context, Article article) {

2. return ListTile(

3. contentPadding:

4. const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),

5. leading: Image.network(

6. article.urlToImage,

7. width: 100,

8. ),

9. title: Text(article.title),

10. subtitle: Text(article.author),

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:

1. class ArticleDetailPage extends StatelessWidget {

2. static const routeName = '/article_detail';

3.

4. final Article article;

5.

6. const ArticleDetailPage({Key? key, required this.article}) : super(key:


key);

7.

8. @override

9. Widget build(BuildContext context) {

10. return Scaffold(

11. appBar: AppBar(

12. title: Text(article.title),

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(

15. child: Column(

16. children: [

17. Image.network(article.urlToImage),

18. Padding(

19. padding: const EdgeInsets.all(10),

20. child: Column(

21. crossAxisAlignment: CrossAxisAlignment.start,

22. children: [

23. Text(article.description),

24. Divider(color: Colors.grey),

25. Text(

26. article.title,

27. style: const TextStyle(

28. color: Colors.black,

29. fontWeight: FontWeight.bold,

30. fontSize: 24,

31. ),

32. ),

33. const Divider(color: Colors.grey),

34. Text('Date: ${article.publishedAt}'),

35. const SizedBox(height: 10),

36. Text('Author: ${article.author}'),

37. const Divider(color: Colors.grey),

38. Text(

39. article.content,

40. style: const TextStyle(fontSize: 16),

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(

44. child: const Text('Read more'),

45. onPressed: () {},

46. ),

47. ],

48. ),

49. ),

50. ],

51. ),

52. ),

53. );

54. }

55. }

Halaman ArticleDetailPage membutuhkan data artikel yang dipilih sehingga perlu kita
kirimkan melalui constructor.

15. Jangan lupa untuk menambahkan route ke halaman ArticleDetailPage.

1. routes: {

2. NewsListPage.routeName: (context) => const NewsListPage(),

3. ArticleDetailPage.routeName: (context) => ArticleDetailPage(

4. article: ModalRoute.of(context)?.settings.arguments as Article,

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

Jangan lupa untuk menginstal package dengan menjalankan perintah:

flutter pub get

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.

3. class ArticleWebView extends StatelessWidget {

4. static const routeName = '/article_web';

5.

6. final String url;

7.

8. const ArticleWebView({Key? key, required this.url}) : super(key: key);

9.

10. @override

11. Widget build(BuildContext context) {

12. return Scaffold(

13. appBar: AppBar(

14. title: Text('News App'),

15. ),

16. body: WebView (

17. initialUrl: url,

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.

18. Tambahkan navigasi ke ArticleWebView ketika tombol “Read more” diklik.

1. ElevatedButton(

2. child: const Text('Read more'),

3. onPressed: () {

4. Navigator.pushNamed(context, ArticleWebView.routeName,

5. arguments: article.url);

6. },

7. ),

Jangan lupa untuk mendaftarkan routes untuk navigasi.

1. routes: {

2. NewsListPage.routeName: (context) => const NewsListPage(),

3. ArticleDetailPage.routeName: (context) => ArticleDetailPage(

4. article: ModalRoute.of(context)?.settings.arguments as Article,

5. ),

6. ArticleWebView.routeName: (context) => ArticleWebView(

7. url: ModalRoute.of(context)?.settings.arguments as String,

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

Anda mungkin juga menyukai