말랑한 하루

[Flutter] (Proejct) MapleApp: 8. dio, API 요청 본문

개발/Flutter

[Flutter] (Proejct) MapleApp: 8. dio, API 요청

지수는말랑이 2024. 1. 5. 18:57
반응형

dio는 http보다 api 통신을 위해 다양하고 강력한 기능을 제공합니다. dio를 사용하면서 어떻게 하면 프로젝트에 더 간편하게 사용할 수 있을지 고민했습니다. 이번 칼럼은 그 고민에 대한 내용들입니다.

🐇 Dio Instance

기본적으로 SingleTon Project에 해당된다 생각하여, Dio의 Instance를 생성해주었습니다.

class DioInstance {
  final dio = Dio();

  DioInstance() {
    dio.options.baseUrl = dotenv.get('NEXON_API_URL');
    dio.options.headers = {
      'accept': 'application/json',
      'x-nxopen-api-key': dotenv.get('NEXON_API_KEY'),
    };
  }

  void setQueryParameters(queryParameter) {
    dio.options.queryParameters = queryParameter;
  }
}

dio class가 생성될 때, dio의 주요 속성에 대한 값을 지정해주었습니다. 이 프로젝트에선 API 통신에 있어 get 요청만 진행하므로 인스턴스화 할 수 있었습니다.

 

또한, Dio에서 사용되는 주요 속성은 다음과 같습니다.

🍒 path

url 경로를 의미합니다. 이는 options의 baseUrl 후미에 붙습니다.

🍒 data

post 요청을 할 때 사용되는 개체입니다.

🍒 options

options는 HTTP 요청 정보 및 구성을 담고 있습니다. 자체적으로 생성된 모든 요청에 대한 기본 구성이 있으며, 단일 요청을 생성할 때 기본 구성을 재 정의할 수 있습니다. options에 포함된 주요 속성은 다음과 같습니다.

🍇 headers

🍇 baseUrl

🍇 sendTimeout/receiveTimeout

 

🍒 queryParamters

get 요청을 할 때 필요한 query 매개변수를 설정할 수 있습니다.

🍒 onSendProgress/onReceiveProgress

🐇 Dio request API

이제 직접 Notifier에서 API 통신을 진행할 것입니다. 그 전에 우리는 Notifier가 어느 시점에서 Build되는지, ref.watch()를 통해 얼마나 빈번하게 접근하는지 알아야 합니다.

 

그래서 Notifier 및 ref.watch를 호출하는 시점 중간 중간 로그를 찍어 놓고 직접 라이프 사이클을 확인해 보려 합니다. Flutter의 LifeCycle은 이번 API통신을 정확히 진행했을 때, 추가적으로 정리하려 합니다.

 

먼저 모든 Notifier의 build는 Application이 실행됐을 때, 함께 실행됩니다. 만약 캐릭터 이름 정보를 가지고 있다면, Application이 실행됐을 때 함께 가져오면 좋겠지만 우리는 캐릭터 이름 정보가 없기 때문에 Notifer가 초기 build될 때 우리는 API통신을 진행하면 안됩니다.

 

하지만 API 통신을 하기 위해서 AsyncNotifier를 사용해야 했고, Future Type의 return을 보내야 했기 때문에 build 시 Future<>를 반환하는 _fetch 함수를 실행하지만, 캐릭터 정보가 없는 경우 빈 객체를 반환하도록 구성하였습니다. 그 내용에 대해 간략하게 보여드리면 다음과 같습니다.

Future<MainCharacter> _fetchCharacter() async {
    if (ref.watch(characterNameProvider.notifier).state == "") {
      return MainCharacter(~);
    }

    final characterName = ref.watch(characterNameProvider.notifier).state;

    final json = await dio.get(~);
		...

    return MainCharacter(~);
  }

  @override
  FutureOr<MainCharacter> build() {
		ref.watch(characterNameProvider);
    return _fetchCharacter();
  }

그리고 원하는 characterName이 입력되어서 Provider에 update되었을 때, Notifier의 build의 ref.watch가 이를 캐치하고 rebuild 되게 끔 만들어 줍니다.

 

🐇 Dio response API

request가 성공적으로 이루어 졌다면 우리는 response를 관리해야 합니다. 현재 칼럼에서는 request/response 중간의 Error를 핸들링 하고 있지 않고 있어, 추후 발전이 필요합니다.

 

여기서 한 가지 문제점이 발생했었습니다. request 요청을 정확히 했음에도 불구하고 결과 값이 반환 되지 않고 있었습니다. 이때, 서버에는 문제가 없었습니다.

 

그래서 다시 한번 Dio 공식 문서를 찾아갔습니다. 그리고 놓쳤었던 LogInterceptor 개체를 찾을 수 있었습니다.

 

🥕 LogInterceptor

LogInterceptor는 로그 요청 및 응답에 대해 자동으로 적용할 수 있습니다. 그리고 항상 마지막에 인터셉터가 추가되어야 합니다. 만약, 이를 어긴다면 다음 인터셉터에 의한 수정 사항이 기록되지 않습니다.

 

logPrint 기능을 활용하여 log를 출력할 수 있지만, Flutter에서는 Flutter의 차제 debugPrint 기능을 사용해야 합니다. 이렇게 하면 디버그 메세지를 flutter logs를 통해서도 사용할 수 있습니다. 이는 다음과 같이 사용할 수 있습니다.

dio.interceptors.add(
	LogInterceptor(
		logPrint: (o) => debugPrint(o.toString()),
	),
)

그리고 이 추가 사항을 Dio의 Instance를 초기화 할 때 적용하여 활용했습니다.

 

log를 통해서 response.statusCode가 200임을 확인할 수 있었습니다. 문제는 response.data가 Map<String, dynamic>형태로 반환 되는데, 기존의 json객체를 Decode하기 위한 코드에 response.data를 적용시켜 해당 함수에서 오류가 났습니다.

 

그래서 저는 이 모든 과정에 try/catch 구문을 적용하고자 노력했으나, 아직 try/catch 구문의 훌륭함을 깨닫지 못했고 Flutter에서는 AsyncValue를 적극 권장하고 있기 때문에 추후 에러 핸들링에 대해선 다시금 작성하겠습니다.

 

하지만 정리할 내용은 해야 하기 때문에, Dio 공식 문서의 하위에 다가가면 오류 처리에 대한 항목이 있습니다. DioException을 활용하여 Error를 확인할 수 있습니다.

 

🥕 DioException

DioException은 다음과 같은 항목을 지니고 있습니다.

RequestOptions requestOptions;
Response? response;
DioExceptionType type;
Object? error;
StackTrace? stackTrace;
String? message;

이 내용을 활용하여 try/catch 구문은 다음과 같이 작성할 수 있습니다. ~은 사용자 임의의 추가 코드입니다.

try {
	await dio.get('<http://api.pub.dev/not-exist>');
} on DioException catch (e) {
	if (e.response != null) {
		~ e.response.data ~
		~ e.response.headers ~
		~ e.response.requestOptions ~
	} else {
		~ e.requestOptions ~
		~ e.message ~
	}
}
반응형
Comments