2023. 4. 5. 14:25ㆍ모바일어플개발/Flutter
안녕하세요~ totally 개발자입니다.
REST API
Rest API는 개발을 하시는 분들이라면 반드시 알고 있어야 하는 개념 중 하나로서 Representational State Transfer의 약자입니다. 데이터를 이름으로 분류하여 데이터를 주고 받는 모든 것을 말합니다. HTTP URI를 통해 데이터 자원(resource)를 표시해주고 5가지 HTTP Method(방법)에 의해 해당 데이터를 주고 받을 수 있습니다. 흔히 관계형 데이터베이스의 CRUD(Create, Read, Update, Delete)를 이렇게 아래처럼 rest API method로 정리할 수 있습니다.
POST (CREATE) | Resource 추가(생성) |
GET (READ) | Resource 가져옴 |
PUT (UPDATE) | Resource의 전체 업데이트 |
PATCH (UPDATE) | Resource의 일부 업데이트 |
DELETE (DELETE) | Resource 삭제 |
이 실습에서는 제일 중요한 POST와 GET에 대해 살펴보도록 하겠습니다.
POST (데이터 생성)
Step 1: 다음처럼 파일들을 생성해줍니다.

Step 2: pubspec.yaml에 http 패키지를 등록해줍니다 (flutter pub get http로도 가능)

Step 3: Model를 위한 Album 클래스를 생성해서 아래처럼 작성합니다.

Step 4: RestApiService 클래스를 작성해줍니다. 여기에서 중요한 부분은 13번째 줄로 http.post를 사용하여 데이터를 생성하는 과정을 거칩니다. 그 후 24번째 줄을 통해 statusCode가 201일 때(created response) 해당 내용을 return할 수 있도록 합니다.

GET (데이터 가져오기)
Step 5: 이어서 데이터를 가져오기 위한 get 부분도 작성합니다. 이 부분은 이전 포스팅에서도 자주 했었던 부분이기에 익숙하실 것으로 생각되며 http.get과 headers를 통해 데이터를 어렵지 않게 가져올 수 있습니다. 40-42번째 부분을 통해 List 형태로 만들어 return 할 수 있습니다.

Step 6: main.dart의 _HomePageState 부분에 다음처럼 변수들을 선언해줍니다. 27번째의 RestApiService()는 싱글톤(Singleton)으로 단 하나의 인스턴스만 생성해서 관리합니다.

Step 7: 새 데이터를 입력 받을 수 있는 위젯을 만들어줍니다. 39~64번째 줄 (전체 소스 코드는 아래에 첨부하였으니 참고하시기 바랍니다) 여기에서 중요한 부분은 51번째 라인으로 _album = restApiService.postAlbum(_controller.text) 부분입니다. 그리고 55~61번째 라인을 통해서 기존 데이터 가져오기 버튼을 누르면 57-58번째 줄을 통해 데이터를 가져올 수 있도록 하였습니다.

Step 8: POST 부분입니다. 저는 ? : 을 활용하여 작성했습니다. Ternary Operator라는 뜻이며 이 링크를 통해 개념을 참고하시기 바랍니다. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
FutureBuilder 부분을 통해 snapshot으로 데이터를 받아와 보여줄 수 있도록 합니다.

Step 9: 마지막 get 부분입니다. 여기에도 동일하게 FutureBuilder로 사용하지만 87번째 타입으로 FutureBuilder<List<Album>>으로 바꿔주어야 하며 그 안에 ListView.builder도 추가된 것을 확인해볼 수 있습니다.

Step 10: 결과는 아래와 같습니다.

