① 배운 것
///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 속성은 신중히 써야함
Error: Bad state: The FormData has already been finalized.
This typically means you are using the same FormData in repeated requests.
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) 등등..
역시 로긴,토큰 과 관련된 코드는 함부로 건드는것이 아니다 ㅎㅎ..
그래도 츄라이츄라이 했기 때문에 (중간에 버그 잡는데 힘들었지만) 결국 깔끔한 코드를 가지게 되서 기분이 좋다.
물론 로긴 관련해서 아직 리팩토링하고 싶은 코드가 많아서 계속 개선해 나갈 예정!
③ 개선을 위한 방법