[050] 플러터 (Flutter) 배우기 - Rest API 사용(GET, POST)

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'],
);
}
}
view raw 050_album.dart hosted with ❤ by GitHub

 

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());
}
},
),
),
);
}
}
view raw 050_main.dart hosted with ❤ by GitHub

 

 

 

 

반응형