GraphQL #

Ketika kita merancang arsitektur komunikasi jaringan aplikasi kita, REST API sering kali menjadi pilihan utama yang melintas di pikiran. Namun, seiring dengan berkembangnya kompleksitas data aplikasi, kita akan mulai menghadapi berbagai tantangan efisiensi pada REST API. Misalnya, kebutuhan melakukan beberapa kali hit API ke endpoint yang berbeda hanya untuk menggambar satu halaman layar (under-fetching), atau menerima payload respons data yang sangat besar berisi properti yang sebenarnya tidak kita butuhkan di UI (over-fetching).

Untuk mengatasi keterbatasan tersebut, industri mengenalkan GraphQL. GraphQL adalah bahasa query untuk API dan runtime untuk mengeksekusi query tersebut dengan menggunakan sistem tipe data yang kita tentukan untuk data kita. Berbeda dari REST API di mana server backend menentukan struktur respons secara mutlak, GraphQL memberikan kendali penuh kepada klien (aplikasi Flutter kita) untuk meminta data secara dinamis. Kita menentukan secara presisi field apa saja yang kita butuhkan — tidak kurang, dan tidak lebih.

Dalam dokumen panduan ini, kita akan membedah konsep-konsep utama GraphQL, mempelajari konfigurasi klien multi-link (menggabungkan HTTP dan WebSockets), menggunakan widget deklaratif bawaan, hingga mengintegrasikan pemanggilan GraphQL secara imperatif di dalam state management Riverpod.

Konsep-Konsep Utama GraphQL #

Sebelum kita masuk ke setup teknis di Flutter, kita harus memahami tiga pilar utama operasi di dalam bahasa GraphQL:

  1. Query: Operasi untuk membaca atau mengambil data dari server. Di dalam REST API, Query setara dengan metode GET.
  2. Mutation: Operasi untuk memodifikasi atau menulis data di server (seperti membuat data baru, memperbarui data, atau menghapus data). Di dalam REST API, Mutation setara dengan metode POST, PUT, PATCH, dan DELETE.
  3. Subscription: Operasi real-time yang memanfaatkan koneksi dua arah (persistent connection) menggunakan protokol WebSockets. Server akan memancarkan data secara aktif ke klien setiap kali terjadi perubahan data tertentu di backend.

Berikut adalah contoh penulisan dokumen GraphQL untuk masing-masing operasi tersebut:

# 1. CONTOH QUERY: Mengambil info produk secara spesifik
query DapatkanDetailProduk($id: ID!) {
  produk(id: $id) {
    id
    nama
    harga
    # Kita hanya meminta nama & harga, properti lain tidak akan dikirim server
  }
}

# 2. CONTOH MUTATION: Menambahkan item ke keranjang belanja
mutation MasukkanKeKeranjang($idProduk: ID!, $qty: Int!) {
  tambahItem(idProduk: $idProduk, qty: $qty) {
    statusSukses
    pesanRespons
    keranjang {
      totalHarga
    }
  }
}

# 3. CONTOH SUBSCRIPTION: Memantau update status pesanan kurir secara real-time
subscription PantauKurir($idPesanan: ID!) {
  updateStatusKurir(idPesanan: $idPesanan) {
    koordinatLintang
    koordinatBujur
    statusPengiriman
  }
}

Perbandingan REST API vs GraphQL #

Mari kita visualisasikan perbedaan mendasar bagaimana data dipertukarkan antara REST API dan GraphQL untuk memahami mengapa GraphQL sangat efisien dalam meminimalkan konsumsi kuota internet perangkat:

graph TD
    classDef default stroke:#333,stroke-width:2px;
    
    subgraph REST_API["REST API (Multiple Endpoints)"]
        R1["GET /produk/123"] -->|1. Request| S1["Server REST"]
        S1 -->|Kembalikan full produk| R1
        R2["GET /produk/123/ulasan"] -->|2. Request| S1
        S1 -->|Kembalikan list ulasan| R2
        R3["GET /produk/123/penjual"] -->|3. Request| S1
        S1 -->|Kembalikan profil penjual| R3
    end
    
    subgraph GraphQL_API["GraphQL API (Single Endpoint)"]
        G1["POST /graphql (Query spesifik)"] -->|1. Single Request| S2["Server GraphQL"]
        S2 -->|Kembalikan data terfilter| G1
    end

