Terakhir diperbarui: 24 November 2025

Penulis: Habibie Ed Dien

Pada codelab ini, Anda akan mempelajari tentang RESTful API di Flutter beserta contoh penggunaannya. Cara kerja, manfaat, dan cara menggunakan RESTful API ke dalam project flutter.

Video singkat berikut menjelaskan tentang fetching data di Flutter. Silakan simak dan pahami!

Tujuan Praktikum

Setelah menyelesaikan codelab ini Anda akan mampu untuk:

Sumber Daya yang Dibutuhkan

Berikut merupakan sumber daya yang diperlukan untuk menyelesaikan praktikum ini:

Pengetahuan yang Anda harus Miliki

API RESTful (atau layanan web RESTful) adalah layanan backend yang menyediakan data untuk aplikasi Anda (frontend atau client), dengan tujuan memungkinkan operasi CRUD (Create, Read, Update, dan Delete) pada data.

Komunikasi ini dilakukan menggunakan protokol HTTP dan serangkaian aksi standar yang disebut verbs. Data yang ditransfer biasanya dalam format JSON.

Empat verb utama yang digunakan adalah:

  1. GET: Untuk mengambil data dari server.
  2. POST: Untuk mengirim atau memasukkan data baru.
  3. PUT: Untuk memperbarui data yang sudah ada.
  4. DELETE: Untuk menghapus data.

Materi Anda selanjutnya berfokus pada cara mendesain klien HTTP untuk melakukan operasi CRUD ini di Flutter.

Sebagian besar aplikasi seluler bergantung pada data yang berasal dari sumber eksternal. Bayangkan aplikasi untuk membaca buku, menonton film, berbagi foto dengan teman, membaca berita, atau menulis email: semua aplikasi ini menggunakan data yang diambil dari sumber eksternal. Saat aplikasi mengonsumsi data eksternal, biasanya ada layanan backend yang menyediakan data tersebut untuk aplikasi: layanan web atau API web.

Yang terjadi adalah aplikasi Anda (frontend atau klien) terhubung ke layanan web melalui HTTP dan meminta beberapa data. Layanan backend kemudian merespons dengan mengirimkan data ke aplikasi, biasanya dalam format JSON atau XML.

Untuk codelab ini, kita akan membuat aplikasi yang membaca dan menulis data dari layanan web. Karena membuat API web berada di luar ruang lingkup codelab ini, kita akan menggunakan layanan mock, yang disebut Wire Mock Cloud, yang akan mensimulasikan perilaku layanan web nyata, tetapi akan sangat mudah untuk disiapkan dan digunakan.

Untuk mengikuti codelab ini, perangkat Anda memerlukan koneksi internet untuk mengambil data dari layanan web.

Dalam codelab ini, kita akan mensimulasikan layanan web menggunakan Wire Mock Cloud dan membuat aplikasi yang membaca data dari layanan mock. Kita akan mulai dengan menyiapkan layanan baru:

  1. Daftar untuk layanan Mock Lab dihttps://app.wiremock.cloud/ dan daftar ke situs tersebut, buat nama pengguna dan kata sandi Anda.
  2. Masuk ke layanan tersebut, buka "Example Mock API", dan klik pada bagian Stubs dari API contoh. Kemudian, klik pada entri pertama—yaitu, Get a JSON resource. Anda harus melihat layar yang mirip dengan berikut ini:
  3. Klik pada tombol New. Untuk Namanya, ketik Pizza List, biarkan GET sebagai verb, dan di kotak teks di dekat verb GET, ketik /pizzalist. Kemudian, di bagian Response, untuk status 200, pilih JSON sebagai format dan tempel konten JSON yang tersedia dihttps://bit.ly/pizzalist.

  1. Tekan tombol Save di bagian bawah halaman untuk menyimpan stub. Ini menyelesaikan pengaturan untuk layanan mock backend.
  2. Kembali ke proyek Flutter Anda, tambahkan dependensi http dengan mengetik di Terminal Anda:
flutter pub add http
  1. Di folder lib dalam proyek Anda, tambahkan file baru bernama httphelper.dart.
  2. Di file httphelper.dart, tambahkan kode berikut:
