플러팅 AI/React-native

React Native에서 AsyncStorage 문제 해결하기: 이미지 썸네일 로딩 실패 현상

Solo.dev 2024. 11. 24. 14:44

모바일 애플리케이션 개발 시, 사용자 데이터의 지속적인 저장과 효율적인 불러오기는 원활한 사용자 경험을 제공하는 데 중요한 역할을 합니다. React Native는 크로스 플랫폼 모바일 애플리케이션을 구축하기 위한 강력한 프레임워크로, 간단한 데이터 저장을 위해 AsyncStorage를 자주 사용합니다. 그러나 AsyncStorage를 사용할 때 예상치 못한 문제가 발생할 수 있으며, .

이번 포스트에서는 React Native 애플리케이션에서 이미지 썸네일을 클릭했을 때 저장된 이미지가 로드되지 않고 로딩 인디케이터가 계속 표시되는 문제를 해결한 과정을 공유하고자 합니다. 이를 통해 AsyncStorage의 데이터 저장 및 불러오기 로직을 어떻게 개선할 수 있는지 알아보겠습니다.

목차

  1. 문제점
  2. 문제 분석
  3. 해결 방안 구현
  4. 최종 결과
  5. 배운
  6. 결론

문제점

시나리오

React Native 애플리케이션에서 사용자는 이미지를 업로드하고, 이를 AsyncStorage에 저장하여 썸네일로 표시할 수 있습니다. 사용자가 썸네일을 클릭하면 저장된 이미지와 관련된 분석 결과를 상세 화면에서 확인할 수 있어야 합니다. 그러나 실제 사용 중에 다음과 같은 문제가 발생했습니다:

  • 썸네일 클릭 시: 저장된 이미지와 분석 결과가 표시되지 않고, 로딩 인디케이터가 계속 표시됨.
  • 저장된 데이터: AsyncStorage에 데이터가 제대로 저장되지 않거나, 저장된 데이터를 올바르게 불러오지 못함.
  • 로그 메시지: 이미지 URI가 savedResults에 존재하지 않는다는 경고 메시지가 반복적으로 출력됨.

초기 코드 스니펫

MainScreen.js: 이미지 선택 및 업로드 처리

const handleImageSelected = async (uri) => {
  const displayUri = uri;
  const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;

  try {
    // 1. SavedResultsScreen으로 네비게이션
    navigation.navigate('SavedResultsScreen', { image: displayUri });

    // 2. 서버에 이미지 업로드
    await uploadImageToFlaskServer(uploadUri);
    console.log('Image successfully uploaded to Flask.');

    // 3. AsyncStorage에 데이터 저장
    const newResult = { image: displayUri, response: '' };
    await saveCurrentResult(newResult);
    console.log('saveCurrentResult called with:', newResult);

  } catch (error) {
    console.error('Error during image selection or upload:', error);
    Alert.alert('업로드 실패', '이미지 업로드 중 문제가 발생했습니다.');
  }
};

SavedResultsScreen.js: 저장된 데이터 불러오기 및 표시

useEffect(() => {
  const fetchSavedResult = async () => {
    try {
      const savedData = await AsyncStorage.getItem('savedResults');
      const savedResults = savedData ? JSON.parse(savedData) : [];
      console.log("Fetched saved results:", savedResults);
      console.log("Current image URI:", image);

      const matchedResult = savedResults.find((item) => {
        console.log("Comparing:", item.image, " with ", image);
        return item.image === image;
      });
      console.log("Matched result:", matchedResult);

      if (matchedResult && matchedResult.response && matchedResult.response.trim() !== '') {
        setGptResponse(matchedResult.response);
        setLoading(false);
        console.log("Found existing response. Loading set to false.");
      } else {
        setGptResponse('');
        setLoading(true);
        console.log("No existing response. Loading remains true.");
      }
    } catch (error) {
      console.error('Error fetching saved result:', error);
      setGptResponse('');
      setLoading(false);
    }
  };

  setLoading(true);
  fetchSavedResult();
}, [image]);

문제 분석

문제를 해결하기 위해 다음과 같은 부분을 중점적으로 분석했습니다:

  1. 데이터 저장 로직: saveCurrentResult 함수가 데이터를 올바르게 저장하지 못하고 있는지 확인.
  2. 이미지 URI의 일관성: 저장 시 사용된 이미지 URI와 불러오기 시의 이미지 URI 형식이 일치하지 않는지 확인.
  3. 데이터 불러오기 로직: SavedResultsScreen에서 데이터를 올바르게 불러오고 있는지 확인.
  4. 로깅: 문제의 원인을 파악하기 위해 상세 로그를 추가하여 데이터 흐름을 추적.