Pada REST API, kita terpaksa melakukan 3 kali request ke 3 endpoint berbeda untuk menampilkan informasi produk, ulasan, dan detail penjual. Pada GraphQL, kita cukup mengirimkan 1 request berisi struktur query gabungan ke endpoint /graphql, dan server akan membalas dengan struktur data terpadu yang persis seperti yang kita minta dalam satu kali transaksi jaringan.


Instalasi Ekosistem GraphQL di Flutter #

Untuk menggunakan GraphQL di Flutter, komunitas menyediakan pustaka graphql_flutter. Pustaka ini dilengkapi dengan sistem manajemen cache berbasis Hive, interceptor tautan (links), serta widget-widget deklaratif bawaan.

Daftarkan dependensinya di dalam berkas pubspec.yaml proyek kita:

dependencies:
  # Pustaka utama GraphQL Flutter
  graphql_flutter: ^5.2.0-beta.7

Konfigurasi GraphQL Client yang Komprehensif #

Di dalam GraphQL, semua konfigurasi rute komunikasi diatur menggunakan sistem rantai tautan (Link Chain). Kita dapat menggabungkan tautan HTTP (HttpLink) untuk menangani Query/Mutation biasa dan tautan WebSockets (WebSocketLink) untuk menangani Subscription real-time.

Kita juga memanfaatkan AuthLink untuk menyisipkan token keamanan secara otomatis ke setiap headers request.

Berikut adalah kode inisialisasi GraphQLClient lengkap yang aman untuk skala produksi:

// core/network/graphql_client_config.dart
import 'package:graphql_flutter/graphql_flutter.dart';
import '../../core/auth/token_storage.dart';

class GraphQLClientConfig {
  static GraphQLClient inisialisasiClient() {
    // 1. Tautan Autentikasi untuk menyisipkan jwt token ke header request
    final tautanAuth = AuthLink(
      getToken: () async {
        final token = await TokenStorage.ambilAccessToken();
        return token != null ? 'Bearer $token' : null;
      },
    );

    // 2. Tautan HTTP untuk menangani Query & Mutation biasa
    final tautanHttp = HttpLink('https://api.tokokita.com/graphql');

    // 3. Tautan WebSocket untuk menangani Subscription real-time
    final tautanWs = WebSocketLink(
      'wss://api.tokokita.com/graphql',
      config: SocketClientConfig(
        autoReconnect: true, // Otomatis menyambung ulang jika koneksi terputus
        inactivityTimeout: const Duration(seconds: 30),
        initialPayload: () async {
          // Mengirimkan token autentikasi saat jabat tangan (handshake) websocket awal
          final token = await TokenStorage.ambilAccessToken();
          return {'Authorization': 'Bearer $token'};
        },
      ),
    );

    // 4. Memisahkan jalur request secara otomatis: 
    // Jika request berjenis subscription, alirkan ke WebSocketLink. Jika tidak, alirkan ke HttpLink.
    final tautanGabungan = Link.split(
      (request) => request.isSubscription,
      tautanWs,
      tautanHttp,
    );

    // 5. Gabungkan rantai tautan autentikasi di depan tautan komunikasi utama
    final rantaiTautanLengkap = tautanAuth.concat(tautanGabungan);

    return GraphQLClient(
      link: rantaiTautanLengkap,
      // Menggunakan HiveStore sebagai database cache lokal yang persisten
      cache: GraphQLCache(store: HiveStore()),
      defaultPolicies: DefaultPolicies(
        query: Policies(
          // Kebijakan cache standar: muat dari cache lokal dulu, lalu update di latar belakang
          fetch: FetchPolicy.cacheAndNetwork,
        ),
        watchQuery: Policies(
          fetch: FetchPolicy.cacheAndNetwork,
        ),
        mutate: Policies(
          // Mutasi data harus selalu ditembak langsung ke server jaringan API
          fetch: FetchPolicy.networkOnly,
        ),
      ),
    );
  }
}