import 'dart:io'; 
import 'package:http/http.dart' as http; 
import 'dart:convert'; 
import 'pizza.dart'; 

class HttpHelper {
  final String authority = '02z2g.mocklab.io';
  final String path = 'pizzalist';
  Future<List<Pizza>> getPizzaList() async {
    final Uri url = Uri.https(authority, path);
    final http.Response result = await http.get(url);
    if (result.statusCode == HttpStatus.ok) {
      final jsonResponse = json.decode(result.body);
      //provide a type argument to the map method to avoid type 
      //error
      List<Pizza> pizzas =
          jsonResponse.map<Pizza>((i) => 
            Pizza.fromJson(i)).toList();
      return pizzas;
    } else {
      return [];
    }
  }
}
  1. Di file main.dart, di kelas _MyHomePageState, tambahkan metode bernama callPizzas. Ini mengembalikan Future dari List objek Pizza dengan memanggil metode getPizzaList dari kelas HttpHelper, sebagai berikut:
Future<List<Pizza>> callPizzas() async {
  HttpHelper helper = HttpHelper(); 
  List<Pizza> pizzas = await helper.getPizzaList(); 
  return pizzas; 
} 
  1. Di metode build dari kelas _MyHomePageState, di body Scaffold, tambahkan FutureBuilder yang membangun ListView dari widget ListTile yang berisi objek Pizza:
Widget build(BuildContext context) { 
    return Scaffold(
      appBar: AppBar(title: const Text('JSON')),
      body: FutureBuilder(
          future: callPizzas(),
          builder: (BuildContext context, AsyncSnapshot<List<Pizza>> 
snapshot) {
          if (snapshot.hasError) {
            return const Text('Something went wrong');
          }
          if (!snapshot.hasData) {
            return const CircularProgressIndicator();
          }
            return ListView.builder(
                itemCount: (snapshot.data == null) ? 0 : snapshot.
data!.length,
                itemBuilder: (BuildContext context, int position) {
                  return ListTile(
                    title: Text(snapshot.data![position].pizzaName),
                    subtitle: Text(snapshot.data![position].
description +
                        ' - € ' +
                        snapshot.data![position].price.toString()),
                  );
                });
          }),
    );  
}
  1. Jalankan aplikasi. Anda harus melihat layar yang mirip dengan berikut ini:

Gambar tersebut merupakan tangkapan layar dari daftar item yang diambil melalui HTTP, menampilkan ListView dengan nama pizza dan deskripsinya.

Kita sekarang hanya memiliki satu metode yang menggunakan kelas HttpHelper. Seiring bertumbuhnya aplikasi, kita mungkin perlu memanggil HttpHelper beberapa kali di bagian berbeda dari aplikasi, dan akan menjadi pemborosan sumber daya untuk membuat banyak instance kelas setiap kali perlu menggunakan metode dari kelas tersebut.

Salah satu cara untuk menghindari ini adalah dengan menggunakan konstruktor factory dan pola singleton: ini memastikan Anda hanya menginstansiasi kelas sekali. Ini berguna setiap kali hanya satu objek yang diperlukan di aplikasi Anda dan ketika Anda perlu mengakses sumber daya yang ingin Anda bagikan di seluruh aplikasi.

Di file httphelper.dart, tambahkan kode berikut ke kelas HttpHelper, tepat di bawah deklarasi:

static final HttpHelper _httpHelper = HttpHelper._internal();
HttpHelper._internal();
factory HttpHelper() {
    return _httpHelper;
}

Ada beberapa pola di Dart dan Flutter yang memungkinkan Anda berbagi layanan dan logika bisnis di aplikasi Anda, dan pola singleton hanya salah satunya. Pilihan lain termasuk Dependency injection, Inherited Widgets, dan Provider serta Service Locators. Ada artikel menarik tentang pilihan berbeda yang tersedia di Flutter di http://bit.ly/flutter_DI

