티스토리 뷰

Flutter는 모바일 개발자들에게 HTTP 요청 처리를 간편하게 제공하며, RESTful API 통합을 지원합니다. 특히 HTTP DELETE 요청은 데이터 삭제 작업을 수행할 때 자주 사용됩니다. 이번 블로그에서는 Flutter로 HTTP DELETE 요청을 구현하는 방법을 자세히 다뤄보고, 이를 통해 효율적인 데이터 삭제 처리 방식을 익힐 수 있도록 돕겠습니다.

참고. Delete data on the internet

HTTP DELETE 요청이란?

HTTP DELETE 요청은 클라이언트에서 서버로 특정 리소스를 삭제하도록 요청하는 HTTP 메서드입니다. 이를 통해 서버 데이터베이스에서 불필요한 정보를 제거하거나, 사용자가 요청한 데이터를 삭제할 수 있습니다.

Flutter에서 HTTP DELETE 요청 구현하기

Flutter에서 HTTP DELETE 요청을 보내기 위해서는 http 패키지를 활용합니다. 이 패키지는 HTTP 통신을 간단히 처리할 수 있도록 다양한 메서드를 제공합니다.

1. 의존성 추가

Flutter 프로젝트에 HTTP 요청을 처리하려면 먼저 http 패키지를 추가해야 합니다.

dependencies:
  http: ^0.15.0

pubspec.yaml 파일에 위 코드를 추가한 후, flutter pub get 명령어로 패키지를 설치합니다.

2. DELETE 요청 함수 작성하기

Flutter에서 DELETE 요청을 보내는 함수는 다음과 같이 작성할 수 있습니다:

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

Future<void> deleteData(String id) async {
  final url = Uri.parse('https://example.com/data/$id');

  final response = await http.delete(url);

  if (response.statusCode == 200) {
    print('삭제 성공: ${response.body}');
  } else {
    print('삭제 실패: ${response.statusCode}');
  }
}

주요 구현 과정

1. URL 설정

삭제하려는 리소스의 URL을 명확히 설정해야 합니다. RESTful API에서는 일반적으로 리소스 ID를 포함한 엔드포인트를 사용합니다. 예: https://example.com/data/123.

2. 응답 처리

DELETE 요청이 성공하면 응답 코드 200 OK를 반환합니다. 따라서 response.statusCode를 통해 요청 성공 여부를 확인할 수 있습니다.

3. 에러 처리

에러 처리도 중요합니다. 요청이 실패했을 경우, 반환된 상태 코드를 기반으로 적절한 오류 메시지를 출력하거나 예외를 던질 수 있습니다.

DELETE 요청의 실제 사용 사례

1. 사용자 데이터 삭제

앱 사용자로부터 요청된 특정 데이터를 삭제하는 데 활용됩니다. 예를 들어, 사용자가 더 이상 필요하지 않은 게시물을 삭제하고 싶을 때 DELETE 요청이 사용됩니다.

2. 서버 측 리소스 관리

서버 데이터베이스에서 불필요하거나 오래된 데이터를 제거할 때 DELETE 메서드를 통해 효율적으로 처리할 수 있습니다.

예제: Flutter에서 리스트 항목 삭제하기

다음은 Flutter 앱에서 특정 항목을 삭제하는 예제입니다:

UI에서 삭제 요청 구현

class ItemList extends StatelessWidget {
  final List<String> items = ['Item 1', 'Item 2', 'Item 3'];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(items[index]),
          trailing: IconButton(
            icon: Icon(Icons.delete),
            onPressed: () async {
              await deleteData(items[index]);
            },
          ),
        );
      },
    );
  }
}

DELETE 요청 사용 시 주의사항

  1. 보안 고려
    민감한 데이터를 삭제할 때는 인증 및 권한 검증을 철저히 해야 합니다. 예를 들어, 토큰 기반 인증을 사용하여 허가된 사용자만 데이터를 삭제할 수 있도록 해야 합니다.
  2. 상태 코드 관리
    DELETE 요청의 응답 상태 코드(200, 404, 500 등)를 정확히 처리하여 사용자가 명확한 피드백을 받을 수 있도록 해야 합니다.
  3. 데이터 무결성
    삭제된 데이터가 필요할 경우를 대비하여 백업 시스템을 마련하거나, 삭제 전에 사용자 확인을 요청하는 기능을 추가할 수 있습니다.

결론

Flutter에서 HTTP DELETE 요청을 구현하는 것은 RESTful API와의 통합에 필수적인 기술입니다. 이를 통해 서버와 클라이언트 간의 데이터 삭제 작업을 간편하게 처리할 수 있으며, UI와 백엔드 간의 통신 효율성을 높일 수 있습니다. 위 내용을 참고하여 자신의 Flutter 프로젝트에 DELETE 요청 기능을 추가해 보세요.

 

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> 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 return an empty Album. After deleting,
    // you'll get an empty JSON `{}` response.
    // Don't return `null`, otherwise `snapshot.hasData`
    // will always return false on `FutureBuilder`.
    return Album.empty();
  } else {
    // If the server did not return a "200 OK response",
    // then throw an exception.
    throw Exception('Failed to delete album.');
  }
}

class Album {
  int? id;
  String? title;

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

  Album.empty();

  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> {
  late Future<Album> _futureAlbum;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Delete Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Delete Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: _futureAlbum,
            builder: (context, snapshot) {
              // If the connection is done,
              // check for response data or an error.
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text(snapshot.data?.title ?? 'Deleted'),
                      ElevatedButton(
                        child: const Text('Delete Data'),
                        onPressed: () {
                          setState(() {
                            _futureAlbum =
                                deleteAlbum(snapshot.data!.id.toString());
                          });
                        },
                      ),
                    ],
                  );
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
              }

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