Inisialisasi dan Registrasi GraphQLProvider #

Agar widget-widget di dalam aplikasi kita dapat menggunakan instansi klien yang sama, kita harus menginisialisasi penyimpanan cache Hive lokal dan membungkus MaterialApp dengan widget GraphQLProvider di dalam file main.dart.

// main.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'core/network/graphql_client_config.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Wajib diinisialisasi untuk menyiapkan database Hive lokal bagi cache GraphQL
  await initHiveForFlutter();

  final clientGraphQL = GraphQLClientConfig.inisialisasiClient();

  runApp(
    GraphQLProvider(
      // Membungkus instansi dengan ValueNotifier agar peka terhadap perubahan status
      client: ValueNotifier(clientGraphQL),
      child: const AplikasiKita(),
    ),
  );
}

class AplikasiKita extends StatelessWidget {
  const AplikasiKita({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(body: Center(child: Text('GraphQL Siap Digunakan'))),
    );
  }
}

Membaca Data Menggunakan Query Widget #

graphql_flutter menyediakan widget pembangun deklaratif bernama Query untuk membaca data dari server. Widget ini menangani siklus hidup pengambilan data, loading state, error state, pencatatan log cache, dan pagination secara terintegrasi.

Berikut adalah implementasi layar daftar produk dengan memanfaatkan fitur pagination fetchMore:

// presentation/screens/produk_list_screen.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import '../../domain/entities/produk.dart';

// Menulis query sebagai string multi-line
const String _queryAmbilProduk = r'''
  query AmbilDaftarProduk($kategori: String, $halaman: Int) {
    produkList(kategori: $kategori, page: $halaman) {
      items {
        id
        nama
        harga
        thumbnail
      }
      adaHalamanBerikutnya
    }
  }
''';

class LayarDaftarProdukGraphQL extends StatelessWidget {
  final String? filterKategori;

