Flutter Akses Data di Internet

Pemrograman Mobile 2


 

Muhamad Saad Nurul Ishlah, M.Comp.

Dept. Sistem Informasi & Dept. Ilmu Komputer, Universitas Pakuan

Agenda Kuliah

  • Mengambil Data dari Internet
  • Parsing JSON di belakang layar
  • Menyimpan Data ke Internet

Mengambil Data dari Internet

Mengambil Data dari Internet

  • Pengambilan data dari internet merupaka sesuatu yang perlu untuk sebagian besar aplikasi
  • Dart dan Flutter menyediakan tools
    • package http
  • Beberapa protokol REST yang disupport http:
    • GET: mengambil data
    • POST: menyimpan data baru
    • PUT: memperbaharui data
    • DELETE: menghapus data

Catatan untuk Android

  • Harus melakukan deklarasi untuk penggunaan internet pada berkas Android manifest (AndroidManifest.xml)
<manifest xmlns:android...>
 ...
 <uses-permission android:name="android.permission.INTERNET" />
 <application ...
</manifest>

Catatan untuk macOS

  • Harus mengizinkan akses jaringan dalam berkas .entitlements
<key>com.apple.security.network.client</key>
<true/>

Langkah Penggunaan http

  1. Tambahkan dependency package http di pubspec.yaml
  2. Lakukan permintaan jaringan menggunakan http di file dart
  3. Ubah respon menjadi objek Dart
  4. Ambil dan tampilkan data 

1. Tambahkan package http

# pada file pubspec.yaml tambahkan dependency dari http
dependencies:
  http: <latest_version>
  • Tambahkan dependency package http versi terakhir
  • Simpan untuk mengunduh package (Visual Code Studio)
  • $ flutter pub get

1. Tambahkan package http

// import package pada file dart
import 'package:http/http.dart' as http;
  • Import package pada file Dart
  • Biasanya pada file yang khusus untuk melakukan pemanggilan jaringan

1. Tambahkan package http

<!-- Diperlukan untuk mengakses data dari internet -->
<uses-permission android:name="android.permission.INTERNET" />
  • Jika menggunakan Android, tambahkan izin untuk mengakses internet pada berkas AndroidManifest.xml

2. Lakukan Permintaan Jaringan

Future<http.Response> fetchAlbum() {
  return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
  • metode http.get() akan mengembalikan objek Future yang berisi Response
  • Future merupakan kelas inti dari Dart yang digunakan untuk operasi yang asinkronus
  • http.Response berisi data yang diterima ketika pemanggilan http sukses dilakukan

3. Ubah Respon

// Kelas Model sebagai skema objek data dart 
class Album {
  final int userId;
  final int id;
  final String title;

  Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}
  • Lakukan pengubahan http.Response menjadi objek Dart
  • JSON and serialization
  • Buat class yang mewakili model data, contoh Album
  • Perhatikan struktur JSON yang diakses

3. Ubah Respon

// Kelas Model sebagai skema objek data dart 
class Album {
  final int userId;
  final int id;
  final String title;

  Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

// Ubah objek http.Response menjadi objek Album
Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
  • Ubah respon menjadi JSON Map dengan dart:convert
  • Jika server mengembalikan respon OK (200), ubah JSON menjadi Album menggunakan metode fromJson()
  • JIka tidak, lempar exception

4. Panggil fungsi pengambilan Data

class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }
  // ···
}
  • Panggil fungsi pemanggilan data pada metode initState() atau pada didChangeDependencies(). Contoh fetchAlbum()
  • initState dipanggil sekali
  • gunakan pada didChangeDependencies jika ingin melakukan pemanggilan API setiap kali ada perubahan pada InheritedWidget
  • Tidak direkomendasikan dipanggil pada build()

5. Tampilkan Data pada UI

FutureBuilder<Album>(
  future: futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }

    // By default, show a loading spinner.
    return const CircularProgressIndicator();
  },
)
  • Gunakan widget FutureBuilder untuk menampilkan data pada UI – proses asinkronus
  • properti future – memanggil fungsi pengambilan data
  • properti builder – render UI. bisa menyesuaikan dengan status pemanggilan data (snapshot)

Contoh Lengkap

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

class Album {
  final int userId;
  final int id;
  final String title;

  Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

Melakukan Otentikasi

final response = await http.get(
  Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  // Send authorization headers to the backend.
  headers: {
    HttpHeaders.authorizationHeader: 'Basic your_api_token_here',
  },
);

Cukup menambahkan properti headers pada request dengan isi kelas HttpHeaders

Parsing JSON di belakang layar

Hindari "Jank"

  • Default – Dart melakukan proses dalam 1 thread (utas)
  • Namun kadang butuh komputasi yang lebih berat, misal
    • memparsing JSON yang besar
    • proses yang membutuhkan waktu > 16 milidetik
  • Menyebabkan "jank" - "halt"
    • kinerja aplikasi yang buruk
    • animasi tersendat/freeze
  • Lakukan proses komputasi yang berat di belakang layar
  • Proses sama dengan pengambilan data, tapi isolasi dengan metode compute()

Isolasi Proses Berat dg compute()

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return parsePhotos(response.body);
}

Isolasi Proses Berat dg compute()

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

Catatan Penggunaan compute

  • compute melakukan komunikasi dengan cara melempar pesan secara bolak-balik
  • pesan dapat berupa nilai primitif
    • null, num, bool, double, String
    • objek sederhana – List<Album>
  • akan mengalami error jika pesan berupa objek kompleks
    • Future
    • http.Response

Mengirim Data ke Internet

Menyimpan data ke Internet

Future<Album> createAlbum(String title) async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/albums'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  if (response.statusCode == 201) {
    // If the server did return a 201 CREATED response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 201 CREATED response,
    // then throw an exception.
    throw Exception('Failed to create album.');
  }
}

Memperbaharui data di Internet

Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to update album.');
  }
}

Menghapus data di Internet

Future<Album> deleteAlbum(String id) async {
  final http.Response response = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/$id'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
  );

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON. After deleting,
    // you'll get an empty JSON `{}` response.
    // Don't return `null`, otherwise `snapshot.hasData`
    // will always return false on `FutureBuilder`.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a "200 OK response",
    // then throw an exception.
    throw Exception('Failed to delete album.');
  }
}

Diskusi

Ada pertanyaan?

Bacaan Tambahan

Referensi

Terima Kasih

Flutter Akses Data di Internet

By Muhamad Ishlah

Flutter Akses Data di Internet

  • 1,274