본문으로 건너뛰기

เนื้อหาภาษาไทย / 태국어 콘텐츠

시스템 가이드 검수 완료된 콘텐츠

การใช้ GraphQL แทน REST API ใน Flutter

บทความนี้แนะนำการใช้ GraphQL แทน REST API ใน Flutter เพื่อเพิ่มประสิทธิภาพในการจัดการข้อมูล โดย GraphQL ช่วยแก้ปัญหา over-fetching และ under-fetching ที่พบใน RE...

การใช้ GraphQL แทน REST API ใน Flutter

ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับการสร้าง Flutter Plugin เพื่อเข้าถึง Native code ผ่าน Platform Channels ในบทความนี้ เราจะมาสำรวจอีกหนึ่งทางเลือกในการจัดการข้อมูล นั่นคือการใช้ GraphQL แทน REST API ใน Flutter ซึ่งจะช่วยให้การดึงข้อมูลมีประสิทธิภาพมากยิ่งขึ้น

REST API เป็นสถาปัตยกรรมที่ใช้กันอย่างแพร่หลายในการสร้าง API แต่ก็มีข้อจำกัดบางอย่าง เช่น การดึงข้อมูลที่มากเกินความจำเป็น (over-fetching) หรือการดึงข้อมูลหลายครั้งเพื่อรวบรวมข้อมูลที่ต้องการ (under-fetching) GraphQL เข้ามาแก้ปัญหาเหล่านี้ด้วยการให้ client ระบุข้อมูลที่ต้องการได้อย่างแม่นยำ ทำให้การดึงข้อมูลมีประสิทธิภาพและยืดหยุ่นมากขึ้น

ทำไมต้อง GraphQL?

  • Over-fetching และ Under-fetching: REST API มักจะส่งคืนข้อมูลมากกว่าที่ client ต้องการ หรือ client อาจต้องทำการ request หลายครั้งเพื่อรวบรวมข้อมูลที่ต้องการ GraphQL แก้ปัญหานี้ด้วยการให้ client ระบุ field ที่ต้องการได้อย่างชัดเจน
  • Strong Typing: GraphQL มีระบบ type ที่แข็งแกร่ง ทำให้การตรวจสอบความถูกต้องของข้อมูลทำได้ง่ายขึ้น และช่วยลดข้อผิดพลาดที่อาจเกิดขึ้นใน runtime
  • Introspection: GraphQL schema สามารถ query ได้ ทำให้ client สามารถ discover API ได้ง่ายขึ้น
  • Single Endpoint: GraphQL มักจะมี endpoint เดียวสำหรับการ query และ mutation ทำให้การจัดการ API ง่ายขึ้น

GraphQL Client ใน Flutter

ในการใช้งาน GraphQL ใน Flutter เราจำเป็นต้องใช้ GraphQL client library ที่จะช่วยให้เราสามารถส่ง GraphQL queries และ mutations ไปยัง server ได้ มี library หลายตัวให้เลือกใช้ แต่ที่นิยมกันคือ:

  • graphql_flutter: เป็น library ที่ครอบคลุมและใช้งานง่าย รองรับทั้ง query, mutation, subscription และ caching
  • ferry: เป็น library ที่เน้นประสิทธิภาพและ scalability เหมาะสำหรับแอปพลิเคชันขนาดใหญ่

ในบทความนี้ เราจะใช้ graphql_flutter เนื่องจากเป็น library ที่เริ่มต้นใช้งานได้ง่าย

การติดตั้ง graphql_flutter

เพิ่ม dependency graphql_flutter ลงใน pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.0 # ตรวจสอบ version ล่าสุด

จากนั้นทำการ run flutter pub get เพื่อติดตั้ง dependency

การตั้งค่า GraphQL Client

เริ่มต้นด้วยการสร้าง GraphQLClient instance โดยระบุ GraphQL endpoint:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final HttpLink httpLink = HttpLink(
    'https://rickandmortyapi.graphcdn.app/', // ตัวอย่าง GraphQL endpoint
  );

  final ValueNotifier<GraphQLClient> client = ValueNotifier(
    GraphQLClient(
      link: httpLink,
      cache: GraphQLCache(store: HiveStore()),
    ),
  );

  var app = MaterialApp(
    title: 'GraphQL Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: GraphQLProvider(
      client: client,
      child: const MyApp(),
    ),
  );

  runApp(app);
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GraphQL Demo'),
      ),
      body: Center(
        child: Text('GraphQL Demo'),
      ),
    );
  }
}

ในโค้ดนี้ เราได้สร้าง GraphQLClient instance โดยระบุ endpoint เป็น https://rickandmortyapi.graphcdn.app/ ซึ่งเป็น GraphQL API สำหรับข้อมูล Rick and Morty นอกจากนี้เรายังใช้ GraphQLProvider เพื่อให้ client สามารถเข้าถึงได้จากทุก widget ใน widget tree

Queries

Queries ใช้สำหรับดึงข้อมูลจาก GraphQL server ตัวอย่างเช่น เราต้องการดึงรายชื่อตัวละครทั้งหมดจาก Rick and Morty API:

query GetCharacters {
  characters {
    results {
      id
      name
      status
      species
    }
  }
}