  const LayarDaftarProdukGraphQL({super.key, this.filterKategori});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Daftar Produk GraphQL')),
      body: Query(
        options: QueryOptions(
          document: gql(_queryAmbilProduk),
          variables: {
            'kategori': filterKategori,
            'halaman': 1,
          },
          fetchPolicy: FetchPolicy.cacheAndNetwork,
        ),
        builder: (QueryResult hasilResult, {VoidCallback? refetch, FetchMore? fetchMore}) {
          // 1. Kondisi error terjadi
          if (hasilResult.hasException) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Error: ${hasilResult.exception.toString()}'),
                  const SizedBox(height: 12),
                  ElevatedButton(onPressed: refetch, child: const Text('Coba Lagi')),
                ],
              ),
            );
          }

          // 2. Kondisi loading awal (saat data cache kosong)
          if (hasilResult.isLoading && hasilResult.data == null) {
            return const Center(child: CircularProgressIndicator());
          }

          // 3. Kondisi sukses mendapatkan data
          final dataMap = hasilResult.data!['produkList'];
          final List itemsMentah = dataMap['items'];
          final bool adaHalamanLanjut = dataMap['adaHalamanBerikutnya'] as bool;

          // Mengonversi data map menjadi list entity
          final listProduk = itemsMentah.map((json) => Produk.fromJson(json)).toList();

          return Column(
            children: [
              if (hasilResult.isLoading)
                const LinearProgressIndicator(), // Indikator refresh di latar belakang
              Expanded(
                child: ListView.builder(
                  itemCount: listProduk.length + (adaHalamanLanjut ? 1 : 0),
                  itemBuilder: (context, index) {
                    if (index == listProduk.length) {
                      // Tombol Load More untuk pagination asinkron
                      return Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: OutlinedButton(
                          onPressed: () {
                            fetchMore!(
                              FetchMoreOptions(
                                variables: {'halaman': 2}, // Coba ambil halaman kedua
                                updateQuery: (Map<String, dynamic>? dataLama, Map<String, dynamic>? dataBaru) {
                                  // Gabungkan daftar item lama dengan item baru
                                  final List itemsLama = dataLama!['produkList']['items'];
                                  final List itemsBaru = dataBaru!['produkList']['items'];
                                  
                                  dataBaru['produkList']['items'] = [...itemsLama, ...itemsBaru];
                                  return dataBaru;
                                },
                              ),
                            );
                          },
                          child: const Text('Muat Lebih Banyak'),
                        ),
                      );
                    }
                    
                    final produk = listProduk[index];
                    return ListTile(
                      leading: Image.network(produk.thumbnail, width: 50, errorBuilder: (_, __, ___) => const Icon(Icons.image)),
                      title: Text(produk.nama),
                      subtitle: Text('Rp ${produk.harga.toStringAsFixed(0)}'),
                    );
                  },
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

Mengubah Data Menggunakan Mutation Widget #

Untuk mengirimkan data atau mengubah state di server (seperti menambah item belanja), kita menggunakan widget Mutation. Pustaka ini juga mengizinkan kita melakukan pembaruan cache lokal secara instan lewat properti update agar UI langsung sinkron tanpa perlu reload koneksi.

// presentation/widgets/tombol_tambah_keranjang.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

const String _mutationTambahItem = r'''
  mutation TambahItemKeranjang($id: ID!, $qty: Int!) {
    tambahItem(idProduk: $id, qty: $qty) {
      statusSukses
      pesanRespons
      keranjang {
        totalItem
        totalHarga
      }
    }
  }
''';

// Kita juga butuh query get keranjang untuk mengupdate cachenya nanti
const String _queryAmbilKeranjang = r'''
  query AmbilKeranjangBelanja {
    keranjang {
      totalItem
      totalHarga
    }
  }
''';

class TombolTambahKeranjangGraphQL extends StatelessWidget {
  final String idProduk;

  const TombolTambahKeranjangGraphQL({super.key, required this.idProduk});

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(_mutationTambahItem),
        // Mengubah cache lokal secara manual agar UI keranjang terupdate secara instan
        update: (GraphQLDataProxy cacheProxy, QueryResult? hasilMutasi) {
          if (hasilMutasi?.data != null) {
            // Tulis ulang cache query keranjang dengan hasil data terbaru dari mutasi
            cacheProxy.writeQuery(
              Request(operation: Operation(document: gql(_queryAmbilKeranjang))),
              data: {
                'keranjang': hasilMutasi!.data!['tambahItem']['keranjang'],
              },
            );
          }
        },
        onCompleted: (Map<String, dynamic>? dataMap) {
          if (dataMap != null && dataMap['tambahItem']['statusSukses']) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Berhasil ditambahkan ke keranjang belanja kita!')),
            );
          }
        },
        onError: (OperationException? error) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Error: ${error?.graphqlErrors.first.message}')),
          );
        },
      ),
      builder: (RunMutation picuMutasi, QueryResult? hasilResult) {
        final apakahSedangLoading = hasilResult?.isLoading ?? false;

        return ElevatedButton.icon(
          onPressed: apakahSedangLoading
              ? null
              : () {
                  // Memicu pengiriman mutasi dengan menyertakan variabel payload
                  picuMutasi({'id': idProduk, 'qty': 1});
                },
          icon: apakahSedangLoading
              ? const SizedBox(
                  width: 18,
                  height: 18,
                  child: CircularProgressIndicator(strokeWidth: 2),
                )
              : const Icon(Icons.shopping_bag_outlined),
          label: const Text('Beli Sekarang'),
        );
      },
    );
  }
}

Sinkronisasi Real-Time Menggunakan Subscription #

Fitur real-time di GraphQL berjalan di atas protokol WebSocket. Kita menggunakan widget Subscription untuk mendengarkan aliran data konstan yang dipancarkan oleh server secara aktif tanpa perlu memicu polling request berulang kali.

// presentation/widgets/widget_lacak_kurir.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

const String _subLacakKurir = r'''
  subscription LacakKurirToko($pesananId: ID!) {
    updateStatusKurir(idPesanan: $idPesanan) {
      statusPengiriman
      koordinatLintang
      koordinatBujur
    }
  }
''';

class WidgetLacakKurir extends StatelessWidget {
  final String idPesanan;

  const WidgetLacakKurir({super.key, required this.idPesanan});