주요 발견사항

  • 데이터 저장 실패: AsyncStorage에 savedResults가 빈 배열([])로 남아 있었음.
  • 이미지 URI 불일치: 저장된 이미지 URI와 불러오기 시의 이미지 URI 형식이 일치하지 않아 데이터 매칭이 실패함.
  • 네비게이션 타이밍: 데이터 저장 전에 네비게이션을 수행함으로써 SavedResultsScreen이 데이터를 불러올 때 아직 데이터가 저장되지 않은 상태였음.

해결 방안 구현

1. 데이터 저장 로직 강화

saveCurrentResult 함수가 데이터를 올바르게 저장하고 있는지 확인하기 위해 추가적인 로그를 삽입하고, 중복 확인 로직을 강화했습니다.

개선된 saveCurrentResult 함수:

const saveCurrentResult = async (newResult) => {
  try {
    const savedData = await AsyncStorage.getItem('savedResults');
    const savedResults = savedData ? JSON.parse(savedData) : [];
    console.log("Before saving - savedResults:", savedResults);

    // 중복된 이미지 확인
    const imageExists = savedResults.some(item => item.image === newResult.image);
    if (!imageExists) {
      const updatedResults = [...savedResults, newResult];
      await AsyncStorage.setItem('savedResults', JSON.stringify(updatedResults));
      console.log('Saved new result to AsyncStorage:', updatedResults);

      // 상태를 업데이트하여 화면을 갱신
      await loadSavedResults();
      setSavedResults(updatedResults);
    } else {
      console.log('Image already exists in savedResults.');
    }
  } catch (error) {
    console.error('Error saving new result:', error);
    Alert.alert('저장 실패', '데이터를 저장하는 중 오류가 발생했습니다.');
  }
};

설명:

  • 중복 확인: 동일한 이미지가 이미 저장되어 있는지 확인하여 중복 저장을 방지.
  • 추가 로그: 저장 전후의 상태를 로그로 확인하여 데이터 흐름을 명확히 파악.
  • 에러 핸들링: 저장 실패 시 사용자에게 알림을 제공하여 문제 인지 가능.

2. 이미지 URI의 일관성 유지

이미지 URI의 형식이 저장 시와 불러오기 시 일치하지 않으면 데이터 매칭이 제대로 이루어지지 않을 수 있습니다. 이를 해결하기 위해 이미지 URI를 일관되게 비교하도록 수정했습니다.

수정된 SavedResultsScreen.js:

useEffect(() => {
  const fetchSavedResult = async () => {
    try {
      const savedData = await AsyncStorage.getItem('savedResults');
      const savedResults = savedData ? JSON.parse(savedData) : [];
      console.log("Fetched saved results:", savedResults);
      console.log("Current image URI:", image);

      const matchedResult = savedResults.find((item) => {
        console.log("Comparing:", item.image, " with ", image);
        return item.image === image;
      });
      console.log("Matched result:", matchedResult);

      if (matchedResult && matchedResult.response && matchedResult.response.trim() !== '') {
        setGptResponse(matchedResult.response);
        setLoading(false);
        console.log("Found existing response. Loading set to false.");
      } else {
        setGptResponse('');
        setLoading(true);
        console.log("No existing response. Loading remains true.");
      }
    } catch (error) {
      console.error('Error fetching saved result:', error);
      setGptResponse('');
      setLoading(false);
    }
  };

  setLoading(true);
  fetchSavedResult();
}, [image]);

설명:

  • 일관된 URI 비교: 저장 시와 불러오기 시 동일한 형식으로 URI를 비교하여 정확한 매칭 보장.
  • 추가 로그: 비교 과정에서 각 URI를 로그로 남겨 문제의 원인을 쉽게 파악할 수 있도록 함.

3. WebSocket을 통한 데이터 업데이트 확인

WebSocket을 통해 수신된 데이터를 AsyncStorage에 올바르게 업데이트하는지 확인하기 위해 updateSavedResult 함수를 수정했습니다.

개선된 updateSavedResult 함수:

const updateSavedResult = async (imageUri, updatedResponse) => {
  try {
    const savedData = await AsyncStorage.getItem('savedResults');
    const savedResults = savedData ? JSON.parse(savedData) : [];
    console.log("Before updating - savedResults:", savedResults);

    const imageExists = savedResults.some(item => item.image === imageUri);
    let updatedResults;

    if (imageExists) {
      updatedResults = savedResults.map((item) => {
        if (item.image === imageUri) {
          return { ...item, response: updatedResponse };
        }
        return item;
      });
      console.log('Updated existing result:', updatedResults);
    } else {
      // 새로운 이미지 추가
      const newResult = { image: imageUri, response: updatedResponse };
      updatedResults = [...savedResults, newResult];
      console.log('Added new result:', newResult);
    }

    await AsyncStorage.setItem('savedResults', JSON.stringify(updatedResults));
    console.log('AsyncStorage updated:', updatedResults);
  } catch (error) {
    console.error('Error updating AsyncStorage:', error);
  }
};

