이미지 태거에서는 처음 앱을 시작했을 때 다음의 과정을 거친다.
1. 앱의 접근 권한을 확인한다.
2. 접근 권한이 있다면, 기기의 캡쳐사진을 전부 불러온다.
3. 해당 사진들을 서버로 보낸다.
4. 서버에 전송을 성공하면 home화면으로 이동한다.
위 4가지의 단계가 전부 home으로 이동하기 전 app.js에서 이루어지는데,
구현에만 집중하다 보니 코드가 아주 길고, 동작 과정이 나도 헷갈리게 되어버렸다.
코드를 좀 더 읽기 쉽게 리팩토링 해보려 한다.
오늘 할 것은 간단하게
1. 복잡한 callback 함수를 async await를 사용해서 바꾼다.
2. 구지 해당 코드에서 로직을 볼 필요가 없는 부분, 아래의 경우에는 getFCMToken()을 custom hook으로 만들어서 숨긴다.
나중에 더 좋은 코드를 알게 되면 다시 한번 리팩토링 해야겠다.
일단 현재의 코드는 아래와 같다.
아래는 app.js에서 기능 부분의 코드이다.
현재 대략 135줄 정도이다.
// fcm token 가져와서 storage에 저장
const getFCMToken = async () => {
let fcmToken = await AsyncStorage.getItem('fcmToken');
console.log('old fcmToken: ', fcmToken);
if (!fcmToken) {
try {
const fcmToken = await messaging().getToken();
if (fcmToken) {
console.log('new fcmToken: ', fcmToken);
await AsyncStorage.setItem('fcmToken', fcmToken);
}
} catch (err) {
console.log(err, 'fcmtoken에서 error 발생');
}
}
return fcmToken;
};
const getStoragePermission = () => {
// 앨범 접근 권한 요청
if (Platform.OS === 'android') {
check(
Platform.Version >= 33
? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
)
.then(result => {
console.log(result);
switch (result) {
case RESULTS.UNAVAILABLE:
Confirm(
'알림',
'해당 기기는 앨범에 접근할 수 있는 기기가 아닙니다.',
);
console.log('앨범 접근 권한 : unavailable');
break;
case RESULTS.GRANTED:
console.log('앨범 접근 권한 : granted');
readImages();
break;
case RESULTS.DENIED:
request(
Platform.Version >= 33
? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
)
.then(res => {
console.log('앨범 접근 권한 허용 : ', res);
readImages();
})
.catch(() => {
Confirm(
'알림',
'앨범 접근 권한 허용 중 에러가 발생했습니다. 앱 설정 화면에서 권한을 허용해 주세요.',
);
});
break;
case RESULTS.BLOCKED:
console.log('앨범 접근 권한 : blocked');
Alert.alert(
'',
'이미지태거에서 기기의 사진에 액세스할 수 있도록 앱 설정 화면에서 권한을 허용해주세요.',
[
{
text: '거부',
onPress: () => {
console.log('앨범 접근 권한 허용 거부됨');
},
},
{
text: '허용',
onPress: () => Linking.openSettings(),
},
],
);
break;
}
})
.catch(err => {
console.log(err);
});
}
};
const readImages = () => {
// 캡쳐사진 읽기
RNFS.readDir(RNFS.ExternalStorageDirectoryPath + '/DCIM/Screenshots')
.then(result => {
sendImages(result);
return Promise.all([RNFS.stat(result), result]);
})
.catch(err => {
console.log(
'MY LOGGG FAILED GET SCREENSHOT IMAGE : ',
err.message,
err.code,
);
});
};
// 캡쳐사진 서버에 전송
const sendImages = async images => {
const formData = new FormData();
console.log('MY LOGGG GET IMAGES FOR SEND : ', images);
try {
images?.map(image => {
formData.append('images', {
uri: 'file://' + image.path,
name: image.name,
type: 'image/jpeg',
});
});
} catch (err) {
console.log('MY LOGGG FORM DATA ERROR : ', err);
}
const fcmToken = await getFCMToken();
const config = {
headers: {
'Content-Type': 'multipart/form-data',
userDeviceToken: fcmToken,
},
};
setDataLoadState(2);
console.log('MY LOGGG : /images SEND IMAGES START');
customAxios
.post('/images', formData, config)
.then(data => {
console.log('MY LOGGG : /images SEND IMAGES SUCCESS : ', data);
setDataLoadState(3);
})
.catch(err => {
console.log('MY LOGGG : /images SEND IMAGES FAIL : ', err, err.config);
setDataLoadState(4);
});
};
코드 리팩토링
1. 중복되는 코드를 하나의 상수로 묶었다.
const permissionVerVersion =
Platform.Version >= 33
? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
2. 길고 복잡한 requestPermission()을 기능 별로 함수 여러 개로 나누고, callback함수를 async await 문법으로 바꾸었다.
const permissionVerVersion =
Platform.Version >= 33
? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const getStoragePermission = async () => {
// 앨범 접근 권한 요청
try {
if (Platform.OS === 'android') {
const result = await check(permissionVerVersion);
handlePermission(result);
}
} catch (err) {
console.log(err);
}
};
const handlePermission = result => {
switch (result) {
case RESULTS.GRANTED:
readImages();
break;
case RESULTS.UNAVAILABLE:
Confirm('알림', '해당 기기는 앨범에 접근할 수 있는 기기가 아닙니다.');
break;
case RESULTS.DENIED:
requestPermission();
break;
case RESULTS.BLOCKED:
Alert.alert(
'',
'이미지태거에서 기기의 사진에 액세스할 수 있도록 앱 설정 화면에서 권한을 허용해주세요.',
[
{
text: '거부',
onPress: () => console.log('앨범 접근 권한 허용 거부됨'),
},
{
text: '허용',
onPress: () => Linking.openSettings(),
},
],
);
break;
}
};
const requestPermission = async () => {
try {
await request(permissionVerVersion);
readImages();
} catch {
Confirm(
'알림',
'앨범 접근 권한 허용 중 에러가 발생했습니다. 앱 설정 화면에서 권한을 허용해 주세요.',
);
}
};
3. 불필요한 코드를 지우고 마찬가지로 async await 문법을 사용하였다.
다만 axios 함수에 대해서는 다른 모든 axios 함수들과 함께 정리할 예정이기 때문에 일단 그대로 두었다.
const readImages = async() => {
// 캡쳐사진 읽기
try {
const result = await RNFS.readDir(
RNFS.ExternalStorageDirectoryPath + '/DCIM/Screenshots',
);
sendImages(result);
} catch (err) {
console.log(
'MY LOGGG FAILED GET SCREENSHOT IMAGE : ',
err.message,
err.code,
);
}
};
// 캡쳐사진 서버에 전송
const sendImages = async images => {
const formData = new FormData();
console.log('MY LOGGG GET IMAGES FOR SEND : ', images);
images?.map(image => {
formData.append('images', {
uri: 'file://' + image.path,
name: image.name,
type: 'image/jpeg',
});
});
const config = {
headers: {
'Content-Type': 'multipart/form-data',
userDeviceToken: fcmToken,
},
};
setDataLoadState(2);
console.log('MY LOGGG : /images SEND IMAGES START');
customAxios
.post('/images', formData, config)
.then(data => {
console.log('MY LOGGG : /images SEND IMAGES SUCCESS : ', data);
setDataLoadState(3);
})
.catch(err => {
console.log('MY LOGGG : /images SEND IMAGES FAIL : ', err, err.config);
setDataLoadState(4);
});
};
4. 마지막 return 부분이 여러개로 나뉘어 있어서 명확하지 않았다. 이 부분을 컴포넌트로 묶어서 return 하는 식으로 바꾸었다.
const dataLoadMessage = {
1: '사진을 불러오는 중입니다...',
2: '태그를 생성하는 중입니다...',
3: '태그 생성이 완료되었습니다.',
4: '이미지 태깅에 실패했습니다.',
};
if (dataLoadState == 1 || dataLoadState == 2)
return <LoadingBar title={dataLoadMessage[dataLoadState]} />;
else if (dataLoadState == 4) {
return (
<ReloadBar
title={dataLoadMessage[dataLoadState]}
onPress={() => {
readImages();
setDataLoadState(1);
}}
/>
);
}
return (
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
);
이렇게 해서 전체 코드는 아래와 같다.
코드 길이는 105줄 정도로 이 전에 비해 30줄 정도가 줄었고, 코드 가독성은 훨씬 좋아졌다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import {NavigationContainer} from '@react-navigation/native';
import React, {useState} from 'react';
import {Alert, Platform} from 'react-native';
import {check, PERMISSIONS, request, RESULTS} from 'react-native-permissions';
import {StackNavigator} from './src/navigator/StackNavigator';
import customAxios from './src/api/axios';
import LoadingBar from './src/components/bar/LoadingBar';
import ReloadBar from './src/components/bar/ReloadBar';
import {useFCMToken} from './src/hook/useFCMToken';
function App() {
const RNFS = require('react-native-fs');
const fcmToken = useFCMToken();
const [dataLoadState, setDataLoadState] = useState(1);
React.useEffect(() => {
getStoragePermission();
}, []);
const permissionVerVersion =
Platform.Version >= 33
? PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const getStoragePermission = async () => {
// 앨범 접근 권한 요청
try {
if (Platform.OS === 'android') {
const result = await check(permissionVerVersion);
handlePermission(result);
}
} catch (err) {
console.log(err);
}
};
const handlePermission = result => {
switch (result) {
case RESULTS.GRANTED:
readImages();
break;
case RESULTS.UNAVAILABLE:
Confirm('알림', '해당 기기는 앨범에 접근할 수 있는 기기가 아닙니다.');
break;
case RESULTS.DENIED:
requestPermission();
break;
case RESULTS.BLOCKED:
Alert.alert(
'',
'이미지태거에서 기기의 사진에 액세스할 수 있도록 앱 설정 화면에서 권한을 허용해주세요.',
[
{
text: '거부',
onPress: () => console.log('앨범 접근 권한 허용 거부됨'),
},
{
text: '허용',
onPress: () => Linking.openSettings(),
},
],
);
break;
}
};
const requestPermission = async () => {
try {
await request(permissionVerVersion);
readImages();
} catch {
Confirm(
'알림',
'앨범 접근 권한 허용 중 에러가 발생했습니다. 앱 설정 화면에서 권한을 허용해 주세요.',
);
}
};
const readImages = async() => {
// 캡쳐사진 읽기
try {
const result = await RNFS.readDir(
RNFS.ExternalStorageDirectoryPath + '/DCIM/Screenshots',
);
sendImages(result);
} catch (err) {
console.log(
'MY LOGGG FAILED GET SCREENSHOT IMAGE : ',
err.message,
err.code,
);
}
};
// 캡쳐사진 서버에 전송
const sendImages = async images => {
const formData = new FormData();
console.log('MY LOGGG GET IMAGES FOR SEND : ', images);
images?.map(image => {
formData.append('images', {
uri: 'file://' + image.path,
name: image.name,
type: 'image/jpeg',
});
});
const config = {
headers: {
'Content-Type': 'multipart/form-data',
userDeviceToken: fcmToken,
},
};
setDataLoadState(2);
console.log('MY LOGGG : /images SEND IMAGES START');
customAxios
.post('/images', formData, config)
.then(data => {
console.log('MY LOGGG : /images SEND IMAGES SUCCESS : ', data);
setDataLoadState(3);
})
.catch(err => {
console.log('MY LOGGG : /images SEND IMAGES FAIL : ', err, err.config);
setDataLoadState(4);
});
};
const dataLoadMessage = {
1: '사진을 불러오는 중입니다...',
2: '태그를 생성하는 중입니다...',
3: '태그 생성이 완료되었습니다.',
4: '이미지 태깅에 실패했습니다.',
};
if (dataLoadState == 1 || dataLoadState == 2)
return <LoadingBar title={dataLoadMessage[dataLoadState]} />;
else if (dataLoadState == 4) {
return (
<ReloadBar
title={dataLoadMessage[dataLoadState]}
onPress={() => {
readImages();
setDataLoadState(1);
}}
/>
);
}
return (
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
);
}
export default App;
리팩토링도 생각보다 시간이 많이 걸리는구나..
중간 중간 내코드이지만 내가 헷갈리는 부분이 많았다.
다음부터는 코드를 쓸 때 부터 구조를 명확히 하는게 좋을 것 같다.
'코딩일지' 카테고리의 다른 글
[2023-06-06] 이미지태거 오류 수정 (0) | 2023.06.06 |
---|---|
[2023-06-06] 이미지태거 리팩토링 - 크기가 큰 배열 나누어서 전송하기 (0) | 2023.06.06 |
[2023-06-05] 보낸 이미지 저장해서 같은 이미지 전송되지 않도록 하기 (0) | 2023.06.05 |
[2023-06-01] 블록체인 해커톤 위한 near protocal 세팅 (window 환경) (0) | 2023.06.01 |
[2023-05-28] 이미지 태거 디버깅 (0) | 2023.05.28 |