[Flutter] dio 라이브러리로 데이터 받기

merci·2023년 4월 3일
0

Flutter

목록 보기
14/24
post-custom-banner

DIO 라이브러리

https://pub.dev/packages/dio 로 가서 라이브러리 검색

의존성추가

dependencies:
  dio: ^5.1.1 // 추가

FakeApi 이용해서 연습하기

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

json <-> 오브젝트 변환하기

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문자열로 파싱된다.

PostRepository 만들기

데이터를 변환해줄 레파지토리를 만든다.

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한다.

FutureBuilder로 상태관리

이번에는 RiverpodFutureBuilder로 상태를 관리해보자

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();
                }
              },
            ),
          )
        ],
      ),
    );
  }
}

프레임이 낮아서 이상하게 보이지만 잠깐의 로딩후 데이터를 따로 그린다.

FutureBuilderfutureawaid/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를 이용해서 객체가 이미 메모리가 존재하면 다시 생성하지 않고 객체를 재사용한다.

profile
작은것부터
post-custom-banner

0개의 댓글