설명:

  • 존재 여부 확인: 이미지가 이미 저장되어 있는지 확인하고, 존재하면 응답을 업데이트, 존재하지 않으면 새로운 항목을 추가.
  • 추가 로그: 업데이트 전후의 상태를 로그로 확인하여 데이터 흐름을 명확히 파악.
  • 에러 핸들링: 업데이트 실패 시 에러를 콘솔에 출력하여 문제를 인지할 수 있도록 함.

4. 데이터 저장 상태 확인을 위한 디버깅 도구 사용

문제 해결 과정에서 데이터가 올바르게 저장되고 불러와지는지 확인하기 위해 checkSavedResults 함수를 추가하여 AsyncStorage의 상태를 직접 확인할 수 있게 했습니다.

SavedResultsScreen.js에서 확인 버튼 추가:

const checkSavedResults = async () => {
  const savedData = await AsyncStorage.getItem('savedResults');
  console.log('SavedResults in AsyncStorage:', savedData);
};

설명:

  • 디버깅: 버튼 클릭 시 AsyncStorage에 저장된 데이터를 로그로 출력하여 데이터 저장 상태를 직접 확인.
  • 문제 파악: 저장된 데이터가 올바르게 저장되었는지, 데이터 구조가 예상과 일치하는지 확인할 수 있음.

최종 결과

위의 해결 방안을 적용한 후, 애플리케이션은 다음과 같은 개선을 보였습니다:

  1. 신뢰할 수 있는 데이터 저장:
    • 이미지는 성공적으로 AsyncStorage에 저장되었으며, 중복 저장이 방지됨.
    • 로그를 통해 데이터가 올바르게 저장되고 업데이트되는 것을 확인할 수 있었음.
  2. 정확한 데이터 불러오기:
    • 썸네일을 클릭 시 저장된 이미지와 분석 결과가 정확하게 표시됨.
    • 로딩 인디케이터가 정상적으로 사라지며, 데이터가 성공적으로 로드됨.
  3. 일관된 이미지 URI 처리:
    • 저장 시와 불러오기 시 동일한 방식으로 URI를 비교하여 데이터 매칭이 정확하게 이루어짐.
    • 불일치로 인한 데이터 매칭 실패 문제가 해결됨.
  4. 향상된 사용자 경험:
    • 사용자는 이제 이미지를 업로드하고, 썸네일을 클릭하여 상세한 분석 결과를 문제없이 확인할 수 있게 되었음.
    • 오류 발생 시 사용자에게 명확한 피드백을 제공하여 신뢰성을 높임.

시각적 확인:

Figure: 저장된 이미지와 분석 결과가 정상적으로 표시됨.


배운 점

이번 문제 해결 과정을 통해 다음과 같은 중요한 교훈을 얻었습니다:

  1. 데이터 저장 로직의 중요성:
    • 데이터 저장 전에 다른 작업을 수행하면 저장된 데이터에 접근할 때 문제가 발생할 수 있음.
    • 데이터를 저장하고 업데이트하는 로직을 꼼꼼히 확인하는 것이 중요함.
  2. 일관된 데이터 형식 유지:
    • 이미지 URI와 같은 식별자의 형식이 일관되지 않으면 데이터 매칭이 실패할 수 있음.
    • 데이터를 저장할 때와 불러올 때 동일한 형식으로 데이터를 처리하는 것이 중요함.
  3. 포괄적인 로깅의 필요성:
    • 상세한 로그를 통해 데이터 흐름을 추적하고 문제의 원인을 신속하게 파악할 수 있음.
    • 디버깅 과정에서 로그는 매우 유용한 도구임.

결론

React Native에서 AsyncStorage를 활용한 데이터 관리는 신중한 접근이 필요합니다.

데이터 저장과 불러오기 과정에서 발생할 수 있는 다양한 문제를 예방하고 해결하기 위해,

데이터 저장 로직을 강화하고, 데이터 형식을 일관되게 유지하며, 포괄적인 로깅과 에러 핸들링을 구현하는 것이 중요합니다. 이번 사례에서는 이러한 전략을 통해 AsyncStorage 관련 문제를 성공적으로 해결하여, 사용자에게 안정적이고 신뢰할 수 있는 기능을 제공할 수 있었습니다.

유사한 AsyncStorage 문제를 겪고 있다면, 이번 포스트에서 소개한 해결 방안을

참고하여 문제를 분석하고 해결해 보시기 바랍니다. 


행복한 코딩 되세요!

React Native나 AsyncStorage에 관한 추가 질문이나 팁이 있다면 아래 댓글로 공유해 주세요!