Pada praktikum ini, Anda akan belajar bagaimana melakukan aksi POST pada layanan web. Ini berguna setiap kali Anda terhubung ke layanan web yang tidak hanya menyediakan data tetapi juga memungkinkan Anda untuk mengubah informasi yang disimpan di sisi server. Biasanya, Anda harus memberikan beberapa bentuk otentikasi ke layanan, tetapi untuk codelab ini, karena kita menggunakan layanan mock, ini tidak diperlukan.

Untuk melakukan aksi POST pada layanan web, ikuti langkah-langkah berikut:

  1. Masuk ke layanan Mock Lab dihttps://app.wiremock.cloud/ dan klik pada bagian Stubs dari API contoh. Kemudian, buat stub baru.
  2. Lengkapi permintaan sebagai berikut:

Tangkapan layar dari WireMock Post Pizza stub, menampilkan konfigurasi stub dengan nama, verb POST, path, status 201, dan body respons JSON.

  1. Tekan tombol Save.
  2. Di proyek Flutter, di file httphelper.dart, di kelas HttpHelper, buat metode baru bernama postPizza, sebagai berikut:
Future<String> postPizza(Pizza pizza) async {
  const postPath = '/pizza';
  String post = json.encode(pizza.toJson());
  Uri url = Uri.https(authority, postPath);
  http.Response r = await http.post(
    url,
    body: post,
  );
  return r.body;
}
  1. Di proyek, buat file baru bernama pizza_detail.dart.
  2. Di bagian atas file baru, tambahkan impor yang diperlukan:
import 'package:flutter/material.dart';
import 'pizza.dart';
import 'httphelper.dart';
  1. Buat StatefulWidget bernama PizzaDetailScreen:
class PizzaDetailScreen extends StatefulWidget {
  const PizzaDetailScreen({super.key});
  @override
  State<PizzaDetailScreen> createState() => _PizzaDetailScreenState();
}

class _PizzaDetailScreenState extends State<PizzaDetailScreen> {
  @override
  Widget build(BuildContext context) {
    return Placeholder();
  }
}
  1. Di bagian atas kelas _PizzaDetailScreenState, tambahkan lima TextEditingController. Ini akan berisi data untuk objek Pizza yang akan diposting nanti. Juga, tambahkan String yang akan berisi hasil dari permintaan POST:
final TextEditingController txtId = TextEditingController();
final TextEditingController txtName = TextEditingController();
final TextEditingController txtDescription = TextEditingController();
final TextEditingController txtPrice = TextEditingController();
final TextEditingController txtImageUrl = TextEditingController();
String operationResult = '';
  1. Override metode dispose() untuk membuang controller:
@override
void dispose() {
  txtId.dispose();
  txtName.dispose();
  txtDescription.dispose();
  txtPrice.dispose();
  txtImageUrl.dispose();
  super.dispose();
}
  1. Di metode build() dari kelas, kembalikan Scaffold, yang AppBar-nya berisi Text "Pizza Detail" dan body-nya berisi Padding dan SingleChildScrollView yang berisi Column:
return Scaffold(
  appBar: AppBar(
    title: const Text('Pizza Detail'),
  ),
  body: Padding(
      padding: const EdgeInsets.all(12),
      child: SingleChildScrollView(
        child: Column(children: []),
      )));
  1. Untuk properti children dari Column, tambahkan beberapa Text yang akan berisi hasil dari post, lima TextField, masing-masing terikat ke TextEditingController mereka sendiri, dan ElevatedButton untuk menyelesaikan aksi POST (metode postPizza akan dibuat selanjutnya). Juga, tambahkan SizedBox untuk menjauhkan widget di layar:
Text(
  operationResult,
  style: TextStyle(
      backgroundColor: Colors.green[200],
      color: Colors.black),
),
const SizedBox(
  height: 24,
),
TextField(
  controller: txtId,
  decoration: const InputDecoration(hintText: 'Insert ID'),
),
const SizedBox(
  height: 24,
),
TextField(
  controller: txtName,
  decoration: const InputDecoration(hintText: 'Insert Pizza Name'),
),
const SizedBox(
  height: 24,
),
TextField(
  controller: txtDescription,
  decoration: const InputDecoration(hintText: 'Insert Description'),
),
const SizedBox(
  height: 24,
),
TextField(
  controller: txtPrice,
  decoration: const InputDecoration(hintText: 'Insert Price'),
),
const SizedBox(
  height: 24,
),
TextField(
  controller: txtImageUrl,
  decoration: const InputDecoration(hintText: 'Insert Image Url'),
),
const SizedBox(
  height: 48,
),
ElevatedButton(
    child: const Text('Send Post'),
    onPressed: () {
      postPizza();
    })           
  1. Di bagian bawah kelas _PizzaDetailScreenState, tambahkan metode postPizza:
