티스토리 뷰

Flutter는 간단하고 효율적인 HTTP 패키지를 사용하여 RESTful API와 통신할 수 있는 강력한 기능을 제공합니다. 이번 포스팅에서는 Flutter를 이용해 데이터를 업데이트하는 방법을 알아보겠습니다. 특히 HTTP PUTPATCH 요청을 활용하여 서버에 데이터를 업데이트하는 과정을 상세히 다룰 것입니다.

참고. Update data over the internet

Flutter에서 데이터 업데이트의 기본 원리

Flutter에서 데이터를 업데이트하려면 다음과 같은 절차를 따릅니다:

  1. HTTP 요청을 통해 서버와 통신
  2. 요청 헤더와 본문 구성
  3. 서버의 응답 처리 및 상태 코드 확인
  4. UI 업데이트

이 과정은 비동기적으로 이루어지며, http 패키지를 사용하여 간단하게 구현할 수 있습니다.

1. HTTP 패키지 설치 및 설정

HTTP 요청을 보내기 위해 http 패키지를 설치해야 합니다. Flutter 프로젝트의 pubspec.yaml 파일에 다음을 추가하세요:

dependencies:
  http: ^0.15.0

이후 패키지를 가져옵니다:

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

2. PUT 요청과 PATCH 요청의 차이

  • PUT 요청: 객체 전체를 교체합니다. 서버에 보낸 데이터로 기존 데이터를 완전히 대체하려는 경우 사용합니다.
  • PATCH 요청: 특정 필드만 수정합니다. 일부 데이터만 업데이트하려는 경우 유용합니다.

예를 들어, 사용자 정보 업데이트를 살펴보겠습니다.

3. Flutter에서 PUT 요청 구현

다음은 Flutter에서 PUT 요청을 사용하여 데이터를 업데이트하는 코드 예제입니다:

Future<void> updateData(String id, Map<String, String> updatedData) async {
  final url = Uri.parse('https://example.com/items/$id');

  final response = await http.put(
    url,
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode(updatedData),
  );

  if (response.statusCode == 200) {
    print('Data updated successfully!');
  } else {
    print('Failed to update data: ${response.reasonPhrase}');
  }
}

주요 설명:

  • Uri.parse: 요청할 URL을 정의합니다.
  • http.put: PUT 요청을 보냅니다.
  • headers: 요청의 메타데이터를 포함합니다.
  • body: 업데이트할 데이터를 JSON으로 직렬화합니다.

4. Flutter에서 PATCH 요청 구현

PATCH 요청은 데이터의 특정 필드를 업데이트할 때 유용합니다. 다음은 코드 예제입니다:

Future<void> patchData(String id, Map<String, dynamic> partialUpdate) async {
  final url = Uri.parse('https://example.com/items/$id');

  final response = await http.patch(
    url,
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode(partialUpdate),
  );

  if (response.statusCode == 200) {
    print('Data partially updated!');
  } else {
    print('Failed to update data: ${response.reasonPhrase}');
  }
}

주요 설명:

  • http.patch: PATCH 요청을 보냅니다.
  • 부분 업데이트를 위해 필요한 필드만 partialUpdate에 포함합니다.

5. 서버 응답 처리 및 UI 갱신

서버 응답의 상태 코드를 확인하여 업데이트 결과를 처리합니다. 성공적으로 데이터를 업데이트한 후에는 Flutter의 상태 관리 방법(예: setState 또는 Provider)을 사용하여 UI를 새로 고칠 수 있습니다.

setState(() {
  items[index] = updatedItem;
});

데이터 업데이트 시 유의사항

  1. 예외 처리: 네트워크 실패나 서버 오류에 대비하여 예외를 처리해야 합니다.
  2. 권한 인증: 데이터 업데이트 요청 시 API 키 또는 토큰 인증이 필요할 수 있습니다.
  3. 데이터 유효성 검사: 서버에 전송하기 전에 데이터가 올바른지 확인하세요.

실전 팁: 디버깅 및 테스트

  • Postman 또는 cURL을 사용하여 서버 API를 테스트하고 올바른 응답을 확인하세요.
  • Flutter DevTools를 활용하여 요청 및 응답 로그를 분석하세요.

결론

Flutter에서 HTTP 요청을 사용하여 데이터를 업데이트하는 것은 RESTful API와 통신하는 데 필수적인 기술입니다. 이번 포스팅에서 설명한 PUT 및 PATCH 요청의 차이와 구현 방법을 활용하면, 더욱 강력하고 유연한 네트워크 애플리케이션을 개발할 수 있습니다.

 

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) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

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) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to update album.');
  }
}

class Album {
  final int id;
  final String title;

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

  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {
        'id': int id,
        'title': String title,
      } =>
        Album(
          id: id,
          title: title,
        ),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  final TextEditingController _controller = TextEditingController();
  late Future<Album> _futureAlbum;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Update Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Update Data Example'),
        ),
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8),
          child: FutureBuilder<Album>(
            future: _futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text(snapshot.data!.title),
                      TextField(
                        controller: _controller,
                        decoration: const InputDecoration(
                          hintText: 'Enter Title',
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          setState(() {
                            _futureAlbum = updateAlbum(_controller.text);
                          });
                        },
                        child: const Text('Update Data'),
                      ),
                    ],
                  );
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
              }

              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}