Belajar Membuat Aplikasi Tempat Wisata Dengan Flutter : Bagian Ketiga


Belajar Membuat Aplikasi Tempat Wisata Dengan Flutter : Bagian Ketiga - Sekarang kita telah sampai pada codelab terakhir. Di akhir codelab ini kita akan menyelesaikan project aplikasi Wisata Bandung. Hasil akhir aplikasi akan seperti berikut:

202006151706344cb0e4f7611298a20ed8ea1c93d5e74b.gif
Mari kita mulai. Buka kembali project codelab Anda sebelumnya.
  1. Pertama kali yang kita lakukan adalah membuat halaman baru untuk menampilkan daftar tempat wisata. Buat berkas baru main_screen.dart lalu buat widget untuk halaman MainScreen.


    1. import 'package:flutter/material.dart';

    2.  

    3. class MainScreen extends StatelessWidget {

    4.   @override

    5.   Widget build(BuildContext context) {

    6.     return Scaffold();

    7.   }

    8. }


  2. Jangan lupa untuk mengganti halaman utama yang ditampilkan pada berkas main.dart.


    1. void main() => runApp(MyApp());

    2.  

    3. class MyApp extends StatelessWidget {

    4.   @override

    5.   Widget build(BuildContext context) {

    6.     return MaterialApp(

    7.       title: 'Wisata Bandung',

    8.       theme: ThemeData.dark(),

    9.       home: DetailScreen(),

    10.     );

    11.   }

    12. }


  3. Pada MainScreen tambahkan AppBaruntuk judul halaman.


    1. class MainScreen extends StatelessWidget {

    2.   @override

    3.   Widget build(BuildContext context) {

    4.     return Scaffold(

    5.       appBar: AppBar(

    6.         title: Text('Wisata Bandung'),

    7.       ),

    8.     );

    9.   }

    10. }


  4. Sebagai body dari Scaffold kita akan menggunakan widget Card. Widget ini adalah widget material design yang menghasilkan tampilan seperti kartu dengan ujung yang membulat dan bayangan di belakang. Kemudian susun Row dan Column seperti contoh untuk menyusun child dari Card. Kodenya akan seperti berikut:
  1. class MainScreen extends StatelessWidget {
  2.   @override
  3.   Widget build(BuildContext context) {
  4.     return Scaffold(
  5.       appBar: AppBar(
  6.         title: Text('Wisata Bandung'),
  7.       ),
  8.       body: Card(
  9.         child: Row(
  10.           crossAxisAlignment: CrossAxisAlignment.start,
  11.           children: <Widget>[
  12.             Image.asset('images/farm-house.jpg'),
  13.             Padding(
  14.               padding: const EdgeInsets.all(8.0),
  15.               child: Column(
  16.                 crossAxisAlignment: CrossAxisAlignment.start,
  17.                 mainAxisSize: MainAxisSize.min,
  18.                 children: <Widget>[
  19.                   Text(
  20.                     'Farm House Lembang',
  21.                     style: TextStyle(fontSize: 16.0),
  22.                   ),
  23.                   SizedBox(
  24.                     height: 10,
  25.                   ),
  26.                   Text('Lembang'),
  27.                 ],
  28.               ),
  29.             )
  30.           ],
  31.         ),
  32.       ),
  33.     );
  34.   }
  35. }


Jalankan aplikasinya. Aplikasi akan mengalami overflow karena aset gambar yang terlalu besar. 

Kita bisa saja mengatur tinggi gambar secara manual, namun kali ini kita akan memanfaatkan widget Expanded agar tampilan juga dapat menyesuaikan di perangkat yang lebih besar atau kecil. 

Bungkus masing-masing item dari widget row ke dalam Expanded. Berikan parameter flex yang menurut Anda cocok.

  1. class MainScreen extends StatelessWidget {
  2.   @override
  3.   Widget build(BuildContext context) {
  4.     return Scaffold(
  5.       appBar: AppBar(
  6.         title: Text('Wisata Bandung'),
  7.       ),
  8.       body: Card(
  9.         child: Row(
  10.           crossAxisAlignment: CrossAxisAlignment.start,
  11.           children: <Widget>[
  12.             Expanded(
  13.               flex: 1,
  14.               child: Image.asset('images/farm-house.jpg'),
  15.             ),
  16.             Expanded(
  17.               flex: 2,
  18.               child: Padding(
  19.                 padding: const EdgeInsets.all(8.0),
  20.                 child: Column(
  21.                   crossAxisAlignment: CrossAxisAlignment.start,
  22.                   mainAxisSize: MainAxisSize.min,
  23.                   children: <Widget>[
  24.                     Text(
  25.                       'Farm House Lembang',
  26.                       style: TextStyle(fontSize: 16.0),
  27.                     ),
  28.                     SizedBox(
  29.                       height: 10,
  30.                     ),
  31.                     Text('Lembang'),
  32.                   ],
  33.                 ),
  34. ),
  35.             )
  36.           ],
  37.         ),
  38.       ),
  39.     );
  40.   }
  41. }

Item pertama Anda sudah siap. Selanjutnya kita akan membuat supaya bisa diklik untuk berpindah ke halaman detail. 