Future postPizza() async {
  HttpHelper helper = HttpHelper();
  Pizza pizza = Pizza();
  pizza.id = int.tryParse(txtId.text);
  pizza.pizzaName = txtName.text;
  pizza.description = txtDescription.text;
  pizza.price = double.tryParse(txtPrice.text);
  pizza.imageUrl = txtImageUrl.text;
  String result = await helper.postPizza(pizza);
  setState(() {
    operationResult = result;
  });
}
  1. Di file main.dart, impor file pizza_detail.dart
  2. Di Scaffold dari metode build() kelas _MyHomePageState, tambahkan FloatingActionButton yang akan navigasi ke rute PizzaDetail:
floatingActionButton: FloatingActionButton(
  child: const Icon(Icons.add),
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
          builder: (context) => const PizzaDetailScreen()),
    );
  }),
  1. Jalankan aplikasi. Di layar utama, tekan FloatingActionButton untuk navigasi ke rute PizzaDetail.
  2. Tambahkan detail pizza di field teks dan tekan tombol Send Post. Anda seharusnya melihat hasil yang sukses, seperti yang ditunjukkan pada Gambar berikut ini:

Tangkapan layar dari layar Pizza Detail dengan pesan sukses "The pizza was posted" setelah menekan tombol Send Post.

Layanan web (khususnya, layanan web RESTful) bekerja dengan verb. Ada empat aksi utama (atau verb) yang umumnya Anda gunakan ketika data terlibat: GET, POST, PUT, dan DELETE.

Dalam praktikum 2 ini, kita menggunakan POST, yang merupakan verb yang secara konvensional digunakan ketika aplikasi meminta server web untuk menyisipkan potongan data baru.

Inilah sebabnya kita harus menginstruksikan layanan web mock untuk menerima POST di alamat /pizza terlebih dahulu, sehingga dapat mencoba mengirim beberapa data ke sana dan membuatnya merespons dengan pesan sukses.

Setelah membuat stub POST di Wiremock, kita membuat metode postPizza, untuk benar-benar melakukan panggilan ke server. Karena ini mengambil string JSON, kita menggunakan metode json.encode untuk mengubah Map menjadi JSON.

Dalam praktikum ini, Anda akan belajar cara melakukan aksi PUT pada layanan web. Ini berguna ketika aplikasi Anda perlu mengedit data yang ada di layanan web.

Untuk melakukan aksi PUT pada layanan web, ikuti langkah-langkah berikut:

  1. Masuk ke layanan Wiremock dihttps://app.wiremock.cloud dan klik pada bagian Stubs dari API contoh. Kemudian, buat stub baru.
  2. Lengkapi permintaan sebagai berikut:

Tangkapan layar dari WireMock Put Pizza stub, menampilkan konfigurasi stub dengan nama, verb PUT, path, status 200, dan body respons JSON.

  1. Klik tombol Save.
  2. Di proyek Flutter, tambahkan metode putPizza ke kelas HttpHelper di file http_helper.dart:
Future<String> putPizza(Pizza pizza) async {
  const putPath = '/pizza';
  String put = json.encode(pizza.toJson());
  Uri url = Uri.https(authority, putPath);
  http.Response r = await http.put(
    url,
    body: put,
  );
  return r.body;
}
  1. Di kelas PizzaDetailScreen di file pizza_detail.dart, tambahkan dua properti, sebuah Pizza dan sebuah boolean, dan di konstruktor, atur kedua properti tersebut:
