일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 토플
- 인프런
- rxswift
- 프로그래머스
- Python3
- 인프런오리지널
- 파이썬중급강의
- 우리를위한프로그래밍
- 파이썬웹크롤링
- 파이썬중급
- nodeJS
- 파이썬
- JS
- swift
- IOS
- 인프런강의
- 스위프트
- 리프2기
- 인프런파이썬
- 자바스크립트
- 교환학생토플
- 토플공부수기
- SwiftUI
- uikit
- 노드JS
- 웹크롤링
- 카카오톡채팅봇
- IOS프로그래밍
- 유학토플
- 인프런파이썬강의
- Today
- Total
먹고 기도하고 코딩하라
[Flutter] API 통신 중 RangeError: invalid value only valid value is 0: 4 에러 해결하기 본문
[Flutter] API 통신 중 RangeError: invalid value only valid value is 0: 4 에러 해결하기
사과먹는사람 2022. 9. 24. 13:072줄 요약:
이 글은 API로 데이터를 받아와서 리스트뷰로 띄우는 중에 생긴 에러를 다루고 있음.
데이터를 json 디코딩해서 모델로 변경하지 않고 바로 쓰고 있다면, 실제로는 값이 없는 프로퍼티를 참조했을 때 이런 에러가 날 수 있음.
본문
원래 swift로 개발하던 앱이 있었는데 언니가 안드로이드로도 내라고 해서 플러터로 급 개발 중이다.
검색 기능을 구현하던 중 문제가 생겼다.
RangeError (end): Invalid value: Only valid value is 0: 4
검색해보니 0: 1 이런 건 있어도 0: 4 이런 에러는 없었다.
왜 이런 에러가 나는지 알 수가 없었음.
이쯤에서 API에 데이터 요청을 하고 받아와서 위젯에 그리는 코드를 한 번 살펴보자.
// 데이터 요청
getJSON(String query) async {
if (page == 1) {
setState(() {
_loading = true;
movieList.clear(); // 클래스에 List movieList; 로 선언되어 있다
});
}
var url = "https://api.themoviedb.org/3/search/movie?api_key=${FlutterConfig
.get('TMDB_KEY')}&language=ko-KR&query=${query}&page=${page}";
var response = await http.get(Uri.parse(url));
setState(() {
List result = json.decode(response.body)['results'];
if (result.isEmpty) {
if (page == 1) {
_noData = true;
} else {
_endofData = true;
}
} else {
_noData = false;
_endofData = false;
movieList.addAll(result);
}
});
if (page == 1) {
setState(() => _loading = false);
}
}
// ListBuilder 부분
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return GestureDetector(
child: Container(
color: Colors.white,
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
movieList[index]['release_date'] == null
? Text(
"연도 미상",
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
) : Text(
movieList[index]['release_date']
.toString()
.substring(0, 4)
.trim(),
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
),
SizedBox(height: 5),
Text(
"\u{2B50} ${movieList[index]['vote_average']}"
.toString()
.trim(),
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
)
],
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
)
]
)
),
);
},
itemCount: movieList.length,
controller: _scrollController
)
핵심만 빠르게 보기 위해서 코드를 조금 줄였다.
왜 이런 에러가 나는지 알 수 없었다. 어떤 건 데이터가 조금이어도 잘 표시가 됐지만, 어떤 건 데이터가 많아도 이런 에러가 나기도 했기 때문이다.
결론적으로, API에서 받아온 데이터를 따로 가공하지 않고 그대로 쓴 게 문제였다.
로그를 찍어보니, 'release_date'가 비어있으면 아예 index에 잡히지 않았다.
그런데 공교롭게도 그게 4번째 데이터여서 0: 4 문제가 생기는 거였다.
다른 검색어로도 시도했는데 0: 4가 나오길래 테스트해보니, 그 검색어 역시 4번째 데이터의 release_date가 비어 있었다.
그러니 나오지 않을 수밖에...
해결법
이를 해결하기 위해 데이터 모델 클래스를 만들어준다.
일단은 이 정도만 필요하기 때문에 간단하게만 만들었다.
class MovieModel {
int? totalResults;
int? totalPages;
List<Movie>? results;
MovieModel({this.totalResults, this.totalPages, this.results});
fromJson(Map<String, dynamic> json) {
totalResults = json['total_results'];
totalPages = json['total_pages'];
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results!.add(new Movie.fromJson(v));
});
}
}
}
class Movie {
int? id;
String? posterPath;
String? title;
String? releaseDate;
num? voteAverage;
Movie({
this.id,
this.posterPath,
this.title,
this.releaseDate,
this.voteAverage
});
}
그런 다음 getJson 함수를 조금 바꿔준다. 똑같은 부분은 생략한다.
먼저 MovieModel 객체를 만들어준 다음에 fromJson 메소드 매개변수로 json을 넣어준다. 그럼 객체의 results에는 Movie들의 정보가 저장된다.
그 리스트가 비었는지 확인해서, 비지 않았다면 movieList에 하나씩 더해준다.
여기서 원래 List movieList로 선언되어 있던 걸 List<Movie> movieList로 타입을 확실히 정해준다.
getJSON(String query) async {
// code
setState(() {
MovieModel model = MovieModel();
model.fromJson(json.decode(response.body));
if (model.results?.isEmpty ?? true) {
// code
} else {
_noData = false;
_endofData = false;
model.results?.forEach((movie) => movieList.add(movie));
}
});
// code
}
이제 위젯에서 string interpolation을 할 때 다음과 같이 바꿔주면 된다.
movieList[index].releaseDate?.isEmpty ?? true // 여기
? Text(
"연도 미상",
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
) : Text(
movieList[index].releaseDate // 여기
.toString()
.substring(0, 4)
.trim(),
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
),
SizedBox(height: 5),
Text(
"\u{2B50} ${movieList[index].voteAverage}" // 여기
.toString()
.trim(),
style: TextStyle(color: Colors.black38,
fontWeight: FontWeight.w500),
)
API에서 받은 데이터를 그대로 쓰는 데에는 위험이 도사리고 있다.
웬만하면 모델 클래스를 만들어 모델로 변환하고, 데이터가 없을 때는 어떻게 할지 에러 처리를 하는 것이 좋다.
'앱 > Flutter' 카테고리의 다른 글
Dart에서 객체 다루기 - Map (0) | 2021.10.06 |
---|---|
Dart에서 리스트 다루기 (0) | 2021.10.04 |
생존을 위한 Dart 문법 톺아보기 (0) | 2021.10.02 |