Karena kita membutuhkan sebuah widget yang berperan sebagai tombol dan bisa diklik, maka kita akan menggunakan widget FlatButton. Kita menggunakan FlatButton karena kita hanya membutuhkan parameter onPressed tanpa atribut lain yang disediakan RaisedButton mau pun IconButton. Pindahkan widget Card Anda menjadi child dari FlatButton.



    1. class MainScreen extends StatelessWidget {

    2.   @override

    3.   Widget build(BuildContext context) {

    4.     return Scaffold(

    5.       appBar: AppBar(

    6.         title: Text('Wisata Bandung'),

    7.       ),

    8.       body: FlatButton(

    9.         onPressed: () {},

    10.         child: Card(...),

    11. ),

    12.     );

    13.   }

    14. }


  1. Parameter onPressed menerima argumen berupa sebuah fungsi lambda. Di sini kita akan menambahkan Navigator untuk berpindah ke halaman detail.


    1. onPressed: () {

    2.   Navigator.push(context, MaterialPageRoute(builder: (context) {

    3.     return DetailScreen();

    4.   }));

    5. },


    Jalankan aplikasi. Seharusnya sampai langkah ini aplikasi Anda sudah dapat berpindah halaman ketika item diklik.
  2. Selanjutnya kita akan menampilkan beberapa item ke MainScreen. Di kelas ini kita masih menggunakan data statis dan lokal yang disimpan pada objek List. Sebelumnya, buatlah kelas sebagai blueprint untuk menyimpan objek tempat wisata kita. Buat folder baru di dalam folder lib dengan cara klik kanan folder lib -> New -> Package, lalu berikan nama model. Di dalam folder model buat berkas dart bernama tourism_place.dart.
  3. Di dalam tourism_place.dart buat data class yang akan menjadi blueprint objek tempat wisata.
  1. class TourismPlace {
  2.   String name;
  3.   String location;
  4.   String description;
  5.   String openDays;
  6.   String openTime;
  7.   String ticketPrice;
  8.   String imageAsset;
  9.   List<String> imageUrls;
  10.  
  11.  
  12.   TourismPlace({
  13.     this.name,
  14.     this.location,
  15.     this.description,
  16.     this.openDays,
  17.     this.openTime,
  18.     this.ticketPrice,
  19.     this.imageAsset,
  20.     this.imageUrls,
  21.   });
  22. }