final Pizza pizza;
final bool isNew;
const PizzaDetailScreen(
    {super.key, required this.pizza, required this.isNew});
  1. Di kelas PizzaDetailScreenState, override metode initState. Ketika properti isNew dari kelas PizzaDetail bukan baru, itu mengatur konten TextField dengan nilai-nilai objek Pizza yang diteruskan:
@override
void initState() {
  if (!widget.isNew) {
    txtId.text = widget.pizza.id.toString();
    txtName.text = widget.pizza.pizzaName;
    txtDescription.text = widget.pizza.description;
    txtPrice.text = widget.pizza.price.toString();
    txtImageUrl.text = widget.pizza.imageUrl;
  }
  super.initState();
}
  1. Edit metode savePizza sehingga memanggil metode helper.postPizza ketika isNew benar, dan helper.putPizza ketika salah:
Future savePizza() async {
...
    final result = await (widget.isNew
  ? helper.postPizza(pizza)
  : helper.putPizza(pizza));    
  setState(() {
      operationResult = result;
    });
  }
  1. Di file main.dart, di metode build dari _MyHomePageState, tambahkan properti onTap ke ListTile sehingga ketika pengguna mengetuknya, aplikasi akan mengubah rute dan menampilkan layar PizzaDetail, meneruskan pizza saat ini dan false untuk parameter isNew:
return ListTile(
    title: Text(pizzas.data![position].pizzaName),
    subtitle: Text(pizzas.data![position].description +
                  ' - € ' +
                  pizzas.data![position].price.toString()),
    onTap: () {
       Navigator.push(
          context,
          MaterialPageRoute(
             builder: (context) => PizzaDetailScreen(
                pizza: pizzas.data![position], isNew: false)),
    );
  1. Di floatingActionButton, teruskan Pizza baru dan true untuk parameter isNew ke rute PizzaDetail:
floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                  builder: (context) => PizzaDetailScreen(
                        pizza: Pizza(),
                        isNew: true,
                      )),            
           );
      }),
);
  1. Jalankan aplikasi. Di layar utama, ketuk Pizza apa pun untuk navigasi ke rute PizzaDetail.
  2. Edit detail pizza di field teks dan tekan tombol Save. Anda seharusnya melihat pesan yang menunjukkan bahwa detail pizza telah diperbarui.

Praktikum ini cukup mirip dengan yang sebelumnya, tetapi di sini menggunakan PUT, verb yang secara konvensional digunakan ketika aplikasi meminta server web untuk memperbarui potongan data yang ada.

Inilah sebabnya harus menginstruksikan layanan web mock untuk menerima PUT di alamat /pizza, sehingga dapat mencoba mengirim beberapa data ke sana.

Perhatikan bahwa alamat /pizza persis sama dengan yang diatur untuk POST, satu-satunya yang berubah adalah verb. Dengan kata lain, Anda dapat melakukan aksi berbeda di URL yang sama, tergantung pada verb yang Anda gunakan.

Setelah membuat stub PUT di Wiremock, kita membuat metode putPizza, yang bekerja seperti metode postPizza, kecuali untuk verb-nya.

Untuk membuat pengguna memperbarui Pizza yang ada, kita menggunakan layar yang sama, PizzaDetail, tetapi juga meneruskan objek Pizza yang ingin diperbarui dan nilai Boolean yang memberi tahu apakah objek Pizza adalah objek baru (sehingga harus menggunakan POST) atau yang ada (sehingga harus menggunakan PUT).

Dalam praktikum ini, Anda akan belajar cara melakukan aksi DELETE pada layanan web. Ini berguna ketika aplikasi Anda perlu menghapus data yang ada di layanan web.

Untuk melakukan aksi DELETE pada layanan web, ikuti langkah-langkah berikut:

  1. Masuk ke layanan Wiremock dihttps://app.wiremock.cloud dan klik pada bagian Stubs dari API contoh. Kemudian, buat stub baru.
  2. Lengkapi permintaan dengan data berikut:
  1. Simpan stub baru.
  2. Di proyek Flutter, tambahkan metode deletePizza ke kelas HttpHelper di file http_helper.dart:
