Pendahuluan
Mengelompokkan data ke dalam sebuah state berdasarkan kelompok data tersebut menjadi pilihan yang menarik untuk digunakan dengan tujuan agar lebih maintenable & mudah digunakan kembali dimanapun data tersebut dibutuhkan. Flutter sebagai sebuah framework modern juga memiliki library yang dapat mengelola state sesuai dengan keingingan penggunanya.
Selain itu state management kerap kali digunakan untuk menyimpan seluruh logic yang berkaitan dengan http request dengan tujuan yang sama yakni agar logic tersebut juga bersifat re-usable. Seri belajar Flutter basic kali ini akan mengajak kita untuk mendalami bagaimana cara menerapkan state management pada Flutter.
Baca Juga: Belajar Flutter Basic #7: Navigation Aplikasi Parawisata
Create Simple Inventory Apps
Case yang akan diangkat adalah sebuah aplikasi sederhana untuk memonitoring data barang dengan beberapa fitur diantaranya: mengurangi stock barang ketika digeser kekiri, refresh data dengan pull down, CRUD (Create Read Update Delete) data barang. Adapun preview-nya kurang lebih seperti gambar berikut.
Buat project baru dengan command:
flutter create dw_inventory
Buka file main.dart
dan modifikasi menjadi:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './pages/product_page.dart';
import './providers/products.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//
return MultiProvider(
providers: [
//BERFUNGSI UNTUK ME-LOAD PROVIDER Products
//JIKA MENGGUNAKAN LEBIH DARI 1 PROVIDER CUKUP PISAHKAN DENGAN COMMAND DI DALAM ARRAY providers.
ChangeNotifierProvider.value(
value: Products(),
),
// ChangeNotifierProvider.value(
// value: ProviderLain(),
// ),
],
child: MaterialApp(
title: 'Daengweb.id',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.pink,
accentColor: Colors.yellow),
//ROUTING UNTUK MENGATUR SETIAP PAGE YANG DI-LOAD
routes: {
'/': (ctx) => ProductPage(),
},
),
);
}
}
Penjelasan: Ada dua hal yang perlu di perhatikan pada bagian ini, pertama adalah routing-nya yang berada pada block code routes
dan yang kedua adalah state management menggunakan providers.
Sebelum membuat providers Products
, terlebih dahulu install beberapa package berikut. Buka file pubspec.yaml
dan tambahkan kedua line dibawah ini:
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0+1 //tambahkan line ini
intl: ^0.15.8 //tambakan line ini
http: ^0.12.0+2 //tambahkan line ini
Note: Pastikan sejajar dengan kata Flutter dan tepat dibawah dependecies, karena posisi code sangat berpengaruh.
Ketika di-save, secara otomatis VsCode akan mengunduh package tersebut. Jika belum, dapat dilakukan secara manual dengan command:
flutter pub get
Selanjutnya, buat file products.dart
di dalam folder lib/providers
dan tambahkan code:
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
// CLASS INI AKAN MENG-HANDLE FORMAT DATA YANG DIINGINKAN
class ProductItem {
final String id;
final String title;
final int stock;
final String description;
ProductItem(
{@required this.id,
@required this.title,
@required this.stock,
@required this.description});
}
//CLASS INI BERTINDAK UNTUK MENG-HANDLE STATE MANAGEMENT
class Products with ChangeNotifier {
List<ProductItem> _items = []; //KITA INISIASI DATA AWAL KOSONG DENGAN TIPE LIST DAN DIDALAMNYA MEMILIKI FORMAT DATA SESUAI DENGAN PRODUCT ITEM.
//PROPERTY DENGAN PREFIX _ MENDANDAKAN BAHWA DIA BERSIFAT PRIVATE
//MAKA KITA BUAT LAGI SEBUAH GETTER YANG BERISI DATA DARI PROPERTY _ITEMS, DIMANA GETTERS INI YANG AKAN DIAKSES SECARA PUBLIK.
List<ProductItem> get items {
return [..._items];
}
//METHOD INI BERFUNGSI UNTUK MENGAMBIL DATA DARI SERVER
Future<void> fetchProduct() async {
//DIMANA BACKEND YG DIGUNAKAN ADALAH FIREBASE
const url = 'https://dw-xxxx.firebaseio.com/products.json';
final response = await http.get(url); //MENGGUNAKAN AWAIT UNTUK MENUNGGU PROSESNYA SEBELUM MELANJUTKAN KE CODE SELANJUTNYA
final convertData = json.decode(response.body) as Map<String, dynamic>; //DECODE DATA DAN UBAH FORMATNYA DENGNA FORMAT MAP DAN KEYNYA ADALAH STRING, VALUENYA DYNAMIC
final List<ProductItem> newData = []; //INISIASI LIST DATA BARU YANG KOSONG
//JIKA HASIL DECODE KOSONG MAKA HENTIKAN PROSES
if (convertData == null) {
return;
}
//JIKA TIDAK KOSONG, INSERT DATA YANG DIDAPATKAN DARI SERVER KEDALAM NEW DATA
convertData.forEach((key, value) {
newData.add(ProductItem(
id: key,
title: value['title'],
stock: value['stock'],
description: value['description']));
});
//KEMUDIAN SEMUA DATA YANG ADA DI DALAM NEW DATA KITA MASUKKAN KE STATE _items
_items = newData;
notifyListeners(); //BERFUNGSI UNTUK MEMBERITAHUKAN BAHWA ADA DATA BARU SEHINGGA WIDGET AKAN DI RE-RENDER
}
}
Tugas selanjutnya adalah membuat file product_page.dart
dan tempatkan di dalam folder lib/pages
.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/products.dart'; //IMPORT PROVIDER products YANG TELAH DIBUAT TADI
import '../components/product_list.dart';
import '../components/side_bar.dart';
class ProductPage extends StatelessWidget {
//FUNGSI UNTUK ME-LOAD DATA TERBARU
Future<void> _refreshData(BuildContext context) async {
//CALL FUNGSI fetchProduct() DARI PROVIDERS Products.dart
await Provider.of<Products>(context, listen: false).fetchProduct();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DW Data Barang'),
),
drawer: SideBar(), //DRAWER BERFUNGSI UNTUK MENGATUR SIDEBAR MENU
body: RefreshIndicator(
onRefresh: () => _refreshData(context), //KETIKA DIPULL DOWN, MAKA FUNGSI _refreshData() DIJALANKAN
child: FutureBuilder(
//LOAD DATA MENGGUNAKAN FUTUREBUILDER
future: Provider.of<Products>(context, listen: false).fetchProduct(),
builder: (ctx, snapshop) {
//KETIKA MASIH LOADING
if (snapshop.connectionState == ConnectionState.waiting) {
//MAKA RENDER WIDGET LOADING
return Center(
child: CircularProgressIndicator(),
);
} else {
//JIKA TERDAPAT ERROR
if (snapshop.error != null) {
//MAKA TAMPILKAN TEXT ERROR
return Center(
child: Text("Error Loading Data"),
);
} else {
//KETIKA LOADING DATA SELESAI DAN TIDAK ADA ERROR
//MAKA KITA AMBIL STATE PRODUCTS MENGGUNAKAN CONSUMER
return Consumer<Products>(
builder: (ctx, product, child) => Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
//DIMANA STATE YANG DIAMBIL DARI PRODUCT YANG DIDALAMNYA TERDAPAT STATE ITEMS
itemCount: product.items.length,
//ADAPUN TAMPILANNYA AKAN DI-HANDLE OLEH CUSTOM COMPONENTS
//MAKA KITA TINGGAL MENGIRIMKAN DATA SETIAP ITEM KE CUSTOM COMPONENTS TERSEBUT
itemBuilder: (ctx, i) => ProductList(
product.items[i].id,
product.items[i].title,
product.items[i].description,
product.items[i].stock,
false,
),
),
),
);
}
}
},
),
),
);
}
}
Component ProductList
akan berisi code yang akan meng-handle tampilan setiap item dari data product akan terlihat seperti apa, buat file product_list.dart
di dalam folder lib/components
dan tambahkan code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/products.dart';
class ProductList extends StatelessWidget {
final String id;
final String title;
final String description;
final int stock;
final bool type;
//BUAT CONSTRUCTOR UNTUK MEMINTA DATA DARI WIDGET YANG MENGGUNAKANNYA
ProductList(this.id, this.title, this.description, this.stock, this.type);
@override
Widget build(BuildContext context) {
//DISMISSIBLE, KETIKA ITEM PRODUKNYA DI GESER MAKA KITA AKAN MENJALANKAN ACTIONS
return Dismissible(
key: ValueKey(id),
background: Container(
color: Theme.of(context).errorColor,
child: Icon(
Icons.call_missed_outgoing,
size: 40,
color: Colors.white,
),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
margin: EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
),
//ARAH GESERNYA LITA SET DARI KANAN KE KIRI
direction: DismissDirection.endToStart,
//KETIKA DI GESER
confirmDismiss: (dismiss) {
//MAKA AKAN MENAMPILKAN DIALOG
showDialog(
context: context,
//DIMANA ISI DIALOGNYA ADALAH ALERTDIALOG
builder: (ctx) => AlertDialog(
title: Text("Kamu Yakin?"),
content: Text("Kamu Akan Mengurangi Stok?"),
actions: <Widget>[
//KETIKA TOMBOL INI DITEKAN MAKA AKAN MENUTUP ALERT DENGAN MENGIRIMKAN VALUE FALSE
FlatButton(
child: Text(
"No",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(ctx).pop(false);
},
),
//DAN TOMBOL INI BERFUNGSI SAMA TAPI MENGIRIMKAN VALUE TRUE
FlatButton(
child: Text(
"Yes",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(ctx).pop(true);
},
)
],
),
).then((result) {
//KITA CEK, JIKA VALUENYA BERNILAI TRUE
if (result) {
//DAN STOKNYA ADA
if (stock > 0) {
//MAKA FUNGSI CHANGESTOCK DARI PROVIDERS AKAN DIJALANKAN
Provider.of<Products>(context, listen: false).changeStock(id);
} else {
//SELAIN ITU AKAN MENAMPILKAN SNACKBAR UNTUK INFORMASI
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("Stok Kosong"),
duration: Duration(seconds: 2),
));
}
}
});
},
child: Card(
elevation: 4,
//ADAPUN TAMPILANNYA KITA GUNAKAN LISTTILE, DIMANA TERDAPAT 3 BAGIAN: LEADING, TITLE DAN TRAILING
child: ListTile(
//LEADING AKAN DI-RENDER PADA POSISI KIRI
//BAGIAN INI AKAN KITA GUNAKAN UNTUK MENAMPILKAN STOK
leading: CircleAvatar(
child: Text(stock.toString()),
),
//TITLE AKAN DI-RENDER DITENGAH SETELAH LEADING, DAN BAGIAN INI AKAN MENAMPILKAN NAMA BARANG
title: Text(title),
//SUBTITLE AKAN DI-RENDER DIBAWAH TITLE. BAGIAN INI AKAN DIGUNAKAN UNTUK MENAMPILKAN DESKRIPS
subtitle: Text(
'Deskripsi: $description',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
//TRAILING AKAN DI-RENDER DISEBELAH KANAN
//LALU KITA CEK JIKA TYPENYA FALSE, KITA AKAN MENAMPILKAN STATUS STOK
trailing: !type
? Text(
stock > 0 ? 'In Stock' : 'Sold Out',
style:
TextStyle(color: stock > 0 ? Colors.green : Colors.red),
)
//DAN JIKA TRUE, MAKA AKAN MENAMPILKAN TOMBOL EDIT DAN HAPUS
: Container(
width: 100,
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
//EDIT BERISI FUNGSI UNTUK BERPINDAH PAGE DENGAN MENGIRIMKAN ARGUMENTS ID
Navigator.of(context)
.pushNamed('/add-product', arguments: id);
},
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
//SEDANGKAN DELETE AKAN MENAMPILKAN ALERT DIALOG LAGI UNTUK KONFIRMASI
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text("Kamu Yakin?"),
content: Text("Proses Ini Akan Menghapus Data"),
actions: <Widget>[
FlatButton(
child: Text(
"Batal",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
FlatButton(
child: Text(
"Lanjutkan",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
//JIKA YES, MAKA FUNGSI removeProduct AKAN DIJALANKAN
Provider.of<Products>(context,
listen: false)
.removeProduct(id)
.then((_) {
Navigator.of(context).pop(false);
});
},
)
],
),
);
},
color: Theme.of(context).errorColor,
),
],
),
),
),
),
);
}
}
Jika diperhatikan, product_page.dart
meng-import file side_bar.dart
dimana pada file ini berisi code untuk menampilkan sidebar menu. Buat file tersebut di dalam folder lib/components
dan tambahkan code berikut:
import 'package:flutter/material.dart';
class SideBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(children: <Widget>[
//KITA BUAT HEADER SIDEBARNYA
AppBar(title: Text('Daengweb.id'), automaticallyImplyLeading: false,),
Divider(), //GARIS PEMISAH
//UNTUK SETIAP MENUNYA KITA GUNAKAN LIST TILE DIMANA LEADINGNYA BERISI ICON DAN TITLENYA BERISI NAMA MENU. ADAPUN KETIKA DI TAP MAKA AKAN MENUJU KE PAGENYA MASING-MASING
ListTile(leading: Icon(Icons.devices), title: Text('Inventory'), onTap: () {
Navigator.of(context).pushReplacementNamed('/');
},),
Divider(),
ListTile(leading: Icon(Icons.lens), title: Text('Manage Inventory'), onTap: () {
Navigator.of(context).pushReplacementNamed('/manage-product');
},),
],),
);
}
}
Manage Products Inventory
Home screen sudah selesai dimana fungsinya adalah menampilkan list barang beserta stoknya dan ketika digeser kekiri maka akan mengurangi stok barang tersebut. Bagian selanjutnya yang akan dibuat adalah page yang berfungsi untuk menampilkan list data barang serta memiliki fungsi untuk menambahkan, meng-edit dan menghapus data barang.
Pertama, buat file master_product_page.dart
di dalam folder lib/pages
dan tambahkan code:
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import '../components/side_bar.dart';
import '../components/product_list.dart';
import '../providers/products.dart';
class MasterProductPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//FUNGSI UNTUK ME-LOAD DATA BARANG YANG TERBARU.
Future<void> _fetchData(BuildContext context) async {
await Provider.of<Products>(context, listen: false).fetchProduct();
}
return Scaffold(
appBar: AppBar(
title: Text("Manage Inventory"),
actions: <Widget>[
//DI APPBAR, KITA BUAT TOMBOL UNTUK BERPINDAH KE PAGE ADD PRODUCT
IconButton(
icon: Icon(Icons.add),
onPressed: () {
Navigator.of(context).pushNamed('/add-product');
},
)
],
),
drawer: SideBar(), //SIDEBARNYA KITA GUNAKAN CLASS YANG SAMA
body: FutureBuilder(
//LOAD DATA MENGGUNAKAN FUTURE BUILDER
future: Provider.of<Products>(context, listen: false).fetchProduct(),
//PENJELASAN BAGIAN INI SAMA DENGAN PENJELASAN YANG ADA DI PRODUCT_PAGE.DART
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text("Error Loading Data"),
);
} else {
return Padding(
padding: EdgeInsets.all(10),
child: RefreshIndicator(
onRefresh: () => _fetchData(context),
child: Consumer<Products>(
builder: (context, product, child) => ListView.builder(
itemCount: product.items.length,
//BAGIAN INI JUGA SAMA SEPERTI SEBELUMNYA. KITA GUNAKAN PRODUCTLIST UNTUK MENAMPILKAN SETIAP ITEM BARANG
itemBuilder: (ctx, i) => ProductList(
product.items[i].id,
product.items[i].title,
product.items[i].description,
product.items[i].stock,
true,
),
),
),
),
);
}
}
},
),
);
}
}
Tugas selanjutnya adalah membuat page yang akan menampilkan form untuk menambahkan data barang, file ini akan kita buat reusable sehingga bisa digunakan untuk add data barang dan juga edit data barang. Buat file add_new_product.dart
di dalam folder lib/pages
dan tambahkan code:
import 'package:provider/provider.dart';
import '../providers/products.dart';
import 'package:flutter/material.dart';
class AddNewProduct extends StatefulWidget {
@override
_AddNewProductState createState() => _AddNewProductState();
}
class _AddNewProductState extends State<AddNewProduct> {
final _titleDispose = FocusNode();
final _priceDispose = FocusNode();
final _descriptionDispose = FocusNode();
final _titleController = TextEditingController();
final _stockController = TextEditingController();
final _descriptionController = TextEditingController();
final _form = GlobalKey<FormState>(); //MEMBUAT GLOBAL KEY UNTUK FORM() WIDGET
var _isLoading = false;
var _initValue = true;
String id;
@override
void didChangeDependencies() async {
//KETIKA TERJADI PERUBAHANGAN MAKA FUNGSI INI DIJALANKAN, KITA CEK JIKA BERNILAI TRUE
if (_initValue) {
//MAKA _isLoading DIUBAH JADI TRUE
setState(() {
_isLoading = true;
});
//MENGAMBIL ID YANG DIKIRIMKAN JIKA ADA (ID INI DIKIRIMKAN KETIKA TOMBOL EDIT DITEKAN)
id = ModalRoute.of(context).settings.arguments as String;
//DI CEK JIKA ID NYA TIDAK KOSONG, YANG BERARTI DARI EDIT AKAN BERISI ID. SEDANGKAN DARI ADD NEW ID NYA KOSONG
if (id != null) {
//MAKA AKAN MENJALANKAN FUNGSI findById()
final response = await Provider.of<Products>(context).findById(id);
//KEMUDIAN DATA YANG DITERIMA AKAN DI ASSIGN
_titleController.text = response.title;
_stockController.text = response.stock.toString();
_descriptionController.text = response.description;
}
//UBAH KEMBALI LOADING JADI FALSE
setState(() {
_isLoading = false;
});
}
//INIT VALUE DI SET JADI FALSE AGAR TIDAK DIJALANKAN KEMBALI SEHINGGA FUNGSI DIATAS HANYA AKAN DIJALANKAN SEKALI KETIKA HALAMAN DILOAD
_initValue = false;
super.didChangeDependencies();
}
@override
void dispose() {
_titleDispose.dispose();
_priceDispose.dispose();
_descriptionDispose.dispose();
super.dispose();
}
//FUNGSI SUBMIT UNTUK MENYIMPAN DATA
Future<void> _submit() async {
final isValid = _form.currentState.validate(); //JALANKAN VALIDASI, DIMANA FUNGSI INI TELAH DISEDIAKAN OLEH FORM WIDGET()
//JIKA TIDAK VALID
if (!isValid) {
return; //MAKA HENTIKAN PROSES
}
_form.currentState.save(); //JIKA VALID MAKA FUNGSI SAVE() DIJALANKAN, DIMANA FUNGSI INI JUGA DARI FORM WIDGET
//SET LOADING JADI TRUE
setState(() {
_isLoading = true;
});
//KARENA FORM INI REUSABLE, MAKA CEK JIKA ID NULL
if (id == null) {
//MAKA YANG DIJALANKAN ADALAH FUNGSI ADD PRODUCT
await Provider.of<Products>(context, listen: false)
.addProduct(ProductItem(
id: null,
title: _titleController.text,
stock: int.parse(_stockController.text),
description: _descriptionController.text,
));
//SELAIN ITU MAKA FUNGSI YANG DIJALANKAN ADALAH UPDATE PRODUCT
} else {
await Provider.of<Products>(context, listen: false)
.updateProduct(ProductItem(
id: id,
title: _titleController.text,
stock: int.parse(_stockController.text),
description: _descriptionController.text,
));
}
//SET KEMBALI LOADING JADI FALSE
setState(() {
_isLoading = false;
});
//KEMBALI KE HALAMAN SEBELUMNYA
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(id == null ? 'Add New Product':'Edit Product'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: _submit, //TOMBOL SIMPAN YANG KETIKA DITEKAN MAKA AKAN MENJALANKAN FUNGSI _submit YANG TELAH DIBUAT SEBELUMNYA
)
],
),
body: _isLoading
? Center(
child: CircularProgressIndicator(), //KETIKA ISLOADING BERNILAI TRUE MAKA INDIKATOR LOADING AKAN DIRENDER
)
: Padding(
padding: EdgeInsets.all(10),
//SELAIN ITU AKAN MENAMPILKAN FORM INPUT
child: Form(
key: _form, //GLOBAL KEY YANG TELAH DIBUAT SEBELUMNYA DIGUNAKAN DISINI
child: Column(
children: <Widget>[
//INPUTAN YANG MENGHANDLE NAMA BARANG
TextFormField(
decoration: InputDecoration(labelText: 'Nama Barang'),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_priceDispose);
},
//VALIDASI INPUTAN BARANG
validator: (value) {
if (value.isEmpty) {
return 'Nama Barang Tidak Boleh Kosong';
}
return null;
},
controller: _titleController, //ADAPUN VALUENYA TELAH DIHANDLE OLEH CONTROLLER MASING-MASING
),
TextFormField(
decoration: InputDecoration(labelText: 'Stok'),
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
onFieldSubmitted: (_) {
FocusScope.of(context)
.requestFocus(_descriptionDispose);
},
validator: (value) {
if (value.isEmpty) {
return 'Stok Tidak Boleh Kosong';
}
if (int.tryParse(value) == null) {
return 'Value Harus Berisi Angkat';
}
if (int.parse(value) <= 0) {
return 'Stok Harus Lebih Besar Dari 0';
}
return null;
},
controller: _stockController,
),
TextFormField(
maxLines: 3,
decoration: InputDecoration(labelText: 'Deskripsi'),
validator: (value) {
if (value.isEmpty) {
return 'Deskripsi Tidak Boleh Kosong';
}
return null;
},
controller: _descriptionController,
),
],
),
),
),
);
}
}
Reusable form input-an diatas sudah selesai, maka kita harus mendefinisikan routing dan juga membuat fungsi untuk simpan dan update data produk. Pertama kita definisikan routing-nya terlebih dahulu, buka file main.dart
dan tambahkan code berikut pada bagian routes:
routes: {
'/': (ctx) => ProductPage(),
'/manage-product': (ctx) => MasterProductPage(), //TAMBAHKAN LINE INI
'/add-product': (ctx) => AddNewProduct() //TAMBAHKAN LINE INI
},
Masih dengan file yang sama, tambahkan import statement:
import './pages/master_product_page.dart';
import './pages/add_new_product.dart';
Kemudian buka file lib/providers/products.dart
dan tambahkan beberapa method berikut:
// [.. CODE SEBELUMNYA ..]
//METHOD YANG BERFUNGSI UNTUK MENGAMBIL DATA TUNGGAL BERDASARKAN ID
Future<ProductItem> findById(String id) async {
final url = 'https://dw-inventory.firebaseio.com/products/$id.json';
final response = await http.get(url);
final convert = json.decode(response.body);
//DATA TERSEBUT KITA RETURN MENGGUNAKAN FORM DARI CLASS PRODUCTITEM
return ProductItem(id: id, title: convert['title'], description: convert['description'], stock: convert['stock']);
}
//METHOD YG BERFUNGSI UNTUK MENAMBAHKAN DATA BARU
Future<void> addProduct(ProductItem product) async {
const url = 'https://dw-inventory.firebaseio.com/products.json';
//METHOD YG DIGUNAKAN ADALAH POST DAN PADA PARAMETER KEDUA KITA KIRIMKAN DATANYA
final response = await http.post(url,
body: json.encode({
'title': product.title,
'stock': product.stock,
'description': product.description
}));
//KEMUDIAN KITA ADD DATANYA KE LOCAL STATE AGAR TIDAK PERLU MELAKUKAN GET LG DARI SERVER
_items.add(ProductItem(
id: json.decode(response.body)['name'],
title: product.title,
stock: product.stock,
description: product.description,
));
notifyListeners(); //INFORMASIKAN UNTUK ME RE-RENDER WIDGET KARENA TERDAPAT DATA BARU
}
//METHOD YG BERFUNGSI UNTUK MENGURANGI STOK BERDASARKAN ID
Future<void> changeStock(String id) async {
final url = 'https://dw-inventory.firebaseio.com/products/$id.json';
final index = _items.indexWhere((prod) => prod.id == id);
final stock = _items[index].stock - 1;
await http.patch(url, body: json.encode({'stock': stock})); //UPDATE DATA DI SERVER
//DAN UPDATE JUGA DI LOCAL STATE
_items[index] = ProductItem(
id: id,
title: _items[index].title,
description: _items[index].description,
stock: stock,
);
notifyListeners();
}
//METHOD INI UNTUK MENGUPDATE SEMUA INFORMASI DATA YANG SEDANG DI EDIT
Future<void> updateProduct(ProductItem product) async {
//BERDASARKAN ID PRODUCT
final url = 'https://dw-inventory.firebaseio.com/products/${product.id}.json';
//KEMUDIAN KITA KIRIMKAN PARAMETER DATA YANG INGIN DI PERBAHARUI
await http.patch(url, body: json.encode({
'title': product.title,
'stock': product.stock,
'description': product.description
}));
//KEMUDIAN KITA UPDATE JUGA LOCAL STATE
final index = _items.indexWhere((prod) => prod.id == product.id);
_items[index] = product;
notifyListeners();
}
//HAPUS PRODUK BERDASARKAN ID
Future<void> removeProduct(String id) async {
final url = 'https://dw-inventory.firebaseio.com/products/$id.json';
await http.delete(url); //KIRIM PERMINTAAN KE SERVER
_items.removeWhere((prod) => prod.id == id); //DAN HAPUS JUGA PADA LOCAL STATE
notifyListeners();
}
Sampai pada tahap ini, semua part dari case sederhana aplikasi inventory telah dibuat. Jalankan code program diatas dengan menekan F5 pada VsCode atau ke menu Debug > Start Debugging.
Baca Juga: Aplikasi Laundry (Laravel 5.8 - Vue.js - SPA) #8: Manage Customers
Kesimpulan
Seria belajar State Management pada Flutter memiliki banyak cakupan yang telah kita pelajari, diantaranya bagaimana mengelola state secara global sehingga dapat digunakan dimana saja state tersebut dibutuhkan, pun juga berlaku hal yang sama dengan method yang berisi logic untuk berinteraksi dengan backend.
Selain itu, pembahasan mengenai HTTP request yang meliputi get, post, patch and delete juga sudah dibahas dengan tujuan untuk berinteraksi dengan backend dimana backend yang digunakan ada Firebase.
Adapun dokumentasi dari artikel ini dapat dilihat di Github.
Comments