[전체 소스 코드]
album.dart
class Album { | |
final int id; | |
final String title; | |
const Album({required this.id, required this.title}); | |
factory Album.fromJson(Map<String, dynamic> json) { | |
return Album( | |
id: json['id'], | |
title: json['title'], | |
); | |
} | |
} |
restApiService.dart
import 'dart:convert'; | |
import 'package:http/http.dart' as http; | |
import 'album.dart'; | |
class RestApiService { | |
static final RestApiService _restApiService = RestApiService._internal(); | |
factory RestApiService() => _restApiService; | |
RestApiService._internal(); | |
Future<Album> postAlbum(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, | |
}), | |
); | |
// 서버가 201 CREATED response를 return했을 때 | |
if (response.statusCode == 201) { | |
return Album.fromJson(jsonDecode(response.body)); | |
} else { | |
throw Exception('Failed to post album.'); | |
} | |
} | |
Future<List<Album>> getAlbumList() async { | |
final response = await http.get( | |
Uri.parse('https://jsonplaceholder.typicode.com/albums'), | |
headers: <String, String>{ | |
'Content-Type': 'application/json', | |
'Accept': 'application/json', | |
}, | |
); | |
final List<Album> result = jsonDecode(response.body) | |
.map<Album>((json) => Album.fromJson(json)) | |
.toList(); | |
return result; | |
} | |
} |
main.dart
import 'package:flutter/material.dart'; | |
import 'package:project/album.dart'; | |
import 'package:project/restApiService.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp(title: 'Flutter App', home: HomePage()); | |
} | |
} | |
class HomePage extends StatefulWidget { | |
const HomePage({Key? key}) : super(key: key); | |
@override | |
State<HomePage> createState() => _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> { | |
final TextEditingController _controller = TextEditingController(); | |
final restApiService = RestApiService(); | |
Future<Album>? _album; | |
Future<List<Album>>? _albumList; | |
bool _getData = false; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('RestAPI GET, POST'), | |
), | |
body: _album == null && !_getData | |
? Container( | |
padding: const EdgeInsets.all(15), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
TextField( | |
controller: _controller, | |
decoration: const InputDecoration(hintText: '제목을 입력하세요'), | |
), | |
const SizedBox(height: 15), | |
ElevatedButton( | |
onPressed: () => setState(() { | |
_album = restApiService.postAlbum(_controller.text); | |
}), | |
child: const Text("새 데이터 생성하기 (POST)"), | |
), | |
ElevatedButton( | |
onPressed: () => setState(() { | |
_albumList = restApiService.getAlbumList(); | |
_getData = !_getData; | |
}), | |
child: const Text("기존 데이터 가져오기 (GET)"), | |
), | |
], | |
), | |
) | |
: _album != null && !_getData | |
? | |
// POST 부분 | |
Container( | |
padding: const EdgeInsets.all(15), | |
child: FutureBuilder<Album>( | |
future: _album, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return Text(snapshot.data!.title); | |
} else if (snapshot.hasError) { | |
return Text("${snapshot.error}"); | |
} else { | |
return const Center(child: CircularProgressIndicator()); | |
} | |
}, | |
), | |
) | |
: | |
// GET 부분 | |
Container( | |
padding: const EdgeInsets.all(15), | |
child: FutureBuilder<List<Album>>( | |
future: _albumList, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return ListView.builder( | |
itemCount: snapshot.data!.length, | |
itemBuilder: (context, index) { | |
return Container( | |
padding: const EdgeInsets.all(15), | |
child: Text( | |
"${snapshot.data![index].id}: ${snapshot.data![index].title}"), | |
); | |
}, | |
); | |
} else if (snapshot.hasError) { | |
return Text("${snapshot.error}"); | |
} else { | |
return const Center(child: CircularProgressIndicator()); | |
} | |
}, | |
), | |
), | |
); | |
} | |
} |
'모바일어플개발 > Flutter' 카테고리의 다른 글
[052] 플러터 (Flutter) 배우기 - showDatePicker 사용하여 날짜 선택하기 (0) | 2023.04.13 |
---|---|
[051] 플러터 (Flutter) 배우기 - sqflite 사용하여 단어장 만들기(로컬 데이터베이스) (4) | 2023.04.08 |
[049] 플러터 (Flutter) 배우기 - Singleton (싱글톤) 개념 이해하기 (0) | 2023.04.04 |
[048] 플러터 (Flutter) 배우기 - Factory Pattern (팩토리 패턴) 이해하기 (0) | 2023.04.03 |
[047] 플러터 (Flutter) 배우기 - Carousel Slider + Indicator (자동 슬라이더 + 인디케이터 구현) (2) | 2023.04.01 |