View

300x250

이번에는 URL에서 이미지를 받아와 파일의 형태로 다루면서 뷰에서 이미지를 보여주고자 하여, 아래의 작업을 처리해주고 있었다.

  1. 파이어베이스에서 이미지의 URL을 받아옴
  2. URL에서 이미지를 다운받아, 로컬에 임시 파일로 저장
  3. File 타입의 변수에 해당 파일을 불러오기
  4. Image 위젯에 이미지 파일을 보여주기

그리고, URL에서 이미지를 다운받아 저장하는 코드는 아래의 코드를 사용해주었다.

Future<File> downloadImageToFile(String imageUrl) async {
    try {
      // HTTP GET 요청을 통해 이미지 데이터 다운로드
      final response = await http.get(Uri.parse(imageUrl));
      if (response.statusCode == 200) {
        // 애플리케이션의 문서 디렉토리에 파일 저장 경로 생성
        final documentDirectory = await getApplicationDocumentsDirectory();
        final filePath =
            '${documentDirectory.path}/downloaded_image.jpg';

        // 파일로 이미지 데이터 저장
        final file = File(filePath);
        await file.writeAsBytes(response.bodyBytes);
        return file;
      } else {
        throw Exception('이미지 다운로드 실패: 상태 코드 ${response.statusCode}');
      }
    } catch (e) {
      throw Exception('이미지 다운로드 중 오류 발생: $e');
    }
  }

위 로직 자체는 잘 동작했지만, 파이어베이스의 이미지 URL을 변경한 뒤에 다시 한 번 위 로직으로 이미지를 새로 저장한 뒤 화면에 그려줘야 하는 경우에 계속 기존의 이미지가 그대로 그려지는 문제가 있었다.

Image.file(
  viewmodel.profileImageFile,
  fit: BoxFit.cover,
)

결론부터 말하자면 이미지 파일의 이름이 동일한 게 문제였다. (진 짜 알 수 없 다 플 러 터 🤔 🤔 🤔)

디버깅을 하기 위해서 내가 확인할 수 있는 거의 모든 곳에 파일의 URL과 다운받은 파일 데이터의 정보를 출력해봤는데, 파이어베이스의 URL, 디버거로 출력된 URL, 다운받은 이미지 파일 모두 변경된 이미지에 대한 데이터였는데, 정작 화면에서는 계속 변경 전의 이미지를 보여줘서 답답해 미칠 노릇이였다. ㅜㅜ

 

인터넷에서 여러 해결 방법을 서치해봤는데, 그럴 듯 했지만 나의 문제를 해결해주지는 못했었다.

 

1. setState 호출

 

setState를 호출하면 뷰를 새로 그려주게 되고, Image.File에 들어가있는 이미지 정보도 새로 로드를 해주기 때문에 이미지를 새로 잘 그려줄 것이라 생각했었다. 그런데, 전혀 놉. 심지어는 Hot Reload 를 해줬을 때에도 기존의 이미지를 계속 보여줬다.

 

 

ㅤ2. Image.file 에 UniqueKey를 주기

 

이게 정말 그럴 듯 했다. 이미지 위젯에게 매번 빌드를 새로 할 때 마다 Key 값으로 UniqueKey를 새로 제공을 하면, State 자체를 매번 새로 만들어줘야 하기 때문에 이미지 데이터를 잘 반영해줄 것이라고 생각했다. 그런데 여기에서도 놉. 이 시점에 원본 File 변수 자체에 문제가 있나 의심을 하기 시작했다. 그치만, 파일을 열어봤을 때에는 정상적으로 새로운 이미지가 잘 들어가있었다.

그래서 “Image 위젯에 이미지가 캐싱되어있어서 뭔가 문제가 발생하는게 아닐까?” 라는 생각이 들어서 이것저것 검색을 해봤는데, 기본적인 이미지 위젯에서는 캐싱된 데이터를 초기화할 수 있는 메서드를 따로 제공하고 있지 않았다. ㅠㅠ