เราสามารถใช้ Query widget จาก graphql_flutter เพื่อ execute query นี้:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class CharacterList extends StatelessWidget {
  const CharacterList({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(
          '''
            query GetCharacters {
              characters {
                results {
                  id
                  name
                  status
                  species
                }
              }
            }
          ''',
        ),
      ),
      builder: (QueryResult result, {VoidCallback? refetch, FetchMore? fetchMore}) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

        if (result.isLoading) {
          return const Center(child: CircularProgressIndicator());
        }

        final characterList = result.data?['characters']['results'];

        if (characterList == null) {
          return const Text('No characters found');
        }

        return ListView.builder(
          itemCount: characterList.length,
          itemBuilder: (context, index) {
            final character = characterList[index];
            return ListTile(
              title: Text(character['name']),
              subtitle: Text('${character['status']} - ${character['species']}'),
            );
          },
        );
      },
    );
  }
}

ในโค้ดนี้ Query widget จะทำการ execute query และ render UI ตามสถานะของ query (loading, error, data)

Mutations

Mutations ใช้สำหรับแก้ไขข้อมูลบน GraphQL server (เช่น สร้าง, อัปเดต, ลบ) เนื่องจาก Rick and Morty API เป็น public API ที่อ่านได้อย่างเดียว (read-only) เราจะไม่สามารถแสดงตัวอย่าง mutation ได้ แต่หลักการใช้งานก็คล้ายคลึงกับ queries คือใช้ Mutation widget จาก graphql_flutter

สมมติว่าเรามี GraphQL API ที่อนุญาตให้สร้าง user ใหม่ เราสามารถเขียน mutation ได้ดังนี้:

mutation CreateUser($name: String!, $email: String!) {
  createUser(name: $name, email: $email) {
    id
    name
    email
  }
}

และใช้ Mutation widget ใน Flutter:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

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

  @override
  State<CreateUserForm> createState() => _CreateUserFormState();
}

class _CreateUserFormState extends State<CreateUserForm> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(
          '''
            mutation CreateUser(\$name: String!, \$email: String!) {
              createUser(name: \$name, email: \$email) {
                id
                name
                email
              }
            }
          ''',
        ),
        onCompleted: (dynamic result) {
          // Handle successful mutation
          print('User created: ${result?['createUser']['name']}');
        },
        onError: (error) {
          // Handle error
          print('Error creating user: $error');
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return Form(
          key: _formKey,
          child: Column(
            children: <Widget>[
              TextFormField(
                controller: _nameController,
                decoration: const InputDecoration(labelText: 'Name'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your name';
                  }
                  return null;
                },
              ),
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(labelText: 'Email'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your email';
                  }
                  return null;
                },
              ),
              ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    runMutation({
                      'name': _nameController.text,
                      'email': _emailController.text,
                    });
                  }
                },
                child: const Text('Create User'),
              ),
              if (result?.isLoading == true) const CircularProgressIndicator(),
            ],
          ),
        );
      },
    );
  }
}

ในโค้ดนี้ Mutation widget จะรับ RunMutation function ซึ่งเราสามารถเรียกใช้เพื่อ execute mutation โดยส่ง variables ที่จำเป็น (name และ email) ไปด้วย

ข้อผิดพลาดที่พบบ่อย

  • GraphQL Endpoint ไม่ถูกต้อง: ตรวจสอบให้แน่ใจว่า GraphQL endpoint ที่ระบุถูกต้องและสามารถเข้าถึงได้
  • Schema Mismatch: ตรวจสอบให้แน่ใจว่า query หรือ mutation ที่เขียนสอดคล้องกับ schema ของ GraphQL API
  • Variables ไม่ถูกต้อง: ตรวจสอบให้แน่ใจว่า variables ที่ส่งไปยัง query หรือ mutation มี type ที่ถูกต้องและจำเป็นตามที่กำหนดไว้ใน schema
  • Network Error: ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต

สรุป

ในบทความนี้ เราได้เรียนรู้เกี่ยวกับการใช้ GraphQL แทน REST API ใน Flutter โดยใช้ graphql_flutter library เราได้เรียนรู้วิธีการตั้งค่า GraphQL client, การ execute queries และ mutations รวมถึงข้อผิดพลาดที่พบบ่อย การใช้ GraphQL ช่วยให้เราสามารถดึงข้อมูลได้อย่างมีประสิทธิภาพและยืดหยุ่นมากขึ้น ซึ่งเป็นประโยชน์อย่างมากในการพัฒนาแอปพลิเคชัน Flutter ที่ต้องการประสิทธิภาพสูง

ในบทความถัดไป เราจะมาเรียนรู้เกี่ยวกับการจัดการ dependency ด้วย GetX ซึ่งเป็น framework ที่ช่วยให้การจัดการ state, navigation และ dependency ใน Flutter ง่ายขึ้น

Office.in.th Custom Service

이 구조를 바탕으로 시스템을 개발하거나 맞춤 변경하고 싶으신가요?

Office.in.th는 엔터프라이즈 웹 애플리케이션, API 연동, 공공/민간 업무 관리 시스템을 설계·개발하며 장기 지원을 제공합니다.

소프트웨어 개발 서비스 보기