글 작성자: 개발자 올라프

기업 협업 내용

 

  • 식물관리 앱 서비스를 하고 있는 스타트업에서 인턴쉽(`22.06.20 ~ `22.07.15) 진행
  • Front-end 4명 참가 (각자 다른 UI 및 기능 구현)
  • React Native를 활용한 앱 개발
  • Sourcetree를 활용한 Git-flow 경험
  • AI 식물 진단을 위한 카메라 UI 및 기능 구현

 

식물 진단 카메라 시연

 

 

 

식물 진단 카메라 소개

 

  • 사진을 바탕으로 식물을 분석해주는 'AI 식물 진단' API를 사용하기에 앞서 사용자가 사용할 카메라 개발
  • 'react-native-vision-camera' 라이브러리를 활용한 카메라 구현

 

 

상단 내비게이션

  • 뒤로 가기 ❌ 버튼은 만들어져 있는 custom-hook 사용
  • 플래시 버튼과 화면 전환 버튼은 useState를 활용하여 현재 상태를 지정했다.
    현재 상태를 바꾸는 함수인 changeFlash, changeSelfie를 만들어서 해당 플래시 버튼, 화면전환 버튼인 <Pressable>에 적용
const [flash, setFlash] = useState('off');
const [selfie, setSelfie] = useState('back');

const devices = useCameraDevices('wide-angle-camera');
const device = devices[selfie];

const changeFlash = () => {
	flash === 'off' ? setFlash('on') : setFlash('off');
};

const changeSelfie = () => {
	selfie === 'back' ? setSelfie('front') : setSelfie('back');
};

<Camera
	ref={camera}
    style={styles.cameraView}
    device={device}
    isActive={true}
    photo={true}
/>

 

진행 바

  • 'react-native-progress' 라이브러리를 활용하여 사용자에게 진행 상황을 알림
  • 사용자는 사진 두 장을 촬영해야 하며 현재 촬영된 사진 개수(length)에 따라서 진행 바가 변함
import * as Progress from 'react-native-progress';

<View style={styles.progressView}>
    {images.length === 0 ? (
      <Progress.Bar progress={0} width={350} color={Color.white} />
    ) : images.length === 1 ? (
      <Progress.Bar progress={0.5} width={350} color={Color.white} />
    ) : (
      <Progress.Bar progress={1} width={350} color={Color.white} />
    )}
</View>

 

사진 추가 및 앨범 기능

  • 사용자 기기 앨범에서 사진 가져오기 및 사진 촬영 버튼 기능 구현
  • 앨범 가져오기 기능은 'react-native-image-crop-picker' 라이브러리 활용
const [images, setImages] = useState([]);

const nextId = useRef(1);

const getAlbum = () => {
    ImagePicker.openPicker({
      width: 300,
      height: 400,
      cropping: true,
    }).then(pickedImage => {
      const image = {
        id: nextId.current,
        path: pickedImage.path,
      };
      setImages(images.concat(image));
      nextId.current += 1;
    });
};

const takePhoto = async () => {
    const result = await camera.current.takePhoto({
      flash: flash,
    });

    if (result) {
      const image = {
        id: nextId.current,
        path: result.path,
      };
      setImages(images.concat(image));
      nextId.current += 1;
    }
};

 

추가된 사진 제거

  • 사진을 촬영하거나 앨범에서 사진을 가져오면 사진이 아래 두 네모칸에 저장되는데 ❌ 버튼을 눌러서 삭제가 가능
  • 사진 찍기 이전에 사용자에게 빈 네모칸을 보여주기 위해서 아래와 같이 <View> 컴포넌트를 두 개로 나눠야 했음
  • 빈 네모칸이 아니라면 map을 활용하여 코드를 줄일 수 있지만, 빈 네모칸을 보이기 위해서 똑같은 코드를 두 번 입력했기 때문에 아쉬운 부분
const removePhoto = id => {
	setImages(images.filter(image => image.id !== id));
};

<View style={styles.contentImageBox}>
    {images[0] && (
      <>
        <Image style={styles.imageShot} source={{ uri: `file://${images[0].path}` }} />
        <Pressable style={styles.closeBtnWrapper} onPress={() => removePhoto(images[0].id)}>
          <ImageWrapper style={styles.closeBtn} source={require('@/사진경로/close.png')} />
        </Pressable>
      </>
    )}
</View>

<View style={styles.contentImageBox}>
    {images[1] && (
      <>
        <Image style={styles.imageShot} source={{ uri: `file://${images[1].path}` }} />
        <Pressable style={styles.closeBtnWrapper} onPress={() => removePhoto(images[1].id)}>
          <ImageWrapper style={styles.closeBtn} source={require('@/사진경로/close.png')} />
        </Pressable>
      </>
    )}
</View>

 

프로젝트 후기

 

 

웹만 다루다가 앱 개발을 기업 협업에서 처음 다루게 되었는데 Xcode, Android Studio, 시뮬레이터 등 모바일 환경에 적응하는데 상당한 시간을 빼앗겼다. 간단한 이론부터 공부해야 했기에 본격적으로 개발에 뛰어든 시간은 그렇게 길지 못했던 것 같다. 특히 협업을 진행하면서 웹 개발에 집중해야 될 시기에 앱 개발을 하고 있다는 점이 나에게 불리하게 작용하는 것은 아닐까 수 없이 고민했다. 하지만 프론트엔드로서 앱 개발 경험도 중요할 것이라는 위코드 매니저님의 말을 듣고, 협업 기간 동안 열심히 해보자고 마음을 잡게 되었다. 덕분에 스타트업에서 UI 및 기능을 직접 고민하면서 처음부터 끝까지 만들어본 경험을 할 수 있었다.

 

그리고 협업 중 개발자로서 첫 기술면접을 진행했다. 면접을 해보는 게 좋지 않겠냐는 말에 대뜸 신청을 하게 되었고, 준비가 되지 않은 상태에서 다음날 바로 면접을 진행하게 되었다. 진행하는 동안 제대로 된 대답을 하나도 할 수 없었기에 엄청 민망했지만, 내가 어떠한 점을 놓치고 있었는지, 부족했는지 알 수 있는 계기가 되었다. 좋은 소식을 듣지는 못했지만 면접 질문 하나하나를 다시 떠올리면서 정리하는 시간을 가졌다.