Siapkan data statis yang ingin ditampilkan Anda dapat menyalin kode berikut dan taruh di berkas main_screen.dart paling bawah.

  1. var tourismPlaceList = [
  2.   TourismPlace(
  3.     name: 'Farm House Lembang',
  4.     location: 'Lembang',
  5.     description:
  6.         'Berada di jalur utama Bandung-Lembang, Farm House menjadi objek wisata yang tidak pernah sepi pengunjung. Selain karena letaknya strategis, kawasan ini juga menghadirkan nuansa wisata khas Eropa. Semua itu diterapkan dalam bentuk spot swafoto Instagramable.',
  7.     openDays: 'Open Everyday',
  8.     openTime: '09:00 - 20:00',
  9.     ticketPrice: 'Rp 25000',
  10.     imageAsset: 'images/farm-house.jpg',
  11.     imageUrls: [
  12.       'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/59/70/farmhouse-lembang.jpg',
  13.       'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/22/f6/photo3jpg.jpg',
  14.       'https://media-cdn.tripadvisor.com/media/photo-m/1280/16/a9/33/43/liburan-di-farmhouse.jpg'
  15.     ],
  16.   ),
  17.   TourismPlace(
  18.     name: 'Observatorium Bosscha',
  19.     location: 'Lembang',
  20.     description:
  21.         'Memiliki beberapa teleskop, antara lain, Refraktor Ganda Zeiss, Schmidt Bimasakti, Refraktor Bamberg, Cassegrain GOTO, dan Teleskop Surya. Refraktor Ganda Zeiss adalah jenis teleskop terbesar untuk meneropong bintang. Benda ini diletakkan pada atap kubah sehingga saat teropong digunakan, atap tersebut harus dibuka. Observatorium Bosscha boleh dikunjungi oleh siapa pun, tanpa tiket. Namun, bagi yang ingin menggunakan teleskop Zeiss, wajib mendaftarkan diri. Untuk instansi atau lembaga pendidikan, diberikan jadwal hari Selasa sampai Jumat. Sementara itu, kunjungan individu dibuka setiap hari Sabtu.',
  22.     openDays: 'Open Tuesday - Saturday',
  23.     openTime: '09:00 - 14:30',
  24.     ticketPrice: 'Rp 20000',
  25.     imageAsset: 'images/bosscha.jpg',
  26.     imageUrls: [
  27.       'https://media-cdn.tripadvisor.com/media/photo-o/12/6b/63/0b/bosscha-observatory.jpg',
  28.       'https://media-cdn.tripadvisor.com/media/photo-p/0d/6a/88/9b/photo3jpg.jpg',
  29.       'https://media-cdn.tripadvisor.com/media/photo-o/11/3f/04/39/p-20171111-110220-largejpg.jpg',
  30.     ],
  31.   ),
  32.   TourismPlace(
  33.     name: 'Jalan Asia Afrika',
  34.     location: 'Kota Bandung',
  35.     description:
  36.         'Jalan Asia Afrika di Bandung memiliki kaitan yang sangat erat dengan pendirian kota Kembang ini. Karena pada saat itu, Gubernur Jenderal Herman Willem Deaendels dari Belanda menancapkan tongkatnya saat memerintahkan pendirian kota ini, yang kemudian diabadikan menjadi tugu Bandung Nol Kilometer.',
  37.     openDays: 'Open Everyday',
  38.     openTime: '24 hours',
  39.     ticketPrice: 'Free',
  40.     imageAsset: 'images/jalan-asia-afrika.jpg',
  41.     imageUrls: [
  42.       'https://media-cdn.tripadvisor.com/media/photo-o/0d/c2/e7/e6/quotes-kota-bandung.jpg',
  43.       'https://media-cdn.tripadvisor.com/media/photo-w/17/f4/44/01/jalan-asia-afrika.jpg',
  44.       'https://media-cdn.tripadvisor.com/media/photo-s/0a/ef/36/e2/jalan-asia-afrika.jpg',
  45.     ],
  46.   ),
  47.   TourismPlace(
  48.     name: 'Stone Garden',
  49.     location: 'Padalarang',
  50.     description:
  51.         'Stone Garden atau Taman Batu di Padalarang – Bandung ini adalah nama secara harafiah untuk apa yang akan kita lihat jika berada di sana. Hamparan batu yang artistik membuat kita merasa tidak sedang berada di Bandung, apalagi di Padalarang. Hamparan batu yang dimaksud bukan terhampar begitu saja di atas tanah luas yang menjadi permukaannya. Batu-batu besar yang ukuran pastinya bervariasi tersusun seperti memiliki suatu formasi matematis.',
  52.     openDays: 'Open Everyday',
  53.     openTime: '06:00 - 17:00',
  54.     ticketPrice: 'Rp 3000',
  55.     imageAsset: 'images/stone-garden.jpg',
  56.     imageUrls: [
  57.       'https://media-cdn.tripadvisor.com/media/photo-o/15/01/d7/4b/p-20180510-153310-01.jpg',
  58.       'https://media-cdn.tripadvisor.com/media/photo-w/15/68/00/32/stone-garden-citatah.jpg',
  59.       'https://media-cdn.tripadvisor.com/media/photo-o/0d/a2/cb/05/stone-garden-citatah.jpg',
  60.     ],
  61.   ),
  62.   TourismPlace(
  63.     name: 'Taman Film Pasopati',
  64.     location: 'Kota Bandung',
  65.     description:
  66.         'Menjadi salah satu tempat wisata di Bandung yang favorit, tentu Taman Film ini memiliki fasilitas cukup memadai. Pemberian fasilitas ini memiliki harapan para pengunjung akan merasa nyaman dan tak segan2 untuk kembali berkunjung terus menerus kesini. Beberapa fasilitas taman yang bisa kamu nikmati diantaranya seperti layar videotron besar berukuran 4×8 untuk memutar berbagai macam pilihan film seperti Film Indonesia, Bollywood, Korea, ataupun Indie Bandung.',
  67.     openDays: 'Open Everyday',
  68.     openTime: '24 hours',
  69.     ticketPrice: 'Free',
  70.     imageAsset: 'images/taman-film.jpg',
  71.     imageUrls: [
  72.       'https://media-cdn.tripadvisor.com/media/photo-o/08/8b/87/50/bandung-movie-park.jpg',
  73.       'https://media-cdn.tripadvisor.com/media/photo-o/17/67/d5/53/img-20190505-114509-largejpg.jpg',
  74.       'https://media-cdn.tripadvisor.com/media/photo-w/09/73/33/05/taman-film-pasopati.jpg',
  75.     ],
  76.   ),
  77.   TourismPlace(
  78.     name: 'Museum Geologi',
  79.     location: 'Kota Bandung',
  80.     description:
  81.         'Museum Geologi didirikan pada tanggal 16 Mei 1929. Museum ini telah direnovasi dengan dana bantuan dari JICA (Japan International Cooperation Agency). Setelah mengalami renovasi, Museum Geologi dibuka kembali dan diresmikan oleh Wakil Presiden RI, Megawati Soekarnoputri pada tanggal 23 Agustus 2000. Sebagai salah satu monumen bersejarah, museum berada di bawah perlindungan pemerintah dan merupakan peninggalan nasional. Dalam Museum ini, tersimpan dan dikelola materi-materi geologi yang berlimpah, seperti fosil, batuan, mineral. Kesemuanya itu dikumpulkan selama kerja lapangan di Indonesia sejak 1850.',
  82.     openDays: 'Open Saturday - Thursday',
  83.     openTime: '09:00 - 15:30',
  84.     ticketPrice: 'Rp 3000',
  85.     imageAsset: 'images/museum-geologi.jpg',
  86.     imageUrls: [
  87.       'https://media-cdn.tripadvisor.com/media/photo-w/19/1c/8e/f7/geology-museum.jpg',
  88.       'https://media-cdn.tripadvisor.com/media/photo-o/11/a7/35/b7/geology-museum.jpg',
  89.       'https://media-cdn.tripadvisor.com/media/photo-s/1a/55/e0/dc/geology-museum.jpg',
  90.     ],
  91.   ),
  92.   TourismPlace(
  93.     name: 'Floating Market',
  94.     location: 'Lembang',
  95.     description:
  96.         'Tempat wisata ini sepertinya memang ditujukan untuk wisata keluarga di Bandung. Di sini kita bisa menikmati suasana kawasan yang tertata rapi dan alami. Pada awalnya, floating market Lembang tidak begitu luas. Tapi sekarang sudah ekspansi dan memiliki banyak objek menarik baru. Nama floating market ini sepertinya merujuk pada stand tempat jualan makanan yang berada dalam perahu.',
  97.     openDays: 'Open Everyday',
  98.     openTime: '09:00 - 17:00',
  99.     ticketPrice: 'Rp 20000',
  100.     imageAsset: 'images/floating-market.png',
  101.     imageUrls: [
  102.       'https://media-cdn.tripadvisor.com/media/photo-o/17/f9/ff/f8/floating-market-bandung.jpg',
  103.       'https://media-cdn.tripadvisor.com/media/photo-p/1a/86/d3/cd/20200103-125059-largejpg.jpg',
  104.       'https://media-cdn.tripadvisor.com/media/photo-p/19/ce/b4/9b/img20181224120857-largejpg.jpg',
  105.     ],
  106.   ),
  107.   TourismPlace(
  108.     name: 'Kawah Putih',
  109.     location: 'Ciwidey',
  110.     description:
  111.         'Kawah Putih adalah tempat wisata di Bandung yang paling terkenal. Berlokasi di Ciwidey, Jawa Barat, kurang lebih sekitar 50 KM arah selatan kota Bandung, Kawah Putih adalah sebuah danau yang terbentuk akibat dari letusan Gunung Patuha. Sesuai dengan namanya, tanah yang ada di kawasan ini berwarna putih akibat dari pencampuran unsur belerang.',
  112.     openDays: 'Open Everyday',
  113.     openTime: '07:00 - 17:00',
  114.     ticketPrice: 'Rp 15000',
  115.     imageAsset: 'images/kawah-putih.jpg',
  116.     imageUrls: [
  117.       'https://media-cdn.tripadvisor.com/media/photo-o/0b/6e/7c/ce/rocks-sticking-out-of.jpg',
  118.       'https://media-cdn.tripadvisor.com/media/photo-p/0b/35/30/14/white-crater.jpg',
  119.       'https://media-cdn.tripadvisor.com/media/photo-o/0a/8b/9a/79/2945-t00572-www-initempatwisat.jpg',
  120.     ],
  121.   ),
  122.   TourismPlace(
  123.     name: 'Ranca Upas',
  124.     location: 'Ciwidey',
  125.     description:
  126.         'Ranca Upas Ciwidey adalah kawasan bumi perkemahan di bawah pengelolaan perhutani. Tempat ini berada di kawasan wisata Bandung Selatan, satu lokasi dengan kawah putih, kolam Cimanggu dan situ Patenggang. Banyak hal yang bisa dilakukan di kawasan wisata ini, seperti berkemah, berinteraksi dengan rusa, sampai bermain di water park dan mandi air panas.',
  127.     openDays: 'Open Everyday',
  128.     openTime: '24 hours',
  129.     ticketPrice: 'Rp 20000',
  130.     imageAsset: 'images/ranca-upas.jpg',
  131.     imageUrls: [
  132.       'https://media-cdn.tripadvisor.com/media/photo-o/1a/e0/7f/9c/kampung-cai-ranca-upas.jpg',
  133.       'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/2f/87/ranca-upas.jpg',
  134.       'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/27/0a/ranca-upas.jpg',
  135.     ],
  136.   ),
  137. ];


  1. Jangan lupa untuk menambahkan import berkas tourism_place.dart pada file main_screen.dart.


    1. import 'package:wisatabandung/model/tourism_place.dart';




    Untuk berkas aset yang digunakan dapat Anda unduh pada tautan berikut.
  2. Sesuai yang telah kita pelajari pada materi ListView, kita akan menampilkan variabel tourismPlaceList di atas menjadi item card yang dapat diklik. Tambahkan widget ListView sebagai body dari Scaffold. Pindahkan widget FlatButton dan seluruh konten di dalamnya sebagai widget dari setiap item di dalam tourismPlaceList.

  1. class MainScreen extends StatelessWidget {
  2.   @override
  3.   Widget build(BuildContext context) {
  4.     return Scaffold(
  5.       appBar: AppBar(
  6.         title: Text('Wisata Bandung'),
  7.       ),
  8.       body: ListView(
  9.         children: tourismPlaceList.map((place) {
  10. return FlatButton(
  11.             onPressed: () {
  12.               Navigator.push(context, MaterialPageRoute(builder: (context) {
  13.                 return DetailScreen();
  14.               }));
  15.             },
  16.             child: Card(
  17.               child: Row(
  18.                 crossAxisAlignment: CrossAxisAlignment.start,
  19.                 children: <Widget>[
  20.                   Expanded(
  21.                     flex: 1,
  22.                     child: Image.asset(place.imageAsset),
  23.                   ),
  24.                   Expanded(
  25.                     flex: 2,
  26.                     child: Padding(
  27.                       padding: const EdgeInsets.all(8.0),
  28.                       child: Column(
  29.                         crossAxisAlignment: CrossAxisAlignment.start,
  30.                         mainAxisSize: MainAxisSize.min,
  31.                         children: <Widget>[
  32.                           Text(
  33.                             place.name,
  34.                             style: TextStyle(fontSize: 16.0),
  35.                           ),
  36.                           SizedBox(
  37.                             height: 10,
  38.                           ),
  39.                           Text(place.location),
  40.                         ],
  41.                       ),
  42.                     ),
  43.                   )
  44.                 ],
  45.               ),
  46.             ),
  47.           );
  48.         }).toList(),
  49.       ),
  50.     );
  51.   }
  52. }


  1. Jangan lupa untuk mengganti tampilan item secara dinamis sesuai data dari objek TourismPlace. Jalankan aplikasi untuk melihat hasil perubahan.
  2. Agar halaman detail bisa menampilkan informasi sesuai tempat wisata yang dipilih, kita perlu mengirimkan data TourismPlace melalui constructor. Buka berkas detail_screen.dart lalu tambahkan variabel serta constructor-nya.


    1. class DetailScreen extends StatelessWidget {

    2. final TourismPlace place;


    3. DetailScreen({@required this.place});

    4.  

    5.   @override

    6.   Widget build(BuildContext context) {...}

    7. }


    Tambahkan anotasi @required agar parameter place wajib disertakan ketika membuat objek DetailScreen.
    Sesuaikan juga informasi yang ditampilkan dengan property yang didapat dari constructor.
  1. class DetailScreen extends StatelessWidget {
  2.   final TourismPlace place;
  3.  
  4.   DetailScreen({@required this.place});
  5.  
  6.   @override
  7.   Widget build(BuildContext context) {
  8.     return Scaffold(
  9.       backgroundColor: Colors.black,
  10.       body: SingleChildScrollView(
  11.         child: Column(
  12.           crossAxisAlignment: CrossAxisAlignment.stretch,
  13.           children: <Widget>[
  14.             Image.asset(place.imageAsset),
  15.             Container(
  16.               margin: EdgeInsets.only(top: 16.0),
  17.               child: Text(
  18.                 place.name,
  19.                 textAlign: TextAlign.center,
  20.                 style: TextStyle(
  21.                   fontSize: 30.0,
  22.                   fontFamily: 'Staatliches',
  23.                 ),
  24. ),
  25.             ),
  26.             Container(
  27.               margin: EdgeInsets.symmetric(vertical: 16.0),
  28.               child: Row(
  29.                 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  30.                 children: <Widget>[
  31.                   Column(
  32.                     children: <Widget>[
  33.                       Icon(Icons.calendar_today),
  34.                       SizedBox(height: 8.0),
  35.                       Text(
  36.                         place.openDays,
  37.                         style: informationTextStyle,
  38. ),
  39.                     ],
  40.                   ),
  41.                   Column(
  42.                     children: <Widget>[
  43.                       Icon(Icons.access_time),
  44.                       SizedBox(height: 8.0),
  45.                       Text(
  46.                         place.openTime,
  47.                         style: informationTextStyle,
  48. ),
  49.                     ],
  50.                   ),
  51.                   Column(
  52.                     children: <Widget>[
  53.                       Icon(Icons.monetization_on),
  54.                       SizedBox(height: 8.0),
  55.                       Text(
  56.                         place.ticketPrice,
  57.                         style: informationTextStyle,
  58. ),
  59.                     ],
  60.                   ),
  61.                 ],
  62.               ),
  63.             ),
  64.             Container(
  65.               padding: EdgeInsets.all(16.0),
  66.               child: Text(
  67.                 place.description,
  68.                 textAlign: TextAlign.center,
  69.                 style: TextStyle(
  70.                   fontSize: 16.0,
  71.                   fontFamily: 'Oxygen',
  72.                 ),
  73. ),
  74.             ),
  75.             Container(
  76.               height: 150,
  77.               child: ListView(
  78.                 scrollDirection: Axis.horizontal,
  79.                 children: place.imageUrls.map((url) {
  80.                   return Padding(
  81.                     padding: const EdgeInsets.all(4.0),
  82.                     child: ClipRRect(
  83.                       borderRadius: BorderRadius.circular(10),
  84.                       child: Image.network(url),
  85.                     ),
  86.                   );
  87. }).toList(),
  88.               ),
  89.             ),
  90.           ],
  91.         ),
  92.       ),
  93.     );
  94.   }
  95. }

  1. Jangan lupa untuk menambahkan data place pada constructor.


    1. Navigator.push(context, MaterialPageRoute(builder: (context) {

    2.   return DetailScreen(place: place);

    3. }));


  2. Selanjutnya, kita akan menambahkan tombol navigasi untuk kembali ke halaman daftar tempat wisata. Tombol ini akan kita taruh di atas gambar utama atau gambar dari aset. Kita akan menggunakan widget Stack. Widget ini digunakan untuk menyusun widget seperti Column atau Row, bedanya widget pada Stack disusun secara bertumpuk (stacked). Ubah kode Anda menjadi seperti berikut:


    1. Stack(

    2.   children: <Widget>[

    3.     Image.asset(place.imageAsset),

    4.     IconButton(icon: Icon(Icons.arrow_back), onPressed: () {})

    5.   ],

    6. ),


    Tambahkan fungsionalitas agar ketika icon back ini diklik akan kembali ke halaman sebelumnya.


    1. IconButton(

    2.   icon: Icon(Icons.arrow_back),

    3.   onPressed: () {

    4.     Navigator.pop(context);

    5.   },

    6. ),


  3. Jika Anda jalankan aplikasi, ikon ini akan sedikit menabrak notification bar pada perangkat Android. Hal ini akan semakin terlihat apabila Anda menggunakan perangkat yang memiliki notch.
    20200615174250855d26bbda154a8513f293024270f4a2.jpeg
    Lalu bagaimana mengatasinya? Pada Flutter terdapat widget bernama Safe Area yang akan memberikan padding sesuai dengan sistem operasi yang digunakan sehingga widget akan berada di area yang aman. Kita akan memanfaatkan widget ini. Buat widgetSafeArea lalu pindahkan IconButtonke dalamnya.


    1. SafeArea(

    2.   child: IconButton(

    3.     icon: Icon(Icons.arrow_back),

    4.     onPressed: () {

    5.       Navigator.pop(context);

    6.     },

    7.   ),

    8. ),


  4. Terakhir, kita akan membuat fitur untuk menambahkan favorit. Fitur favorit ini memang belum lengkap, namun setidaknya cukup memberikan Anda gambaran bagaimana mengubah state aplikasi dan bagaimana widget dapat tampil sesuai dengan state yang ada.

    Buat StatefulWidget pada berkas detail_screen.dart. Widget ini akan kita gunakan untuk menampilkan ikon favorit.


    1. class FavoriteButton extends StatefulWidget {

    2.   @override

    3.   _FavoriteButtonState createState() => _FavoriteButtonState();

    4. }

    5.  

    6. class _FavoriteButtonState extends State<FavoriteButton> {

    7.   @override

    8.   Widget build(BuildContext context) {

    9.     return IconButton(

    10.       icon: Icon(Icons.favorite_border),

    11.       onPressed: () {},

    12.     );

    13.   }

    14. }


  5. Tambahkan variabel boolean pada class_FavoriteButtonState. Variabel ini merupakan sebuah state yang dapat berubah dan widget kita akan tampil sesuai state-nya.


    1. bool isFavorite = false;


    State isFavorite ini akan berubah ketika ikon favorit diklik. Sehingga tambahkan kode untuk mengubah variabel isFavorite. Pastikan Anda memanggil fungsi setState untuk mengubah state.


    1. onPressed: () {

    2.   setState(() {

    3.     isFavorite = !isFavorite;

    4.   });

    5. },


    Ubah ikon yang ditampilkan sesuai dengan kondisi state. Pada kode di bawah ini kita menggunakan ekspresi ternary.


    1. icon: Icon(

    2.   isFavorite ? Icons.favorite : Icons.favorite_border,

    3.   color: Colors.red,

    4. ),


  6. Tambahkan widgetFavoriteButton ini sejajar dengan icon navigasi back.


    1. SafeArea(

    2.   child: Row(

    3.     mainAxisAlignment: MainAxisAlignment.spaceBetween,

    4.     children: <Widget>[

    5.       IconButton(

    6.         icon: Icon(Icons.arrow_back),

    7.         onPressed: () {

    8.           Navigator.pop(context);

    9.         },

    10.       ),

    11.       FavoriteButton(),

    12.     ],

    13.   ),

    14. ),


    Jalankan aplikasi dan lihat perubahannya. Ketika ikon favorit diklik dan fungsi setState() dipanggil, maka method build akan kembali dijalankan dan widget akan dibuat dan ditampilkan menurut state-nya.
