오늘은 dio
패키지를 활용하여 오픈 API의 데이터를 받아오고, 이 데이터를 기반으로 검색 기능을 지원하는 MVVM 아키텍처 앱을 만들어보자.
Dio 라이브러리는 서버와 통신을 돕기 위한 여러 가지 기능을 내장하고 있는 패키지이다.
먼저 프로젝트에 Dio 패키지를 설치한다.
flutter pub add dio
다음으로 Dio 객체를 쓰는 레포지토리 클래스를 만들어보자.
data/repository/
에 location_repository.dart
라는 파일과 클래스를 생성한다.
이 Dio 객체를 통해 get, post 등 Dio에서 지원하는 내장 함수를 쓸 수 있다.
우리는 행정구역 이름을 입력해서 관련 시설을 검색하는 기능을 만들 예정이다. 따라서 findByLocation
이라는 이름으로 함수를 만들었다.
response 객체에 get() 함수에서 받아오는 데이터를 집어넣는다.
이 때 파라미터에 지난 게시글에서 설명했던 데이터들을 적절히 입력해주어야 한다. (쿼리, 키값, url 등...)
네이버 API는 header 값을 요구하는데, 헤더는 Options() 안에 넣으면 된다.
이 값을 넣지 않으면 요청이 가지 않으므로 주의하자.
이런 식으로 요구 형식이 각 API마다 다르므로 잘 확인해야 한다.
서버로부터 데이터를 받아오는 데 시간이 걸리므로 Future 타입으로 선언하고 async 태그를 붙인다.
await
get 함수를 실행할 때 앞에 await를 걸지 않으면 응답 성공 여부를 확인하는
response.statusCode
정보에 접근할 수 없다.
요청이 완료되었다는 확신이 있어야 해당 데이터를 사용할 수 있으므로 반드시await
를 붙여주자.
다음으로 응답 코드에 따라 분기를 나누어 요청한 데이터를 가공해보자.
서버와 통신할 때 통신 상태에 따라 다양한 코드를 수신할 수 있다고 지난 게시글에서 설명했는데, 우리가 받아야 하는 코드는 요청 성공을 의미하는 200
이다.
서버로부터 아무런 응답도 받지 못하는 경우가 발생할 수 있으므로 try-catch
문을 쓰자.
필요한 정보는 3가지이다.
이 데이터가 JSON에서 어디 있는지 알아야 가져올 수 있다.
thunder client를 사용하면 확인할 수 있다.
"items" 안에 "title", "category", "roadAddress"가 있다.
그러면 우리는 items를 리스트로 반환하고 그 안에서 title, category, roadAddress 데이터를 꺼내 쓴다고 생각하면 된다.
응답 코드가 200인 경우 받은 데이터를 list 형태로 가공하고, 그렇지 않은 경우 그냥 빈 리스트를 반환한다.
if문 안에 있는 문자열은 받아오는 JSON에 기재된 데이터 경로이다.
어떤 API를 쓰느냐에 따라 매번 달라지기 때문에 이 코드는 참고만 하고, 직접 thunder clien등을 이용하여 알아낸 경로를 입력해야 한다.
만약 아예 응답을 받지 못한 상황이라면 빈 리스트를 반환한다.
1) 'items' 데이터를 items
객체에 넣고, 2) iterable을 만들고, 이 중에서 3) 'title'에 해당하는 데이터만으로 리스트를 만들고 있다.
그런데 우리에게 필요한 데이터는 세 가지이다. (타이틀, 건물 분류, 도로명주소)
바로 떠오르는 해결법은 두 가지이다.
1번으로 진행하면 ListView.builder에서 어떻게 응용해야 할지 생각만 해도 머리가 아프기 때문에 모델을 만들어보자.
data/model/
에 location.dart
라는 이름으로 모델 파일을 만든다.
필요한 데이터를 선언하고
JSON 변환 함수를 만든다.
이제 repository 클래스에서 데이터 저장하는 부분을 수정해보자.
1) 먼저 Map<String, dynamic>을 선언해 JSON을 디코딩한다.
2) iterable 변수에 맵 -> fromJson->List 순으로 변환한 값을 저장한다.
3) iterable.tolist를 반환한다.
그리고 이제 String이 아니라 Location객체를 쓰기 있기 때문에 findByLocation
함수의 반환 타입을 Future<List<Location>>
으로 바꿔야 한다.
과연 데이터를 올바르게 받아왔는지 테스트를 한 번 돌려보자.
데이터를 받아와보고, 결과가 저장되는 리스트인 result가 비어있지 않다면 성공하는 테스트이다.
결과는 실패...
사실 모델 클래스 만들기 전에 단일 데이터만 받아오게 해서 테스트를 돌려 봤을 때는 멀쩡히 돌아갔던 걸 보면 서버로 요청 자체는 잘 가고, 데이터를 받아서 가공하는 단계에서 뭔가 문제가 있는 것 같다.
에러 메시지를 보면 Map<String, dynamic>이 String의 subtype이 아니라서 오류가 생겼다고 한다. 이러면 레포지토리는 두고 모델을 한번 테스트해보자.
임의의 데이터를 넣고 location.title이 "title"값을 가지는지 테스트해봤는데 잘 돌아가는 것으로 봐서 모델 클래스에는 문제가 없는 것 같다.
에러 메시지를 보니까 Map<String, dynamic> 을 선언할 때 뭔가 문제가 생기는 것 같아서 그 부분을 없애고 다시 시도해보았다.
Map 선언 부분을 아예 없애고 jsonDecode를 뺐더니 됐다...
이유는 잘 모르겠다. jsonDecode를 안해도 되는 건지...
해결할 점 : jsonDecode는 필수가 아닌가?
이제 데이터를 연결했으니 검색 화면에 출력해보자.
우선 뷰모델이 필요하다.
하지만 지금은 검색 결과로 나올 List 하나에만 상태 관리를 해주면 되기 때문에 상태 클래스를 복잡하게 구성할 필요가 없다.
1번을 건너뛰고 상태클래스 이름 대신 해당 리스트를 넣어서 구현해보자.
우선 riverpod을 설치한다.
flutter pub add flutter_riverpod
다음으로 뷰모델과 Provider를 만든다.
상태 클래스 이름이 들어가야 하는 자리에 List가 들어갔다.
이렇게 ListView를 Consumer로 감싸고, 그 안에서 locations라는 배열을 만들어 해당 배열을 ListView에 인자로 전달한다. 그러면 ListView는 매번 동적으로 전달되는 locations 배열을 요소로 삼는다.
다음 게시글에서는 검색 바와 리스트뷰를 연동하고 코드를 간결하게 정리한 뒤 현재 위치 기반으로 검색하는 기능을 만들어보자.