https://pub.dev/packages/dio 로 가서 라이브러리 검색
dependencies:
dio: ^5.1.1 // 추가
https://jsonplaceholder.typicode.com/posts/1 을 요청하면 아래의 json을 받게 된다.
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
이것을 이용해서 test코드를 작성해보자
먼저 post_repository작성
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class PostRepository {
final dio = Dio();
Future<void> findById(int id) async {
Response responseFT = await dio.get("https://jsonplaceholder.typicode.com/posts/$id");
print(responseFT.data);
}
Future<void> findAll() async {
Response responseFT = await dio.get("https://jsonplaceholder.typicode.com/posts");
print(responseFT.data);
}
}
테스트 코드 작성
void main() async {
await findById_test();
// await findAll_test();
}
Future<void> findById_test() async{
int id = 1;
PostRepository postRepository = PostRepository();
await postRepository.findById(id);
}
Future<void> findAll_test() async{
PostRepository postRepository = PostRepository();
await postRepository.findAll();
}
결과
{userId: 1, id: 1, title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit, body: quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto}
dio 라이브러리는 json 데이터를 받아서 String 형태의 Map으로 변환해준다.
Map에 넣어서 원하는 결과를 얻을 수 있다.
Future<void> findById(int id) async {
Response responseFT = await dio.get("https://jsonplaceholder.typicode.com/posts/$id");
Map<String, dynamic> responseMap = responseFT.data;
print(responseMap["userId"]);
}
// 테스트 결과 1
https://app.quicktype.io/ 로 가서 RestApi문서가 반환할 데이터를 입력한다.
예를들어 아래의 json
https://jsonplaceholder.typicode.com/posts/1
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
원하는 결과를 선택하면 클래스로 변환해준다.
import 'dart:convert';
Post postFromJson(String str) => Post.fromJson(json.decode(str));
String postToJson(Post data) => json.encode(data.toJson());
class Post {
Post({
required this.userId,
required this.id,
required this.title,
required this.body,
});
int userId;
int id;
String title;
String body;
factory Post.fromJson(Map<String, dynamic> json) => Post(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"],
);
Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"body": body,
};
}
json.decode(str)
는 json문자열을 Map형태의 타입으로 반환해준다.
Post.fromJson
생성자는 Map형태의 데이터를 받아서 factory
객체를 반환한다.
factory
는 이미 객체가 존재한다면 캐싱해서 재사용을하고 객체가 존재하지 않는다면 생성한 객체를 반환한다. ( 싱글톤 처럼 )
toJson()
은 Post객체의 데이터를 Map컬렉션으로 변환해주고
반환된 Map컬렉션은 json.encode()
를 이용해서 Json문자열로 파싱된다.
데이터를 변환해줄 레파지토리를 만든다.
class PostRepository {
final dio = Dio();
Future<Post> findById(int id) async {
Response responseFT = await dio.get("https://jsonplaceholder.typicode.com/posts/$id");
Map<String, dynamic> responseMap = responseFT.data;
Post post = Post.fromJson(responseMap); // Map으로 매핑된 데이터를 Post 오브젝트로 변환
return post;
}
Future<List<Post>> findAll() async {
Response responseFT = await dio.get("https://jsonplaceholder.typicode.com/posts");
List<dynamic> responseBody = responseFT.data; // -> List<Map<String, dynamic>>
List<Post> postList = responseBody.map((e) => Post.fromJson(e)).toList(); // map을 오브젝트로 변환
return postList;
}
}
dio 라이브러리는 Json을 Map으로 변환해준다.
Map을 Dart오브젝트로 변환하는 코드를 이용해서 Dart오브젝트를 만든다.
Riverpod
을 이용해서 Dart오브젝트의 데이터를 구독중인 위젯을 다시 build한다.
이번에는 Riverpod
의 FutureBuilder
로 상태를 관리해보자
class HomePage2 extends StatelessWidget {
const HomePage2({Key? key}) : super(key: key);
Widget build(BuildContext context) {
// print("나도 실행"); // 다시 빌드 되는지 확인
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder(
future: PostRepository().findById(1),
builder: (context, snapshot) {
if (snapshot.hasData) {
print("데이터 있음");
Post post = snapshot.data!;
return Center(child: Text("${post.title}", style: TextStyle(
fontSize: 30,
),));
} else {
print("데이터 없음");
return CircularProgressIndicator();
}
},
),
),
Expanded(
child: FutureBuilder(
future: PostRepository().findAll(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Post> postList = snapshot.data!;
return ListView.separated(
itemCount: postList.length,
separatorBuilder: (context, index) {
return Divider(
color: Colors.grey,
height: 1,
thickness: 1,
);
},
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.ac_unit_outlined),
title: Text("${postList[index].title}"),
subtitle: Text("${postList[index].body}"),
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
)
],
),
);
}
}
프레임이 낮아서 이상하게 보이지만 잠깐의 로딩후 데이터를 따로 그린다.
FutureBuilder
의 future
가 awaid/async
를 이용해서 비동기로 데이터를 다운받고 완료시 콜백을 날린다.
받은데이터가 없으므로 snapshot
이 존재하지 않아 위젯을 그리지 않는다.
콜백을 날리기 전에는 print("데이터 없음");
가 호출되고 로딩이미지가 화면에 잠시 나온다.
두개의 FutureBuilder
가 따로 실행되면서 잠시동안 로딩이미지가 나왔다가 future
가 완료되면 await
부분부터 다시 실행된다.
이후 builder
가 다시 호출되고 snapshot
을 다시 체크하는데 데이터를 다운받았으므로 print("데이터 있음");
이후의 위젯을 그리게 된다.
위 코드에서 PostRepository()
가 두번 생성되므로 좋지 않다.
싱글톤을 이용해서 하나의 객체를 재사용한다.
class PostRepository {
final dio = Dio();
static PostRepository _instance = PostRepository._single(); // _ -> private
PostRepository._single();
factory PostRepository(){
return _instance;
}
// 이후 생략
factory
를 이용해서 객체가 이미 메모리가 존재하면 다시 생성하지 않고 객체를 재사용한다.