그렇게 계속 코드와 구글을 두리번거리다가, ‘저장한 파일 이름이 계속 동일한 이름이라서 문제가 있는게 아닐까?’ 라는 생각이 들었다. 어차피 임시파일이니깐, URL에서 이미지 데이터를 다운받는 파일의 이름으로 동일한 이름을 계속 재활용하면서 덮어씌우기로 파일을 저장하고 있었다. 아래처럼.

final documentDirectory = await getApplicationDocumentsDirectory();
final filePath =
    '${documentDirectory.path}/downloaded_image.jpg';

그래서 여기 부분에 시간을 넣어서 유니크한 이름으로 만들어줬다.

final filePath =
    '${documentDirectory.path}/downloaded_image_${DateTime.now().toString()}.jpg';

그랬더니, 정상적으로 변경된 이미지 데이터가 화면에 잘 반영이 되기 시작했다.

요 문제에 대해서는 나는 이렇게 판단했다.

Image.file 위젯 자체도 최적화를 위해 이미지의 캐싱을 시도하고 있고, Image.file 위젯에서는 데이터의 캐싱을 위한 key 값으로 ‘이미지 데이터’ 자체가 아니라 ‘파일 이름’을 사용하고 있기 때문에, 파일 내용이 변경되더라도 파일명이 동일하면 같은 파일이라고 여겨 새로 데이터를 로드하지 않고 캐시된 이미지를 사용한게 아닐까 싶다.

임시파일의 이름을 하나만 두면 덮어씌우기로 파일을 관리하면서, 파일을 지우고 새로 만들 필요 없이 편하게 저장공간을 관리할 수 있을 것이라 생각했는데, 이게 나의 패인이였던 것 같다.

앞으로는 임시 파일을 저장한 뒤에 사용할 일이 있으면 고유한 파일 이름을 만들어서 사용해주고, 사용이 끝나면 그냥 지워주는 방식으로 관리하자.

혹시나 모르니 팩트체크를 위해 한 번 다큐먼트에 들어가봤다.

오호, 뭔가 캐싱에 대한 이야기를 여기서 던지고 있다. 말하는 바를 보아하니 ”데이터를 ImageCache 에서 관리를 하는데, 여기에서는 파일의 변경사항에 대해 자동으로 반영을 해주지 않으니, 데이터가 변경되면 앱 쪽에서 관리를 해줘야 한다”고 되어있다. 그리고 아래쪽에 see also 주석을 보면 FileImage 클래스에서 이 변경을 알리는 함수를 제공해준다고 되어있다.

그래서 해당 내용을 바탕으로 GPT와 구글을 서치해보고, 좀 더 유려한 코드를 작성하는 방법을 생각해봤다.

// 이렇게 데이터를 가지고 있다가
FileImage fileImage = FileImage(imageFile);

// Image.file(...)로 바로 파일에 접근하지 말고
// 이렇게 위젯에서 그려주기
Image(
  image: fileImage,
),

// 만약 파일에 변경이 생길 것 같으면 아래 함수 호출해서 캐시를 제거해주기
// 다시 fileImage를 사용하는 시점에 데이터를 로드하여 사용하게 된다.
fileImage.evict();

우선 Image.file 위젯으로 직접 파일에 접근하는 것이 아니라, FileImage 라는 클래스를 활용해서 중간 객체를 만들어준다. 그리고 일반적인 Image 위젯으로 해당 이미지를 화면에 보여준다. 만약 파일이 변경될 시점이라면 fileImage.evict 함수를 호출하여 캐시를 지워버리자. 그러면 fileImage를 사용해야 하는 시점에 다시 파일에서 이미지를 로드해 새로운 데이터를 보여줄 수 있게 된다.

어떻게 보면 파일 데이터 날 것 자체를 위젯에 끌어다 사용하려 하다가, 파일 데이터가 변경되더라도 파일 이름이 똑같아서 업데이트가 안됐던게 반은 맞았던 것 같다.

흠… 지식이 늘었다.

320x100
Share Link
reply
반응형
«   2024/12   »
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