241210 TIL

나고수·2024년 12월 10일

2024 TIL

목록 보기
87/94
post-thumbnail

① 배운 것

안드로이드 인앱결제 테스트는 내부테스트에서 가능

  • firebase app distribution에서는 불가능
  • ios는 테스트플라이트가 아니여도 firebase app distribution에서도 가능함

AsyncNotifier KeepAlive 시 주의점

  • A 페이지에서 유일하게 B AsyncNotifier을 listen한다고 가정
    (keppAlive가 아니면 A페이지를 나가면 B Provider는 dispose됨)
  • B provider 최초 build() 했는데 AsyncError 상태가 되었을때(AsyncData가 존재하지 않을때) -> A 페이지를 나갔다 들어와도 keepAlive상태 이기 때문에 B provider는 새로 호출되지 않고 여전히 AsyncError상태임
  • 그래서 error일때 api요청 하는 함수를 다시 호출해야함
  • 그렇지 않으면 B provider는 한번 error상태가 되면 다시 호출 될 수 있는 방법이 없음
  • keepAlive 속성을 주지 않으면 A 페이지를 들어갈때마다 B provider가 새로 build()되기 때문에 새로 api가 호출되어서 상관없음
///WidgetsBinding.instance.addPostFrameCallback안에서 api를 호출하지 않으면 위젯을 그리는 동안에 state가 변경되어서 버그가 남 
  error: (_, __) {
   WidgetsBinding.instance.addPostFrameCallback((_) {
     ref
       .read(BProvider().notifier)
       .getSomeData();
   });

   return progressIndicator;
 },
(keepAlive: true)
class B extends _$B {
  
  Future<someResponse> build() {
    return fetchSomeData();
  }

  Future<someResponse> fetchSomeData() async {
    final someRepository = ref.read(someRepositoryProvider);
    return await someRepository.getSomeData();
  }

  Future<void> getSomeData() async {
    state =
      const AsyncLoading();
    state = await AsyncValue.guard(() async {
      return fetchSomeData();
    });
  }

}

👉 AsyncNotifier에서 KeepAlive 속성은 신중히 써야함

dio http request 시 form-data 재사용 안됨 이슈

  • 401에러가 떨어지면 토큰을 리프래시하고 새로 받은 토큰으로 header의 authorization을 갈아끼워서 이전 api를 재요청한다.
    근데 이때 유독 사진 보내는 곳에서만 재요청 시 문제가 발생함
  • 이유는 dio의 form-data 재사용 안됨 이슈 때문이였음
  • 👇 에러 메시지
Error: Bad state: The FormData has already been finalized. 
This typically means you are using the same FormData in repeated requests.
  • 그래서 이전 api를 재요청할때는 이전 요청의 formdata를 복사해 새로운 formdata를 만들고 새로운 formdata를 새 요청의 option으로 갈아끼워서 요청해야한다.
if (options.data is FormData) {
  FormData originalFormData = options.data;
  FormData newFormData = FormData();
  newFormData.fields.addAll(originalFormData.fields);

  for (final file in originalFormData.files) {
    newFormData.files.add(MapEntry(file.key, file.value.clone()));
  }
  options.data = newFormData;
}

//이전 요청 재요청 
final response = await dio.fetch(options);

👇 코드 설명

FormData는 HTTP 요청에서 파일을 포함한 멀티파트 데이터를 전송할 때 사용하는 형식입니다. 
FormData는 크게 두 부분으로 구성됩니다:

fields: 일반적인 키-값 쌍의 데이터
// 예시
formData.fields = [
  MapEntry("userName", "John"),
  MapEntry("age", "25")
]

files: 파일 데이터
// 예시
formData.files = [
  MapEntry("profileImage", MultipartFile(...)),
  MapEntry("document", MultipartFile(...))
]


성공한 코드를 라인별로 설명하면:
// FormData 타입인 경우에만 처리
if (options.data is FormData) {
  // 원본 FormData 가져오기
  FormData originalFormData = options.data;
  // 새로운 빈 FormData 생성
  FormData newFormData = FormData();
  
  // 모든 일반 필드값들을 새 FormData에 복사
  newFormData.fields.addAll(originalFormData.fields);

  // 각 파일에 대해
  for (final file in originalFormData.files) {
    // clone()을 사용해 파일 데이터를 깊은 복사
    // clone()은 MultipartFile의 모든 속성(파일 내용, 이름, 타입 등)을 복사
    newFormData.files.add(MapEntry(file.key, file.value.clone()));
  }
  
  // 새로 만든 FormData로 교체
  options.data = newFormData;
}

이것때문에 정말! 삽질을 많이했다.
토큰 재발급받아서 재요청하는 부분을 try로 잡고 catch에서 DioException(e)를 받았는데,
처음에는 http 통신 문제인줄알고 e.statuscode랑 e.message print했는데 둘다 null로 나오길래 뭐지뭐지 하다가 헉 그러면 http통신 자체의 문제가 아니라 그 전 단계에서 뭔가 에러가 났구나 하는 생각에 e.error을 프린트 해보니까 위의 저 에러로그가 나와서 겨우 실마리를 찾아서 해결 할 수 있었다.

e.statuscdoe랑 e.message가 null이면 http 통신 과정의 문제가 아니라 그 전단계에서 뭔가 문제가 났다는걸 빨리 알아차렸어야하는데 그 부분이 좀 오래걸렸다. ㅠ 그래도 알아차린게 어디냐믄서..

참고로 e.message와 e.error의 차이는 다음과 같다고 한다.
그렇다면 위의에러가 e.message에는 안나오고 e.error에만 나온 이유가 납득이감 (네트워크 요청의 문제가 아니라 그 전의 근본 원인이여서..?)

② 회고 (restropective)
이번에 토큰관련 & 로그인, 로그아웃 관련해서 코드 수정을 했는데, 잡기 어려운 에러가 꽤 났다. formdata 재활용 불가 이슈를 포함하여, logout때 어떤 provider를 invalidate하고 있었는데 그게 불필요하다고 생각되어 그 부분을 없앴더니, 버그가남. 알고보니 그 provider를 watch하고 있는 다른 provider가 Invalidate되는 과정이 필요한거였음.! 근데 수정한 코드로는 그 로직을 작성하기가 어려워서 로직은 다르지만 결과는 같은 코드작성(로그인 시 실제 invalidate되어야 하는 provider를 다시 init) 등등..

역시 로긴,토큰 과 관련된 코드는 함부로 건드는것이 아니다 ㅎㅎ..
그래도 츄라이츄라이 했기 때문에 (중간에 버그 잡는데 힘들었지만) 결국 깔끔한 코드를 가지게 되서 기분이 좋다.
물론 로긴 관련해서 아직 리팩토링하고 싶은 코드가 많아서 계속 개선해 나갈 예정!

③ 개선을 위한 방법

profile
되고싶다

0개의 댓글