Selamat! Anda telah menyelesaikan seluruh codelab dan projek Wisata Bandung. Seluruh kodenya adalah seperti berikut:

main_screen.dart
import 'package:flutter/material.dart';
import 'package:wisatabandung/detail_screen.dart';
import 'package:wisatabandung/model/tourism_place.dart';


class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Wisata Bandung'),
),
body: ListView(
children: tourismPlaceList.map((place) {
return FlatButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return DetailScreen(place: place);
}));
},
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 1,
child: Image.asset(place.imageAsset),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
place.name,
style: TextStyle(fontSize: 16.0),
),
SizedBox(
height: 10,
),
Text(place.location),
],
),
),
)
],
),
),
);
}).toList(),
),
);
}
}


var tourismPlaceList = [
TourismPlace(
name: 'Farm House Lembang',
location: 'Lembang',
description:
'Berada di jalur utama Bandung-Lembang, Farm House menjadi objek wisata yang tidak pernah sepi pengunjung. Selain karena letaknya strategis, kawasan ini juga menghadirkan nuansa wisata khas Eropa. Semua itu diterapkan dalam bentuk spot swafoto Instagramable.',
openDays: 'Open Everyday',
openTime: '09:00 - 20:00',
ticketPrice: 'Rp 25000',
imageAsset: 'images/farm-house.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-s/0d/7c/59/70/farmhouse-lembang.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/f0/22/f6/photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-m/1280/16/a9/33/43/liburan-di-farmhouse.jpg'
],
),
TourismPlace(
name: 'Observatorium Bosscha',
location: 'Lembang',
description:
'Memiliki beberapa teleskop, antara lain, Refraktor Ganda Zeiss, Schmidt Bimasakti, Refraktor Bamberg, Cassegrain GOTO, dan Teleskop Surya. Refraktor Ganda Zeiss adalah jenis teleskop terbesar untuk meneropong bintang. Benda ini diletakkan pada atap kubah sehingga saat teropong digunakan, atap tersebut harus dibuka. Observatorium Bosscha boleh dikunjungi oleh siapa pun, tanpa tiket. Namun, bagi yang ingin menggunakan teleskop Zeiss, wajib mendaftarkan diri. Untuk instansi atau lembaga pendidikan, diberikan jadwal hari Selasa sampai Jumat. Sementara itu, kunjungan individu dibuka setiap hari Sabtu.',
openDays: 'Open Tuesday - Saturday',
openTime: '09:00 - 14:30',
ticketPrice: 'Rp 20000',
imageAsset: 'images/bosscha.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/12/6b/63/0b/bosscha-observatory.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0d/6a/88/9b/photo3jpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/3f/04/39/p-20171111-110220-largejpg.jpg',
],
),
TourismPlace(
name: 'Jalan Asia Afrika',
location: 'Kota Bandung',
description:
'Jalan Asia Afrika di Bandung memiliki kaitan yang sangat erat dengan pendirian kota Kembang ini. Karena pada saat itu, Gubernur Jenderal Herman Willem Deaendels dari Belanda menancapkan tongkatnya saat memerintahkan pendirian kota ini, yang kemudian diabadikan menjadi tugu Bandung Nol Kilometer.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/jalan-asia-afrika.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0d/c2/e7/e6/quotes-kota-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/17/f4/44/01/jalan-asia-afrika.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/0a/ef/36/e2/jalan-asia-afrika.jpg',
],
),
TourismPlace(
name: 'Stone Garden',
location: 'Padalarang',
description:
'Stone Garden atau Taman Batu di Padalarang – Bandung ini adalah nama secara harafiah untuk apa yang akan kita lihat jika berada di sana. Hamparan batu yang artistik membuat kita merasa tidak sedang berada di Bandung, apalagi di Padalarang. Hamparan batu yang dimaksud bukan terhampar begitu saja di atas tanah luas yang menjadi permukaannya. Batu-batu besar yang ukuran pastinya bervariasi tersusun seperti memiliki suatu formasi matematis.',
openDays: 'Open Everyday',
openTime: '06:00 - 17:00',
ticketPrice: 'Rp 3000',
imageAsset: 'images/stone-garden.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/15/01/d7/4b/p-20180510-153310-01.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/15/68/00/32/stone-garden-citatah.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0d/a2/cb/05/stone-garden-citatah.jpg',
],
),
TourismPlace(
name: 'Taman Film Pasopati',
location: 'Kota Bandung',
description:
'Menjadi salah satu tempat wisata di Bandung yang favorit, tentu Taman Film ini memiliki fasilitas cukup memadai. Pemberian fasilitas ini memiliki harapan para pengunjung akan merasa nyaman dan tak segan2 untuk kembali berkunjung terus menerus kesini. Beberapa fasilitas taman yang bisa kamu nikmati diantaranya seperti layar videotron besar berukuran 4×8 untuk memutar berbagai macam pilihan film seperti Film Indonesia, Bollywood, Korea, ataupun Indie Bandung.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Free',
imageAsset: 'images/taman-film.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/08/8b/87/50/bandung-movie-park.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/17/67/d5/53/img-20190505-114509-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/09/73/33/05/taman-film-pasopati.jpg',
],
),
TourismPlace(
name: 'Museum Geologi',
location: 'Kota Bandung',
description:
'Museum Geologi didirikan pada tanggal 16 Mei 1929. Museum ini telah direnovasi dengan dana bantuan dari JICA (Japan International Cooperation Agency). Setelah mengalami renovasi, Museum Geologi dibuka kembali dan diresmikan oleh Wakil Presiden RI, Megawati Soekarnoputri pada tanggal 23 Agustus 2000. Sebagai salah satu monumen bersejarah, museum berada di bawah perlindungan pemerintah dan merupakan peninggalan nasional. Dalam Museum ini, tersimpan dan dikelola materi-materi geologi yang berlimpah, seperti fosil, batuan, mineral. Kesemuanya itu dikumpulkan selama kerja lapangan di Indonesia sejak 1850.',
openDays: 'Open Saturday - Thursday',
openTime: '09:00 - 15:30',
ticketPrice: 'Rp 3000',
imageAsset: 'images/museum-geologi.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-w/19/1c/8e/f7/geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/11/a7/35/b7/geology-museum.jpg',
'https://media-cdn.tripadvisor.com/media/photo-s/1a/55/e0/dc/geology-museum.jpg',
],
),
TourismPlace(
name: 'Floating Market',
location: 'Lembang',
description:
'Tempat wisata ini sepertinya memang ditujukan untuk wisata keluarga di Bandung. Di sini kita bisa menikmati suasana kawasan yang tertata rapi dan alami. Pada awalnya, floating market Lembang tidak begitu luas. Tapi sekarang sudah ekspansi dan memiliki banyak objek menarik baru. Nama floating market ini sepertinya merujuk pada stand tempat jualan makanan yang berada dalam perahu.',
openDays: 'Open Everyday',
openTime: '09:00 - 17:00',
ticketPrice: 'Rp 20000',
imageAsset: 'images/floating-market.png',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/17/f9/ff/f8/floating-market-bandung.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/1a/86/d3/cd/20200103-125059-largejpg.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/19/ce/b4/9b/img20181224120857-largejpg.jpg',
],
),
TourismPlace(
name: 'Kawah Putih',
location: 'Ciwidey',
description:
'Kawah Putih adalah tempat wisata di Bandung yang paling terkenal. Berlokasi di Ciwidey, Jawa Barat, kurang lebih sekitar 50 KM arah selatan kota Bandung, Kawah Putih adalah sebuah danau yang terbentuk akibat dari letusan Gunung Patuha. Sesuai dengan namanya, tanah yang ada di kawasan ini berwarna putih akibat dari pencampuran unsur belerang.',
openDays: 'Open Everyday',
openTime: '07:00 - 17:00',
ticketPrice: 'Rp 15000',
imageAsset: 'images/kawah-putih.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/0b/6e/7c/ce/rocks-sticking-out-of.jpg',
'https://media-cdn.tripadvisor.com/media/photo-p/0b/35/30/14/white-crater.jpg',
'https://media-cdn.tripadvisor.com/media/photo-o/0a/8b/9a/79/2945-t00572-www-initempatwisat.jpg',
],
),
TourismPlace(
name: 'Ranca Upas',
location: 'Ciwidey',
description:
'Ranca Upas Ciwidey adalah kawasan bumi perkemahan di bawah pengelolaan perhutani. Tempat ini berada di kawasan wisata Bandung Selatan, satu lokasi dengan kawah putih, kolam Cimanggu dan situ Patenggang. Banyak hal yang bisa dilakukan di kawasan wisata ini, seperti berkemah, berinteraksi dengan rusa, sampai bermain di water park dan mandi air panas.',
openDays: 'Open Everyday',
openTime: '24 hours',
ticketPrice: 'Rp 20000',
imageAsset: 'images/ranca-upas.jpg',
imageUrls: [
'https://media-cdn.tripadvisor.com/media/photo-o/1a/e0/7f/9c/kampung-cai-ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/2f/87/ranca-upas.jpg',
'https://media-cdn.tripadvisor.com/media/photo-w/13/ee/27/0a/ranca-upas.jpg',
],
),
];