Future<String> deletePizza(int id) async {
  const deletePath = '/pizza';
  Uri url = Uri.https(authority, deletePath);
  http.Response r = await http.delete(
    url,
  );
  return r.body;
}
  1. Di file main.dart, di metode build dari kelas _MyHomePageState, refactor itemBuilder dari ListView.builder sehingga ListTile terkandung dalam widget Dismissible, sebagai berikut:
return ListView.builder(
    itemCount: (pizzas.data == null) ? 0 : pizzas.data.length,
    itemBuilder: (BuildContext context, int position) {
        return Dismissible(
                    key: Key(position.toString()),
                    onDismissed: (item) {
                      HttpHelper helper = HttpHelper();
                      pizzas.data!.removeWhere(
                          (element) => element.id == pizzas.data![position].id);
                      helper.deletePizza(pizzas.data![position].id!);
                    },
                    child: ListTile(...
  1. Jalankan aplikasi. Ketika Anda swipe elemen apa pun dari daftar pizza, ListTile akan menghilang.

Praktikum ini cukup mirip dengan dua praktikum sebelumnya, tetapi di sini, menggunakan DELETE, verb yang secara konvensional digunakan ketika aplikasi meminta server web untuk menghapus potongan data yang ada.

Inilah sebabnya harus menginstruksikan layanan web mock untuk menerima DELETE di alamat /pizza, sehingga dapat mencoba mengirim beberapa data ke sana dan itu akan merespons dengan pesan sukses.

Setelah membuat stub DELETE di Wiremock, kita membuat metode deletePizza, yang bekerja seperti metode postPizza dan putPizza; itu hanya memerlukan ID pizza untuk menghapusnya.

Untuk membuat pengguna menghapus Pizza yang ada, kita menggunakan widget Dismissible, yang digunakan ketika Anda ingin swipe elemen (kiri atau kanan) untuk menghapusnya dari layar.

  1. Selesaikan semua fitur di aplikasi PBL kelompok Anda mulai dari materi Codelab 9: Camera hingga Codelab 14: Restful API.
  2. Push progress pekerjaan PBL kelompok Anda ke repository git, lalu presentasikan progress pada pertemuan berikutnya kepada dosen Anda!

Selamat Anda telah menyelesaikan Codelab ini. Anda telah mempelajari terkait Restful API di Flutter dan contoh penggunaannya.

Dalam codelab terakhir ini, Anda telah melihat beberapa cara untuk mengelola data di aplikasi Flutter dan membaca serta menulis data ke layanan web.

Pertama, Anda telah melihat proses mengonversi model Dart menjadi format JSON, format data yang ringan dan diterima secara universal.

Anda juga telah melihat cara menserialisasi dan mendeserialisasi objek Dart ke dan dari JSON serta belajar cara menangani data JSON yang tidak kompatibel dengan model aplikasi Anda, dengan beberapa strategi untuk menyesuaikan dan mengatasinya.

Anda telah menggunakan SharedPreferences dari pekan lalu, sistem penyimpanan key-value sederhana yang memungkinkan menyimpan jumlah data kecil. Anda kemudian telah melihat cara menggunakan SecureStorage sebagai metode untuk menyimpan data sensitif seperti kata sandi.

Anda belajar cara mengakses filesystem dengan memanfaatkan paket path_provider, yang memungkinkan pengembang untuk mengambil lokasi di filesystem perangkat. Anda telah melihat cara membuat, membaca, dan menulis file di perangkat Anda.

Mengalihkan fokus ke komunikasi dengan layanan web, Anda telah belajar tentang desain klien HTTP dan melihat cara mengambil data dari layanan web serta mengirim data ke server menggunakan verb POST, PUT, dan DELETE.

Singkatnya, dalam codelab ini, Anda mendapatkan pandangan mendalam tentang penanganan JSON, persistensi data, penyimpanan aman, dan komunikasi dengan internet. Anda sekarang telah dilengkapi dengan pengetahuan dan keterampilan yang diperlukan untuk mengelola data secara efektif dan berkomunikasi dengan server remote di aplikasi Flutter Anda.

Apa selanjutnya?

Silakan cek beberapa sumber belajar lainnya...

Referensi