Pendahuluan
Cara menarik dan efektif yang sebaiknya digunakan dalam belajar bahasa pemrograman adalah dengan membiasakan diri menyelesaikan sebuah case secara bertahap, mulai dari yang sederhana hingga aplikasi yang cukup kompleks. Maka pada artikel kali ini kita akan belajar membuat aplikasi Tracking resi dari salah satu ekspedisi yang sering digunakan di Indonesia.
Berinteraksi dengan API yang dimiliki oleh ekspedisi tersebut akan mengajarkan kita bagaimana cara meminta data dari server, menerima datanya dan melakukan formatting agar dapat digunakan sesuai dengan kebutuhan.
Adapun schema yang diinginkan adalah menampilkan 3 buah layer, pertama adalah layer form input beserta tombol cari, kedua adalah card untuk menampilkan informasi jika resi ditemukan dan yang terakhir adalah card untuk menampilkan informasi jika resi tidak ditemukan.
Baca Juga: Belajar Flutter Basic #8: State Management HTTP Request
Create Layout Tracking
Langkah awal yang akan dilakukan adalah membuat layout sebelum memasukkan perintah berdasarkan fungsinya masing-masing. Pertama install Flutter terlebih dahulu dengan command:
flutter create dw_tracking_resi
Tunggu hingga proses instalasi selesai, kemudian buka project-nya dan modifikasi file lib/main.dart
menjadi:
import 'package:flutter/material.dart';
import './silincah.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Daengweb.id',
theme: ThemeData(
primarySwatch: Colors.pink,
),
home: ResiSilincah(),
);
}
}
Note: Code diatas hanya berisi material app yang telah di-set untuk me-load class ResiSilincah()
.
Buat file silincah.dart
di dalam folder lib
dan tambahkan code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import './detail.dart';
class ResiSilincah extends StatefulWidget {
@override
_ResiSilincahState createState() => _ResiSilincahState();
}
class _ResiSilincahState extends State<ResiSilincah> {
//DEFINISIKAN VARIABLE
String noResi = '';
String layanan = '';
String name = '';
String status = '';
String tujuan = '';
String date = '';
String penerima = '';
//VARIABLE DENGAN TIPE BOOLEAN
bool loading = false;
bool showDetail = false;
bool isNotFound = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(leading: Icon(Icons.art_track), title: Text('Daengweb Resi')),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, //WIDGET YANG ADA DI DALAM COLUMN AKAN DI SET WIDTHNYA SEJAUH YG BISA DIJANGKAU
children: <Widget>[
// FORM INPUT PENCARIAN RESI DISINI
Divider(),
//KETIKA VARIABLE LOADING BERNILAI TRUE
loading
? Center(
child: CircularProgressIndicator(), //MAKA AKAN MENAMPILKAN INDIKATOR LOADING
)
//JIKA BERNILAI FALSE
: showDetail //DI CEK LAGI, JIKA SHOWDETAIL BERNILAI TRUE
? Detail(
noResi, layanan, name, status, tujuan, date, penerima) //MAKA COMPONENT DETAIL DI RENDER
//JIKA SHOW DETAIL BERNILAI FALSE
: isNotFound //DI CEK LAGI, JIKA IS NOT FOUND TRUE
? Card( //MAKA KITA TAMPILKAN CARD YANG BERISI INFORMASI BAHWA RESI TIDAK DITEMUKAN
elevation: 4,
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Nomor Resi Tidak Ditemukan", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
),
),
)
: Center() //SELAIN ITU HANYA MERENDER WIDGET CENTER
],
),
),
),
);
}
}
Note: Comment FORM INPUT PENCARIAN RESI DISINI
nantinya akan di-replace dengan widget textfield dan button.
Bagian terakhir dari layouting adalah membuat custom Widget yang berisi card untuk menampilkan detail informasi apabila resi ditemukan. Didalam folder yang sama, buat file detail.dart
dan tambahkan code:
import 'package:flutter/material.dart';
class Detail extends StatelessWidget {
//DEFINISIKAN VARIABLE YANG SAMA
String noResi = '';
String layanan = '';
String name = '';
String status = '';
String tujuan = '';
String date = '';
String penerima = '';
//CONSTRUCTOR YANG BERARTI KETIKA ME-LOAD FILE INI, DIA AKAN MEMINTA DATA
Detail(this.noResi, this.layanan, this.name, this.status, this.tujuan, this.date, this.penerima);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile( //DETAIL INFORMASINYA KITA GUNAKAN LISTTILE
title: Text( //PADA BAGIAN TITLE DIGUNAKAN UNTUK MENAMPILKAN RESI DAN JENIS LAYANANNYA
'${noResi} - $layanan',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
subtitle: Padding(
//SEDANGKAN PADA BAGIAN SUBTITLE (TEPAT DIBAWAH TITLE), KITA TAMPILKAN INFORMASI LAINNYA
padding: const EdgeInsets.only(top: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Divider(),
//BAGIAN PERTAMA ADALAH NAMA PENERIMA
Text(
'Nama: $name',
style: TextStyle(
color: Colors.black87, fontSize: 13),
),
//KEMUDIAN STATUS PENGIRIMAN
Text(
'Status: $status',
style: TextStyle(
color: Colors.black87, fontSize: 13),
),
//TUJUAN PENGIRIMAN
Text(
'Tujuan: $tujuan',
style: TextStyle(
color: Colors.black87, fontSize: 13),
),
//TANGGAL BARANG DITERIMA
Text(
'Tgl Terima: $date',
style: TextStyle(
color: Colors.black87, fontSize: 13),
),
//DAN SIAPA NAMA PENERIMANYA
Text(
'$penerima',
style: TextStyle(
color: Colors.black87, fontSize: 13),
),
],
),
),
),
),
);
}
}
Form Input & HTTP Request
Setelah menyelesaikan layout yang akan menampilkan informasi baik resi ditemukan atau tidak ditemukan, maka tugas selanjutnya adalah membuat form input yang berfungsi untuk memasukkan nomor resi yang akan dicari. Buka file silincah.dart
dan tambahkan code berikut tepat pada komentar form input
Container(
padding: const EdgeInsets.all(8),
child: TextField(
focusNode: _focusNode,
controller: _resiController, //UNTUK MENG-HANDLE VALUENYA KITA GUNAKAN CONTROLLER
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Masukkan Resi',
labelText: 'Nomor Resi'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: MaterialButton(
child: Text("Cari"),
highlightElevation: 2,
color: Colors.redAccent,
textColor: Colors.white,
onPressed: cekResi, //KETIKA TOMBOLNYA DITEKAN, MAKA AKAN MENJALANKAN FUNGSI cekResi
),
),
Masih dengan file yang sama, kita buat initState serta controller dan focus node, kemudian letakkan diluar dari Widget build()
final TextEditingController _resiController = TextEditingController();
FocusNode _focusNode;
@override
void initState() {
_focusNode = FocusNode();
_focusNode.addListener(() {
if (_focusNode.hasFocus) _resiController.clear();
});
super.initState();
}
Tugas selanjutnya adalah membuat method cekResi()
untuk meng-handle proses http request ke API ekspedisi terkait. Tentu saja dengan file yang sama tambahkan code:
void cekResi() async {
setState(() {
//RESET SHOW DETAIL DAN ISNOTFOUND MENJADI FALSE
showDetail = false;
isNotFound = false;
loading = true; //SET LOADING JADI TRUE AGAR LOADING INDICATOR DI RENDER
});
//KEMUDIAN BUAT HEADERS DIMANA TIPENYA ADALAH MAP DENGAN KEY STRING DAN VALUE STRING
//HEADER INI DIGUNAKAN KETIKA MENGIRIM REQUEST KE API UNTUK MENGHINDARI BLOCKING DARI API TERKAIT
Map<String, String> requestHeaders = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9',
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Host': 'sicepat.com',
'Origin': 'http://sicepat.com',
'Referer': 'http://sicepat.com',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Cookie':
'__cfduid=d4a82874e43363ef776bd20169c2e37ca1568383926; ci_session=nphsvktt6jl68lu74er1osttbfc4ct9d',
'X-Requested-With': 'XMLHttpRequest'
};
//BUAT JUGA VARIABLE LAINNYA DENGAN TIPE MAP DAN KEY STRING, VALUE STRING
//DATA INI DIGUNAKAN SEBAGAI BODY KETIKA MENGIRIMKAN REQUEST KE API
Map<String, String> resi = {'awb[]': _resiController.text};
//LAKUKAN REQUEST KE API DENGAN MENYERTAKAN BODY = RESI DAN HEADERS = REQUESTHEADERS
final response = await http.post('http://sicepat.com/checkAwb/doSearch',
body: resi, headers: requestHeaders);
//DECODE HASILNYA DAN HANYA MENGAMBIL VALUE DARI KEY HTML
final content = json.decode(response.body)['html'];
//KEMUDIAN VALUE DARI CONTENT KITA REMOVE BEBERAPA BAGIAN YANG TIDAK DIBUTUHKAN DENGAN BANTUAN METHOD BARU YANG BERNAMA removeHeading() DIMANA METHOD INI AKAN KITA BUAT KEMUDIAN
final withoutHtml = removeHeading(content);
setState(() {
loading = false; //SET LOADING JADI FALSE KARENA PROSES REQUESTNYA SDH BERHASIL
if (withoutHtml.length > 1) { //JIKA RESINYA ADA
//MAKA DATA DARI API TERSEBUT KITA ASSIGN KE DALAM VARIABLE YANG TELAH DIBUAT SEBELUMNYA
//KARENA DATANYA MENGGUNAKAN FORMAT HTML, MAKA KITA PERLU MEMECAH DATANYA DAN MENGHILANGKAN HTML CODENYA
//KITA JUGA AKAN MEMBUAT METHOD BARU BERNAMA withoutHtml YANG BERFUNGSI UNTUK MEMECAH DATA
noResi = explodeItem(withoutHtml[1], '<div class="visible-xs">', 0);
layanan = explodeItem(withoutHtml[1], '<div class="visible-xs">', 1);
name = explodeItem(withoutHtml[4], '<td class="hidden-xs">', 1);
status = explodeItem(withoutHtml[7], '<td>', 1);
tujuan = explodeItem(withoutHtml[3], '<div class="visible-xs">', 0);
date = explodeItem(withoutHtml[5], '<div class="visible-xs">', 0);
penerima = explodeItem(withoutHtml[5], '<div class="visible-xs">', 1);
showDetail = true; //SET SHOW DETAIL JADI TRUE AGAR CARD INFORMASI RESI DIRENDER
} else {
isNotFound = true; //IJKA RESI TIDAK DITEMUKAN, SET IS NOT FOUND JADI TRUE
}
FocusScope.of(context).nextFocus(); //HILANGKAN FOCUS DARI TEXT FIELD
_resiController.clear(); //DAN BERSIHKAN VALUENYA
});
return;
}
Ada dua method bantuan yang akan meng-handle data dari API karena data tersebut tidak menggunakan format json. Tambahkan kedua method tersebut setelah method cekResi()
List removeHeading(String htmlText) { //METHOD PERTAMA NILAI BALIKNYA DENGAN FORMAT LIST (ARRAY)
//METHOD INI MENERIMA VALUE DARI RESPONSE API, KEMUDIAN CODE HTML DIBAWAH DIHAPUS MENGGUNAKAN REPLACE ALL
final removeHeading = htmlText.replaceAll(
'<div class=\"awb-detail-title text-center\"> <div class=\"container\"> Status Pengiriman Anda </div> </div> <div class=\"awb-detail-sub-title text-center\"> <div class=\"container\"> Terimakasih telah menggunakan pengiriman SiCepat. Silahkan cek daftar pengiriman Anda </div> </div><span class=\"awb-click-info\">Silahkan klik salah satu baris untuk melihat detail pengiriman.</span> <div class=\"table-responsive\"><table id=\"awb-list\" class=\"table table-striped nowrap ws-table\"\n cellspacing=0 width=\"100%\"> <thead><tr> <th id=\"seq\" class=\"hidden-xs\">No</th> <th id=\"awb_number\" class=\"\">No RESI</th> <th id=\"service\" class=\"hidden-xs\">Layanan</th> <th id=\"destination\" class=\"\">Tujuan</th> <th id=\"receipt_name\" class=\"hidden-xs\">Penerima</th> <th id=\"receipt_date\" class=\"\">Tgl Diterima</th> <th id=\"receipt_paket\" class=\"hidden-xs\">Penerima Paket</th> <th id=\"status\" class=\"\">STATUS</th></tr> </thead><tbody><tr class=\"res-item\"> ',
'');
//SETELAH BAGIAN YANG TIDAK DIPERLUKAN DIHAPUS, KITA PECAH DATANYA MENGGUNAKAN SPLIT DENGAN </TD> SEBAGAI PARAMETERNYA
final splitTd = removeHeading.split('</td>');
return splitTd.toList(); //KEMUDIAN KEMBALIKAN VALUE YANG BARU DALAM BENTUK LIST
}
String explodeItem(String str, String remove, int index) { //METHOD INI AKAN MENGHASILKAN STRING SEBAGAI NILAI BALIK
//PARAMETER PERTAMA ADALAH STRING YANG AKAN DIPECAH
//PARAMETER KEDUA KEDUA ADALAH PEMECAHNYA. CONTOH: PADA METHOD SEBELUMNYA KITA GUNAKAN </TD>, KARENA METHOD INI REUSABLE JADI KITA BUAT DALAM BENTUK VARIABLE JADI TERGANTUNG REQUEST
//PARAMETER KETIGA INDEX DATA YANG MAU DIAMBIL
//KITA PECAH STRINGNYA MENGGUNAKAN PARAMETER REMOVE DAN HASILNYA DIUBAH JADI LIST
final result = str.split(remove).toList();
RegExp exp = RegExp(r"<[^>]*>", multiLine: true, caseSensitive: true); //BUAT REGEX UNTUK MENGIDENTIFIKASI HTML
return result[index].replaceAll(exp, ''); //REPLACE SEMUA HTML CODE
}
Bagian terakhir adalah meng-install package http. Buka file pubspec.yaml
dan tambahkan code dibawah di dalam part dependecies
dependencies:
flutter:
sdk: flutter
http: ^0.12.0+2 #TAMBAHKAN CODE INI
Kemudian pada console, jalankan command:
flutter pub get
Baca Juga: Belajar Flutter Basic #7: Navigation Aplikasi Pariwisata
Kesimpulan
Case membuat Aplikasi tracking resi ekspedisi telah memberikan kita beberapa pelajaran menarik dalam memahami Flutter. Dalam case ini saja kita telah belajar bagaimana meminta data ke-server, mengelola response yang diberikan, menampilkan progress loading, me-render informasinya menggunakan Card dan serta cara install external package.
Adapun dokumentasi code dari artikel ini bisa kamu lihat di Github.
Comments