detail_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:wisatabandung/model/tourism_place.dart';

var informationTextStyle = TextStyle(
fontFamily: 'Oxygen',
);

class DetailScreen extends StatelessWidget {
final TourismPlace place;

DetailScreen({@required this.place});

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Stack(
children: <Widget>[
Image.asset(place.imageAsset),
SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
FavoriteButton(),
],
),
),
],
),
Container(
margin: EdgeInsets.only(top: 16.0),
child: Text(
place.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontFamily: 'Staatliches',
),
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
children: <Widget>[
Icon(Icons.calendar_today),
SizedBox(height: 8.0),
Text(
place.openDays,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
Icon(Icons.access_time),
SizedBox(height: 8.0),
Text(
place.openTime,
style: informationTextStyle,
),
],
),
Column(
children: <Widget>[
Icon(Icons.monetization_on),
SizedBox(height: 8.0),
Text(
place.ticketPrice,
style: informationTextStyle,
),
],
),
],
),
),
Container(
padding: EdgeInsets.all(16.0),
child: Text(
place.description,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
fontFamily: 'Oxygen',
),
),
),
Container(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: place.imageUrls.map((url) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(url),
),
);
}).toList(),
),
),
],
),
),
);
}
}


class FavoriteButton extends StatefulWidget {
@override
_FavoriteButtonState createState() => _FavoriteButtonState();
}


