[Flutter] Retrofit 적용 (호출 방법 및 예시)
저번 달 글(클릭)에서는 Flutter에 Retrofit 적용 방법과 모델, RestClient 구현 방법을 알아보았다.
이번에는 실질적으로 데이터베이스에서 데이터를 가져와서 실행하는 예시를 통해서 호출 방법까지 알아보도록 하겠다.
비동기 처리 방식(Future / aysnc - await)
Flutter는 Dart 언어를 기반으로 이루어져 있다. Dart에서 데이터를 읽거나, 전송하는 네트워크 통신에서는 비동기 처리 방식이 사용된다.
여기서 동기(synchronous) / 비동기(asynchronous)에 대해 짧게 설명하자면
- 동기(synchronous : 코드가 순차적으로 실행되는 방식으로, 하나의 작업이 완료될 때까지 다음 작업은 대기한다.
- 비동기(asynchronous): 작업이 완료되기를 기다리지 않고, 다음 작업을 바로 실행하는 방식으로, 오래 걸리는 작업이 완료될 때까지 앱의 다른 부분이 실행될 수 있다.
Dart에서는 ‘Future’와 ‘async’ / ‘await’ 키워드를 통해 비동기 코드를 작성하는데 나도 이 부분을 이해하는데 많은 시간이 필요했다.
- Future: 나중에 완료될 수 있는 비동기 작업을 나타내며, Future 객체는 작업이 완료되었을 때 원하는 데이터가 담길 장소를 의미한다. Future는 다음 3가지 상태 중 하나를 반환하게 된다.
- 대기(Pending): 작업이 완료되지 않은 상태
- 완료(Completed): 작업이 완료된 상태
- 에러(Error): 작업이 실패한 상태
- async / await: async함수는 항상 Future를 반환하며, await는 Future가 완료될 때까지 기다린다. await 키워드는 async 함수 안에서만 사용이 가능하다.
- Future.delayed(Duration:시간) 함수를 사용하여 원하는 시간만큼 딜레이를 넣을 수 있다.
Future<User> getUser(int userId) async{
final User user= await _UserRestClient.getUser(userId);
return user;
}
Future<User> getUser(int userId) async{ //2초 딜레이 후에 User반환
final User user= await Future.delayed(Duration(seconds: 2)
,()=>_UserRestClient.getUser(userId));
return user;
}
다음과 같이 User를 호출하고 싶은 페이지에 호출 함수를 작성한다. Future<User>는 작업이 완료된 경우에만 데이터베이스에서 User를 가져와 반환하고, 실패한다면 에러 값을 반환할 것이다.
RestClient.g.dart
이전 글에서 UserRestClient.dart를 제작하고 ‘flutter pub run build_runner build’ 명령어를 터미널에 실행하면 UserRestClient.g.dart가 생성된다는 것까지 설명했다.
@RestApi(baseUrl:'서버 IP 주소')
abstract class UserRetrofitClient {
factory UserRetrofitClient(Dio dio, {String? baseUrl}) =
_UserRetrofitClient;
@GET('user/{userId}')
Future<User> getUser(@Path('userId')int userId);
}
UserRestClient.dart
class _UserRetrofitClient implements UserRetrofitClient {
_UserRetrofitClient(
this._dio, {
this.baseUrl,
}) {
baseUrl ??= '서버 ip 주소';
}
final Dio _dio;
String? baseUrl;
@override
Future<User> getUser(userId) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
final _result =
await _dio.fetch<Map<String, dynamic>>(_setStreamType<User>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'user/${userId}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
final value = User.fromJson(_result.data!);
return value;
}
~~ 코드 생략 ~~
}
UserRestClient.g.dart
다음과 같이 UserRestClient.g.dart 코드가 자동 생성될 것이다. 이 파일은 실제 HTTP 요청을 처리하는 구현체를 포함하며 어떤 메서드를 사용하여 요청을 보내고, 응답 데이터를 원하는 타입의 데이터로 반환하는 것을 확인할 수 있다.
FutureBuilder
모델, RestClient, Future 함수까지 모두 작성했다면 이제 호출한 데이터를 사용하는 일만 남았다.
Future는 비동기 작업이므로 작업이 완료할 때까지 UI에 표시할 수 없다. 따라서 Flutter에는 그에 맞는 위젯인 FutureBuilder가 있다.
- 기본 구조
FutureBuilder<User>(
future: getUser(userId), // Future 객체
builder: (BuildContext context, AsyncSnapshot snapshot) {
// snapshot은 현재 Future의 상태를 포함한다.
},
)
AsyncSnapshot의 connectionState 객체를 통해 현재 Future의 상태를 나타낼 수 있고, 크게 3가지 상태로 구분할 수 있다.
- ‘ConnectionState.waiting’: Future가 완료되지 않았을 때 상태로 주로 CircularProgressIndicator 위젯을 사용하여 로딩 화면을 표시한다.
- ‘snapshot.hasError’: Future가 에러를 반환했을 때 에러 메세지를 표시한다.
- ‘snapshot.hasData’: Future가 데이터를 성공적으로 반환했을 때 데이터를 표시한다.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FutureBuilder<User>(
future: getUser(userId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // 로딩 중일 때 표시할 위젯
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}"); // 에러 발생 시 표시할 위젯
} else if (snapshot.hasData) {
final User result = snapshot.data; // 데이터가 있을 때 표시할 위젯
String? name= result.userName;
return Text('$name');
} else {
return Text("No data"); // 데이터가 없을 때 표시할 위젯
}
},
),
),
),
);
}
Future<User> getUser(int userId) async{
final User user= await _UserRestClient.getUser(userId);
return user;
}
FutureBuilder 예시
모든 경우에 데이터를 반환할 수 있어야만 에러가 발생하지 않기 때문에 마지막에 else 문을 빼먹지 않도록 주의해야 한다. snapshot.hasData 상태로 성공적으로 반환하게 된다면, snapshot.data에 원하는 타입의 데이터가 반환될 것이다.
정리
이로써 Flutter에서 구현한 Retrofit를 활용하여 비동기 처리 방식으로 데이터를 호출하는 방법을 알아보았다.
처음에 비동기 방식에 익숙하지 않아 UI 위젯에 원하는 데이터를 출력하는 것이 쉽지 않았다. 다른 분들도 이 코드에서 List 형태의 데이터를 반환하는 등 응용 방법을 활용하여 앱을 만들 수 있으면 좋겠다.