  @override
  Widget build(BuildContext context) {
    return Subscription(
      options: SubscriptionOptions(
        document: gql(_subLacakKurir),
        variables: {'pesananId': idPesanan},
      ),
      builder: (QueryResult hasilResult) {
        if (hasilResult.isLoading) {
          return const Center(child: Text('Menghubungkan ke satelit GPS kurir...'));
        }
        
        if (hasilResult.hasException) {
          return Center(child: Text('Gagal melacak: ${hasilResult.exception.toString()}'));
        }
        
        if (hasilResult.data == null) {
          return const Center(child: Text('Menunggu data lokasi kurir terbaru...'));
        }

        final dataStatus = hasilResult.data!['updateStatusKurir'];
        final String status = dataStatus['statusPengiriman'];
        final double lat = dataStatus['koordinatLintang'];
        final double lng = dataStatus['koordinatBujur'];

        return Card(
          elevation: 4,
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Text('Status Kurir: $status', style: const TextStyle(fontWeight: FontWeight.bold)),
                const SizedBox(height: 8),
                Text('Koordinat Saat Ini: $lat, $lng'),
              ],
            ),
          ),
        );
      },
    );
  }
}

Memilih Fetch Policy yang Tepat #

GraphQL client memiliki mekanisme manipulasi cache lokal yang sangat canggih melalui pengaturan Fetch Policy. Kita dapat menentukan bagaimana klien harus berinteraksi dengan cache lokal dan server jaringan internet:

  1. FetchPolicy.cacheFirst (Bawaan): Klien akan membaca cache lokal terlebih dahulu. Jika data cache ada, langsung tampilkan ke UI dan jangan pernah melakukan hit ke jaringan internet. Gunakan untuk data yang jarang berubah (seperti daftar provinsi).
  2. FetchPolicy.cacheAndNetwork: Klien akan langsung merender data dari cache lokal jika tersedia (sehingga layar termuat sangat cepat), lalu secara simultan melakukan request jaringan internet di latar belakang. Saat respons jaringan kembali, klien akan memperbarui database cache lokal dan memperbarui UI secara halus. Sangat direkomendasikan untuk daftar postingan medsos atau halaman beranda.
  3. FetchPolicy.networkOnly: Klien akan mengabaikan cache lokal dan langsung memaksa melakukan request jaringan internet. Gunakan ini untuk mutasi pembayaran, keranjang belanja terbaru, atau data transaksi sensitif.
  4. FetchPolicy.cacheOnly: Klien hanya akan membaca data dari cache lokal tanpa pernah mengirimkan request ke jaringan internet. Berguna saat aplikasi berjalan dalam mode luring (offline mode).
  5. FetchPolicy.noCache: Klien akan langsung mengambil data dari jaringan internet tanpa pernah menyimpan hasilnya ke database cache lokal kita.

GraphQL Fragments: Efisiensi Deklarasi Field #

Sering kali kita harus mendefinisikan kolom-kolom properti yang sama di berbagai jenis query yang berbeda (seperti id, nama, dan harga produk). Menuliskan kolom-kolom tersebut berulang kali akan melanggar prinsip DRY (Don’t Repeat Yourself).

Kita dapat membuat Fragments untuk mendefinisikan sekelompok field yang re-usable:

// 1. Definisikan fragment re-usable kita
const String _fragmentPropertiProduk = '''
  fragment DetailProdukFields on Produk {
    id
    nama
    harga
    thumbnail
  }
''';

// 2. Gunakan fragment di dalam Query detail produk
const String _queryDetailProduk = '''
  $_fragmentPropertiProduk
  
  query DapatkanDetail($id: ID!) {
    produkDetail(id: $id) {
      ...DetailProdukFields
      deskripsiLengkap
      stokBarang
    }
  }
''';

// 3. Gunakan fragment yang sama di dalam Query pencarian produk
const String _queryCariProduk = '''
  $_fragmentPropertiProduk
  
  query Cari($q: String!) {
    produkCari(query: $q) {
      ...DetailProdukFields
    }
  }
''';

Menggunakan GraphQL Secara Imperatif dengan Riverpod #