class _FavoriteButtonState extends State<FavoriteButton> {
bool isFavorite = false;


@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: Colors.red,
),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
);
}
}

tourism_place.dart
class TourismPlace {
String name;
String location;
String description;
String openDays;
String openTime;
String ticketPrice;
String imageAsset;
List<String> imageUrls;


TourismPlace({
this.name,
this.location,
this.description,
this.openDays,
this.openTime,
this.ticketPrice,
this.imageAsset,
this.imageUrls,
});
}

Anda dapat mengunduh seluruh kodenya pada tautan berikut: https://github.com/dicodingacademy/a159-flutter-pemula-labs/tree/codelab3-final
Tambahan: Flutter dikenal dengan framework-nya yang sangat mudah dalam menghadirkan tampilan yang menarik termasuk menambahkan animasi. Salah satu yang paling mudah adalah Hero Animation
Sebagai tantangan, bisakah Anda menambahkan Hero Animation pada aplikasi Anda? Dicoding sudah pernah mengulasnya dalam blog berikut: https://www.dicoding.com/blog/menerapkan-animasi-pada-project-flutter/


LihatTutupKomentar

Tutorial Cara Deployment (Build APK &amp; IPA) Aplikasi Android Atau iOS Di Flutter

Tutorial Cara Deployment Build APK Atau IPA Di Flutter Setelah melalui tahapan pengembangan aplikasi, salah satu tahapan terakhir yang perlu...