Pendahuluan
Navigasi memeliki peran yang penting dalam mengontrol jalannya sebuah aplikasi, istilah lain yang sering digunakan adalah routing. Tepatnya pada Flutter atau aplikasi mobile, navigasi digunakan untuk berpindah antar screen yang satu dengan screen lainnya. Jika anda terbiasa dengan aplikasi web maka istilah screen sama halnya dengan halaman sebuah web.
Dalam setiap perpindahan juga terkadang kita harus mengirimkan data agar dapat digunakan pada screen selanjutnya. Maka pada serial kali ini kita akan belajar bagaimana menggunakan navigasi serta cara passing data ke halaman selanjutnya yang sedang dituju. Selain itu kita juga akan belajar bagaimana menggunakan ListView, bagaimana menampilkan gambar serta mencari data berdasarkan id yang dimiliki.
Baca Juga: Belajar Flutter Basic #6: Stateless & Stateful Widget
View List Categories
Bagian pertama sekaligus pembuka adalah kita akan membuat sebuah halaman untuk menampilkan data kategori dari pariwisata yang ada. Adapun hasil yang akan dicapai akan terlihat seperti gambar dibawah ini.
Pastikan kamu telah meng-install Flutter dengan command:
flutter create dw_pariwisata
Kemudian buat project tersebut dan buka file main.dart
lalu modifikasi menjadi:
import 'package:flutter/material.dart';
import './screens/category_screen.dart'; //import file categoryscreen yang nantinya berfungsi untuk menampilkan list kategori
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Aplikasi Pariwisata',
//KITA SET THEME-NYA SECARA UMUM AGAR KETIKA MELAKUKAN PERUBAHAN, CUKUP PADA SETTING DIBAWAH AKAN MEMBERIKAN EFEK KE SEMUA PAGE YANG ADA
theme: ThemeData(
primaryColor: Colors.pinkAccent, //WARNA UTAMA KITA GUNAKAN pinkAccent
accentColor: Colors.pink, //SECONDARY COLORNYA KITA SET KE PINK
canvasColor: Color.fromRGBO(255, 254, 229, 1), //WARNA BACKGROUND CANVASNYA PAKAI RGBO DIMANA CODE DIATAS AKAN MENGHASILKAN WARNA KUNING
//SET JUGA KONFIGURASI UNTUK TEXT-NYA
textTheme: ThemeData.light().textTheme.copyWith(
title: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), //TITLE KITA GUNAKAN BOLD DAN SIZE 20
subhead: TextStyle(fontWeight: FontWeight.bold), //SUBHEADNYA CUKUP BOLD SAJA
)
),
//SET ROUTING YANG AKAN MENGATUR ALUR APLIKASI
routes: {
'/': (ctx) => CategoryScreen() //DIMANA ROOT ROUTINGNYA AKAN ME-LOAD CategoryScreen
// TANDA / BERARTI SECARA DEFAULT KETIKA APLIKASI DI-LOAD MAKA ROUTE TERSEBUT AKAN DIJALANKAN
},
);
}
}
Selanjut adalah membuat file category_screen.dart
dan tempatkan di dalam folder lib/screens
, kemudian masukkan code berikut:
import 'package:flutter/material.dart';
import '../components/list_category.dart'; //IMPORT FILE list_category.dart YANG BERFUNGSI SEBAGAI LAYOUT UNTUK TIAP ITEM CATEGORY YANG AKAN DITAMPILKAN
import '../dummy_data.dart'; //FILE INI BERISI DATA DUMMY CATEGORY
class CategoryScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Daengweb Pariwisata"), //SET TEXT PADA APPBAR
),
//KITA AKAN MENGGUNAKAN GRIDVIEW AGAR TAMPILANNYA SESUAI NAMANYA AKAN MEMBENTUK GRID SESUAI BESARAN ELEMEN YANG DITAMPILKAN
body: GridView(
padding: EdgeInsets.all(15), //SET PADDINGNYA AGAR TIDAK TERLALU KE TEPI SCREEN
//ADAPUN CHILDRENNYA ADALAH HASIL LOOPING DARI DATA DUMMY CATEGORY
//DIMANA SETIAP DATANYA AKAN ME-LOAD COMPONENT ListCategory SEKALIGUS MENGIRIMKAN DATA YANG AKAN DITAMPILKAN
children: CATEGORIES_DUMMY_DATA
.map((cat) => ListCategory(cat.id, cat.title, cat.description, cat.image))
.toList(),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300, //MENGATUR BESARNYA ELEMEN GRID PER ITEMNYA,
childAspectRatio: 1.5, //MENGATUR ASPEK RASIO
crossAxisSpacing: 20, //MENGATUR JARAK ELEMENT SECARA HORIZONTAL
mainAxisSpacing: 20, //MENGATUR JARAK ELEMENT SECARA VERTICAL
),
),
);
}
}
Sampai pada tahap ini kita masih menemukan error karena ada dua buah file yang dibutuhkan, maka mari kita buat satu persatu. Pertama buat file dummy_data.dart
di dalam folder lib
dan tambahkan code:
import './models/category.dart';
const CATEGORIES_DUMMY_DATA = const [
Category(
id: 'P1',
title: 'Pantai',
description: '',
image: 'http://pluspng.com/img-png/the-beach-png-black-and-white--952.png',
),
Category(
id: 'P2',
title: 'Gunung',
description: '',
image: 'https://images.vexels.com/media/users/3/137476/isolated/preview/d89adf16dc6fce8b9abe54aec3af2546-four-peak-mountain-icon-by-vexels.png',
),
Category(
id: 'P3',
title: 'Budaya',
description: '',
image: 'https://upload.wikimedia.org/wikipedia/commons/f/f8/Drama-icon.png',
),
Category(
id: 'P4',
title: 'Kuliner',
description: '',
image: 'https://cdn3.iconfinder.com/data/icons/vacation-line-1/48/culinary_restaurant_food_vacatipn_traveling-512.png',
),
Category(
id: 'P5',
title: 'Religi',
description: '',
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Religion_icon.svg/1138px-Religion_icon.svg.png',
),
Category(
id: 'P6',
title: 'Edukasi',
description: '',
image: 'https://upload.wikimedia.org/wikipedia/commons/e/e8/Education%2C_Studying%2C_University%2C_Alumni_-_icon.png',
),
];
Code diatas hanyalah sebuah data dummy yang akan ditampilkan dimana struktur datanya diatur oleh model category.dart
, buat file tersebut dan letakkan di dalam folder lib/models
:
import 'package:flutter/foundation.dart';
class Category {
//DEFINISIKAN SELURUH VARIABLE YANG DIINGINGKAN
final String id;
final String title;
final String description;
final String image;
//KEMUDIAN BUAT CONSTRUCTOR UNTUK MEMINTA DATA TERSEBUT
//KITA MENGGUNAKAN REQUIRED MAKA DIPERLUKAN UNTUK MENGIMPORT foundation.dart
const Category({
@required this.id,
@required this.title,
@required this.description,
@required this.image
});
}
Tugas selanjutnya adalah membuat component list_category.dart
yang berisi view untuk menampilkan data category yang telah dikirimkan oleh GridView dari file category_screen.dart
, kemudian letakkan file tersebut di dalam folder lib/components
dan tambahkan code:
import 'package:flutter/material.dart';
class ListCategory extends StatelessWidget {
//KARENA ADA DATA YANG DIKIRIM DARI FILE YANG MEMANGGILNYA MAKA KITA PERLU MENDEFINISIKAN DATA YANG AKAN DITERIMA
final String id;
final String title;
final String description;
final String imageUrl;
//KEMUDIAN BUAT CONSTRUCTOR UNTUK MENERIMA DATA TERSEBUT
ListCategory(this.id, this.title, this.description, this.imageUrl);
//METHOD INI AKAN BERJALAN KETIKA TOMBOL KATEGORI DI TAP (KLIK)
void goToNewScreen(BuildContext context) {
//FUNGSI NAVIGASINYA AKAN ADA DISINI TAPI AKAN DIBAHAS KEMUDIAN JADI TOLONG DIINGAT
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => goToNewScreen(context), //KETIKA DITAP MAKA MEMANGGIL METHOD DIATAS
highlightColor: Colors.white.withAlpha(30),
splashColor: Colors.white.withAlpha(20),
child: Card(
elevation: 5,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
//BAGIAN PERTAMA DARI CARD KITA AKAN MENAMPIKKAN GAMBAR
Padding(
padding: EdgeInsets.only(top: 10),
//DIMANA GAMBARNYA DIAMBIL DARI URL YANG TELAH DIDEFINISIKAN PADA DATA DUMMY
child: Image.network(
imageUrl,
height: 65,
width: double.infinity, //BESAR GAMBARNYA SEBESAR MUNGKIN DARI ELEMEN YG TERSEDIA
),
),
//ANTARA GAMBAR DAN TEXT KITA BUAT JARAK MENGGUNAKAN SIZEDBOX
SizedBox(
height: 4,
),
//TEXT UNTUK MENAMPILKAN NAMA KATEGORINYA
Center(
child: Text(title, style: Theme.of(context).textTheme.title))
],
),
),
),
);
}
}
View List Places
Ketika kategori pada screen kategori di tap (klik), maka akan berpindah pada screen selanjutnya yang berfungsi untuk menampilkan data tempat wisata yang berada pada kategori terkait. Tugas kita pada bagian ini adalah meng-handle data places yang akan ditampilkan menggunakan ListView.
Agar masih berkaitan pada file diatas, dimana masih ada method yang belum selesai perintah navigasinya, maka hal pertama yang akan dilakukan adalah dengan membuat file places_screen.dart
dan tempat file tersebut di dalam folder lib/places
. Masukkan code berikut:
import 'package:flutter/material.dart';
import '../dummy_data.dart'; //FILE DUMMY INI MASIH DIPERLUKAN KARENA DI DALAMNYA AKAN DITAMBAHKAN DATA DUMMY UNTUK PLACES
import '../components/list_place.dart'; //SAMA DENGAN YANG SEBELUMNYA, COMPONENT INI BERISI VIEW UNTUK MENAMPILKAN SETIAP TEMPAT WISATA
class PlacesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
//FUNGSI INI UNTUK MENGAMBIL ARGUMEN YANG DIKIRIMKAN MELALUI NAVIGATION KETIKA KATEGORI DI TAP
final route =
ModalRoute.of(context).settings.arguments as Map<String, String>;
//DIMANA ARGUMEN YANG DIKIRIMKAN BERISI KUMPULAN DATA YANG DIDALMNYA ADALAH KEY DAN VALUE
final id = route['id']; //KITA AMBIL VALUE DARI KEY ID CATEGORY
final title = route['title']; //VALUE DARI TITLE CATEGORY
//KEMUDIAN BUAT LOGIC UNTUK MENGAMBIL DATA TEMPAT BERDASARKAN ID CATEGORI DAN DITEMPATKAN KE DALAM VARIABLE placeLists
final placeLists = PLACES_DUMMY_DATA.where((place) {
return place.category == id;
}).toList();
return Scaffold(
appBar: AppBar(
title: Text("Wisata " + title), //BUAT APPBAR LAGI DIMANA TITLE DIAMBIL DARI TITLE DIATAS
),
//KEMUDIAN LOOPING DATANYA MENGGUNAKAN LISTVIEW BUILDER
body: ListView.builder(
itemBuilder: (ctx, index) {
//LOAD COMPONENT LISTPLACE DENGAN MENGIRIMKAN DATA YANG AKAN DITAMPILKAN
return ListPlace(
placeLists[index].id,
placeLists[index].name,
placeLists[index].image,
placeLists[index].description,
);
},
itemCount: placeLists.length, //TOTAL DATA YANG AKAN LOOPING
),
);
}
}
Sebelum melangkah lebih lanjut, kembali ke file components/list_category.dart
dan modifikasi method yang tadi dikosongkan menjadi:
void goToNewScreen(BuildContext context) {
//FUNGSI INI AKAN MEMBERIKAN INSTRUKSI UNTUK MEMBUAT SCREEN PLACES YANG NNTINYA AKAN DIDEFINSIKAN ROUTINGNYA DAN MENGIRIMKAN ARGUMENT BERUPA ID, TITLE DAN DESCRIPTION
Navigator.of(context).pushNamed('/places', arguments: {'id': id, 'title': title, 'description': description});
}
Kemudian definisikan routing /places
dengan membuka file main.dart
dan tambahkan code berikut:
routes: {
'/': (ctx) => CategoryScreen(),
'/places': (ctx) => PlacesScreen() //TAMBAHKAN BAGIAN INI
},
Masih dengan file yang sama, jangan lupa import PlacesScreen pada bagian import statement:
import './screens/places_screen.dart';
Melanjutkan fitur untuk menampilkan data tempat wisata, maka bagian yang akan diselesaikan kali ini adalah data dummy tempat wisata. Buka file dummy_data.dart
(red: kita satukan file-nya dengan dummy data kategori) dan tambahkan code:
import './models/places.dart';
const PLACES_DUMMY_DATA = const [
Places(
id: 'A1',
name: 'Pantai Bira Bulukumba',
description: 'Tanjung bira terletak di daerah Sulawesi Selatan. Lokasi khususnya adalah Kabupaten Bulukumba yang jaraknya sangat jauh, sekitar 200 km dari pusat ibukota Makassar. Sehingga tanjung bira bisa diaktakan terletak di ujung selatan daratan sulawesi selatan. Tanjung bira memang bisa dikatakan senjata oleh masyarakat sekitar untuk menarik masyarakat luar dan wisatawan baik lokal maupun asing untuk datang ke sana. Tanjung bira tentu saja menawarkan hal seperti pantai putih, alam bawah laut dan juga pemandangan senja yang tidak ada duanya.',
author: 'Nuge',
category: 'P1',
image: "https://www.alorinatours.co.id/wp-content/uploads/2018/07/Pulau-Liukang.jpg",
price: 50000
),
Places(
id: 'A2',
name: 'Pantai Bara Bulukumba',
description: 'Soal indah, Pantai Bara dan Pantai Bira ibarat saudara kembar. Selain namanya yang mirip, letaknya sama-sama di Bulukumba, Sulsel. Pantai pasir putih nan lembut dan laut biru ada di sini. Pantai Bara memang belum setenar Tanjung Bira. Meski demikian, pemandangan di sini tak kalah menarik. Hamparan pasir putih dipadukan dengan tenangnya laut dari arah Flores, membuat kita betah berlama-lama di Pantai Bara.',
author: 'Riski',
category: 'P1',
image: "https://media.travelingyuk.com/wp-content/uploads/2019/05/Pantai-Bara-di-Bulukumba.jpg",
price: 20000
),
Places(
id: 'A3',
name: 'Ke\'te Kesu Toraja',
description: 'Di sini kamu dapat menemukan banyak rumah tongkonan yang telah berdiri semenjak zaman leluhur. Di atas bagian pintu masuk kamu dapat menemukan kepala kerbau yang ditempelkan di dinding. Dalam budaya masyarakat Toraja, hal tersebut memiliki arti kemakmuran, kejayaan, dan status sosial.',
author: 'Riski',
category: 'P3',
image: "https://www.panentour.com/wp-content/uploads/2019/03/3D-TORAJA-MAKASSAR-PARE-PARE-2-1024x578.jpg",
price: 15000
),
Places(
id: 'A4',
name: 'Ammatoa Kajang Bulukumba',
description: 'Suku Kajang Ammatoa terletak di kabupaten Bulukumba, Kecamatan Kajang, Sulawesi Selatan. Desa ini dinamakan Tana Toa yang merupakan tanah yang tertua di dunia dikarenakan kepercayaan masyarakat adatnya. Secara geografis, luas wilayah Desa Kajang Ammatoa sekitar 331,17 ha dan memiliki kondisi hutan yang sangat lebat. Hampir seluruh dusun yang berada di dalamnya di kelilingi hutan dan tidak ada jalan beraspal di dalam kawasan ini.',
author: 'Nuge',
category: 'P3',
image: "https://ammatoa.com/wp-content/uploads/2016/09/kajang.jpg",
price: 24000
),
];
Note: Saya hanya memasukkan tempat untuk kategori Pantai (P1) dan Budaya (P3) agar tidak terlalu panjang, jadi kalau mau ditambahkan sendiri silahkan.
Buat model places.dart
di dalam folder lib/models
dan tambahkan code untuk struktur data yang diinginkan:
import 'package:flutter/foundation.dart';
class Places {
//DEFINISIKAN SEMUA DATA YANG DIPERLUKAN
final String id;
final String name;
final String category;
final String description;
final String image;
final String author;
final int price;
//MINTA DATA TERSEBUT DENGAN MENAMBAHKAN REQUIRED AGAR KETIKA MODEL DILOAD DATANYA WAJIB ADA
const Places({
@required this.id,
@required this.name,
@required this.category,
@required this.description,
@required this.image,
@required this.author,
@required this.price
});
}
Terakhir buat file list_place.dart
di dalam folder lib/components
dan tambahkan code:
import 'package:flutter/material.dart';
class ListPlace extends StatelessWidget {
//DEFINISIKAN VARIABLE UNTUK MENAMPUNG DATA YANG DITERIMA
final String id;
final String name;
final String image;
final String description;
//MENERIMA DATA YANG DIKIRIMKAN MELALUI CONSTRUCTOR
ListPlace(this.id, this.name, this.image, this.description);
void goToDetail(BuildContext context) {
//Kosongkan lagi bagian ini untuk saat ini
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 5,
child: Padding(
padding: EdgeInsets.only(top: 15, bottom: 15),
//MENGGUNAKAN LIST TILE UNTUK MENAMPILKAN DATA TEMPATNYA
child: ListTile(
leading: Image.network(image), //BAGIAN KIRI ADALAH ME-LOAD GAMBAR
title: Text(name), //BAGIAN TENGAH AKAN MENAMPIKAN NAMA TEMPAT WISATANYA
subtitle: Text(description.substring(0, 100) + '...'), //DIBAWAH TITLE AKAN MENAMPILKAN DESKRIPSI SINGKAT YANG DILIMIT HANYA 100 HURUF
onTap: () => goToDetail(context), //KETIKA DITAP AKAN MEJALAN METHOD goToDetail()
),
),
);
}
}
View Detail Place
Ketika salah satu dari list tempat wisata di tap maka akan diarahkan ke halaman detail untuk melihat data tempat wisata tersebut secara lebih detail mencakup deskripsi yang lengkap, gambar yang lebih besar, beserta biaya masuknya.
Buat file detail_screen.dart
di dalam folder lib/screens
dan tambahkan code:
import 'package:flutter/material.dart';
import '../dummy_data.dart';
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
//TERIMA DATA DARI ARGUMEN, KARENA ARGUMEN YANG AKAN DIKIRIMKAN DATANYA HANYA SEBUAH STRING BIASA MAKA DARI .arguments LANGSUNG AKAN BERISI DATA TERSEBUT
final id = ModalRoute.of(context).settings.arguments as String;
//KEMUDIAN MENCARI DATA PLACE DARI DATA DUMMY BERDASARKAN ID MENGGUNAKAN FIRSTWHERE()
final selectedPlace =
PLACES_DUMMY_DATA.firstWhere((place) => place.id == id);
return Scaffold(
appBar: AppBar(
title: Text("Detail Informasi"),
),
//KITA GUNAKAN SingleChildScrollView() AGAR HALAMAN BISA DI SCROLL
body: SingleChildScrollView(
child: Column(
children: <Widget>[
//BAGIAN PERTAMA ADALAH FUNGSI UNTUK ME-LOAD GAMBAR
Stack(
children: <Widget>[
//DIMANA PADA SISI BAWAH KANAN DAN KIRIM AKAN DIBUAT MELENGKUNG
//MENGGUNAKAN BORDERRADIUS
ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
//LOAD GAMBARNYA DARI INTERNET
child: Image.network(
selectedPlace.image,
width: double.infinity,
height: 250,
fit: BoxFit.cover,
),
),
//BAGIAN INI AKAN MENAMPILKAN NAMA TEMPAT WISATA
//KITA GUNAKAN POSITIONED KARENA AKAN MENAMPILKAN TEKSNYA DI DALAM GAMBAR
//AGAR LEBIH MUDAH DI ATUR POSISINYA
Positioned(
bottom: 20, //JARAKNYA 20 DARI BAWAH
right: 15, //DAN 15 DARI KANAN
child: Container(
width: 300,
color: Colors.black54,
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//KEMUDIAN KITA TAMPILKAN NAMA TEMPAT WISATANYA
Text(
selectedPlace.name,
style: TextStyle(fontSize: 26, color: Colors.white),
softWrap: true,
overflow: TextOverflow.fade,
),
//DIBAWAH TEKSNYA KITA TAMPILKAN NAMA PENULISNYA / INFORMATORNYA
Text(
"Author: " + selectedPlace.author,
style: TextStyle(fontSize: 15, color: Colors.grey),
softWrap: true,
overflow: TextOverflow.fade,
)
],
),
),
)
],
),
//BUAT JARAK DENGAN SIZEDBOX
SizedBox(
height: 4,
),
//BUAT CARD LAGI UNTUK MENAMPILKAN HARGA TIKET MASUK
Card(
margin: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Icon(Icons.money_off, size: 40,),
Text("Tiket Masuk: Rp ${selectedPlace.price}", style: Theme.of(context).textTheme.title,)
],
),
),
//BUAT CARD LAGI UNTUK MENAMPILKAN DESCRIPTION
Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: Padding(
padding: EdgeInsets.all(10),
child: Text(selectedPlace.description),
),
)
],
),
),
);
}
}
Kembali ke file sebelumnya yang belum selesai fungsi untuk navigation-nya, buka file list_place.dart
dan modifikasi method yang dikosongkan menjadi:
void goToDetail(BuildContext context) {
//BERPINDAH KE SCREEN /DETAIL DENGAN MENGIRIMKAN ARGUMEN ID
Navigator.of(context).pushNamed('/detail', arguments: id);
}
Kemudian buka file main.dart
dan definisikan routes-nya dengan menambahkan line:
routes: {
'/': (ctx) => CategoryScreen(),
'/places': (ctx) => PlacesScreen(),
'/detail': (ctx) => DetailScreen() //TAMBAHKAN LINE INI
},
Masih dengan file yang sama, jangan lupa tambahkan import statement:
import './screens/detail_screen.dart';
Baca Juga: Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #7: Push Notification Expenses
Kesimpulan
Banyak hal yang diperolah dari serial ini dimana kita telah belajar bagaimana berpindah screen menggunakan Navigator, mengirimkan arguments setiap kali berpindah screen dimana pada argument tersebut bisa berisi kumpulan data ataupun hanya sebuah string (single data). Tidak hanya itu tapi pada screen yang dituju juga telah memperlihat bagaimana cara menerima data yang dikirimkan dari screen sebelumnya.
Selain itu kita juga belajar menampilkan list data menggunakan GridView maupun ListView, dimana keduanya memiliki fungsi yang serupa tapi tak sama. Adapun penggunaannya keduanya tergantung kebutuhan dan view yang diinginkan.
Adapun dokumentasi code dari artikel ini bisa kamu lihat di Github.
Comments