Menggunakan widget pembangun seperti Query atau Mutation di dalam kode UI Flutter terkadang membuat struktur berkas visual kita menjadi sangat panjang dan sulit dibaca. Kita dapat memisahkan seluruh logika query dan mutasi dari UI dengan cara memanggil instansi GraphQLClient secara imperatif di dalam layer State Management Notifier (Riverpod).

Berikut adalah implementasi controller produk menggunakan GraphQL secara imperatif:

// presentation/providers/graphql_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import '../../domain/entities/produk.dart';
import '../errors/exceptions.dart';

// Provider untuk mengekspos instansi GraphQLClient
final graphqlClientProvider = Provider<GraphQLClient>((ref) {
  return GraphQLClientConfig.inisialisasiClient();
});

class ProdukGraphQLNotifier extends AutoDisposeAsyncNotifier<List<Produk>> {
  @override
  Future<List<Produk>> build() async {
    return _ambilDataDariJaringan();
  }

  Future<List<Produk>> _ambilDataDariJaringan() async {
    // 1. Membaca instansi client dari Provider DI
    final klien = ref.read(graphqlClientProvider);

    // 2. Melakukan query secara imperatif tanpa menggunakan widget builder
    final QueryResult hasil = await klien.query(
      QueryOptions(
        document: gql(_queryAmbilProduk),
        variables: {'halaman': 1},
        fetchPolicy: FetchPolicy.cacheAndNetwork,
      ),
    );

    // 3. Tangani exception secara terstruktur
    if (hasil.hasException) {
      final error = hasil.exception!;
      throw AppException(
        error.graphqlErrors.isNotEmpty 
            ? error.graphqlErrors.first.message 
            : 'Gagal memuat produk dari server GraphQL.',
      );
    }

    // 4. Kembalikan data murni hasil parsing
    final List itemsMentah = hasil.data!['produkList']['items'];
    return itemsMentah.map((json) => Produk.fromJson(json)).toList();
  }

  // Melakukan mutasi secara imperatif
  Future<void> tambahItemBelanja(String idProduk) async {
    final klien = ref.read(graphqlClientProvider);

    final QueryResult hasil = await klien.mutate(
      MutationOptions(
        document: gql(_mutationTambahItem),
        variables: {'id': idProduk, 'qty': 1},
      ),
    );

    if (hasil.hasException) {
      throw AppException(hasil.exception!.graphqlErrors.first.message);
    }
    
    // Refresh status data Notifier kita setelah mutasi berhasil
    ref.invalidateSelf();
  }
}

final daftarProdukGraphQLProvider = AsyncNotifierProvider.autoDispose<ProdukGraphQLNotifier, List<Produk>>(
  ProdukGraphQLNotifier.new,
);

Dengan arsitektur fungsional ini, UI widget kita cukup memantau data menggunakan ref.watch(daftarProdukGraphQLProvider) secara murni tanpa perlu bersentuhan dengan sintaksis GraphQL gql atau widget builder bawaan pustaka.

Ringkasan #

  • GraphQL memberikan kendali penuh kepada klien untuk meminta properti data secara dinamis guna meminimalkan over-fetching dan under-fetching data.
  • Tiga Operasi Utama GraphQL terdiri atas Query (membaca data), Mutation (mengubah data), dan Subscription (aliran data real-time berbasis WebSockets).
  • Multi-Link Chain diimplementasikan dengan menggabungkan tautan HTTP (HttpLink) dan WebSockets (WebSocketLink) menggunakan fungsi pembagi rute Link.split.
  • AuthLink bertugas menyisipkan token keamanan Bearer ke headers request secara otomatis untuk semua komunikasi GraphQL kita.
  • Persistent Caching memanfaatkan HiveStore lokal untuk menyajikan data secara instan saat offline lewat konfigurasi FetchPolicy.cacheAndNetwork.
  • GraphQL Fragments menyederhanakan deklarasi field yang re-usable untuk mencegah duplikasi penulisan kolom data di berbagai query.
  • GraphQL Imperatif dapat diintegrasikan secara bersih dengan Riverpod Notifier untuk memisahkan seluruh query data dari layar UI widget kita.

← Sebelumnya: Authentication   Berikutnya: Best Practice →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact