[Project] RETRIP 개발 회고

yooni·2022년 6월 5일
0
post-thumbnail

BEB 과정의 마지막 프로젝트. 가장 오랜 시간과 애정❣️을 담은 프로젝트였다. 많은 것을 배우기도 했고..
프로젝트 끝나고 일주일 정도 푹 쉬었는데, 기억이 더 날아가기 전에 어서 회고를 작성해보려고 한다.




✏️ 역할 분담

  • Front-end, Design/UX (내가 담당한 🙋🏻‍♀️)
    클라이언트 웹사이트
  • Back-end
    서버, DB
  • Smart Contract
    solana, Rust




1. RETRIP application

📺 시연 영상 링크

유저의 이동량을 추적하여 검증하고 보상으로 RETRIP 자체 발행 토큰인 RTRP를 지급하는 M2E(Move To Earn) 어플리케이션이다. 엄밀히는 걷는 이동에만 보상이 지급된다. 유저의 GPS를 추적하여 이동하는 속도를 기준으로 판단하는데, 평균 보행 속도보다 너무 느리거나 빠른 구간에서는 보상이 지급되지 않는다.



1-1. 주요 기능 & 아이디어


💡 Bucket & RTRP

  • 가입한 유저의 회원 정보에는 Bucket 구매 여부가 포함된다.
  • Bucket을 구매하지 않은 회원은 Move mode에서 보상을 받거나 Photo NFT를 발행할 수 없다.
  • 사용자가 Bucket을 구매하면 SOL 토큰 외 RETRIP 발행 토큰인 RTRP 토큰을 지갑에 저장할 수 있으며, Move mode에서 RTRP 토큰 보상을 받을 수 있게 된다.

💡 Wallet Logic

Sign up (Connect Wallet)

  • 회원 가입은 내 기기에 솔라나 지갑을 연결하는 것을 의미한다. 이미 가지고 있는 니모닉 코드를 입력하거나, 새로운 니모닉 코드를 생성하여 지갑을 연결할 수 있다.
  • 로그인 시 사용할 6자리 비밀번호를 입력받는다.
  • 니모닉 코드를 엔트로피화 한다.
  • 사용자가 입력한 6자리 비밀번호를 이용하여 엔트로피를 AES 암호화한다.
  • 암호화된 엔트로피는 기기의 secure store에 저장된다.

Login

  • 사용자 기기에 AES 암호화되어 저장되어 있는 엔트로피를 복호화하기 위해 6자리 비밀번호를 입력받는다.
  • 맞는 비밀번호가 입력된다면 엔트로피를 복호화할 수 있다.
  • 복호화된 엔트로피를 다시 니모닉 코드화 한다.
  • 니모닉 코드를 이용해 KeyPair(public/private)를 생성한다.
  • 이 키페어는 사용자가 앱에 로그인한 동안 서버에 리퀘스트를 보낼 때마다 사용된다. 키페어를 통해 인증 토큰을 매번 새롭게 생성하여 모든 리퀘스트에 포함하여 보낸다.

💡 Move mode와 보상

🌏 H3
지구 상의 모든 면적을 일정한 크기의 육각형으로 구분지어 구역을 나누는 오픈 소스 API이다.
우리 프로젝트에서는 한 변의 길이가 약 9.5m인 육각형을 단위로 사용하였고, 각 육각형을 하나의 '허니콘'으로 부르기로 했다.

Map에서 Start 버튼을 누르면, Move mode로 진입한다. 하나의 허니콘에서 유효한 걷기 이동이 발생하면 1개의 RTRP 토큰을 보상받을 수 있다. 이 때 이동하는 평균 속도로 유효성을 검사하는데, 너무 느리거나 빠른 이동은 걷기 이동으로 볼 수 없어 보상을 지급하지 않는다.


💡 Photo NFT

Photo NFT는 하나의 게시글, Feed로 생각할 수 있다. 모든 게시글은 블록체인 상에 NFT로 발행된다.

Bucket을 구매한 사용자는 Map에서 카메라 버튼을 눌러 사진을 찍고 해당 사진을 NFT로 발행할 수 있다. 이 때 이전에 찍은 사진을 가져올 수 없고 실시간으로 찍은 사진만 민팅이 가능하다. 현재의 순간을 추억으로 기록한다는 취지를 살리기 위함이다.

사진을 찍으면 자동으로 사진을 찍은 위치 정보와 그 때의 날씨 정보도 자동으로 업로드 된다. 모든 NFT들은 Map에 핀처럼 해당 위치에 꼽히게 된다. 다른 유저들은 자유롭게 서로의 NFT를 확인할 수 있다. 어떠한 NFT가 가치가 있다고 판단될 경우 유저끼리 사고 팔 수 있다. (아직 사고 파는 기능은 구현되어 있지 않다.)



1-2. Pages 상세

📃 Connect Wallet & Make Password

기존 솔라나 지갑의 니모닉 코드를 입력하여 연결하거나 새로운 니모닉 코드를 발급하고,
로그인시 사용할 6자리 비밀번호를 지정한다.


📃 Log in

지갑 연결시 등록한 6자리 비밀번호만으로 앱을 열 수 있다.


📃 Bucket 구매

RTRP 보상을 받기 위해서는 걷기 보상 단위인 '허니콘'을 담기 위한 버킷을 구매해야 한다.
버킷 가격은 0.1 Sol 이다.


📃 Map

Map : Move mode, Explore mode
지도 상에 보이는 NFT들의 상세 정보도 확인할 수 있다.


📃 Minting Photo NFT

Bucket을 구매한 유저는 Map에서 Photo NFT minting이 가능하다.


📃 Feeds & Feed Detail

다른 유저들이 발행한 NFT들을 피드 형식으로 볼 수 있으며,
각 NFT의 상세 정보도 확인할 수 있다.


📃 My Page

  • My NFTs - 내가 발행한/소유한 Photo NFT 목록 확인
  • My Wallet - SOL/RTRP 잔액 확인
  • My Txs - 보상 지급, NFT 민팅 등 내가 참여한 거래 내역 확인




2. Frontend 개발 회고

(내가 담당한 부분들에 대해서만 기술함)

⌨️ Stacks

  • React-native
  • Expo
  • redux tool-kit
  • React Navigation
  • Styled-components
  • GraphQL, Apollo
  • Figma
  • iOS/Android Simulator

🖥 Wire Frame 제작 - Figma

프로젝트1,2에서도 Figma를 사용하긴 했는데 이번에 앱을 개발하면서 좀 더 제대로(?) 사용해보게 되었다. 스마트폰 앱의 경우 화면이 작기 때문에 한 화면에 담을 수 있는 정보량이 많지 않아 최대한 각 페이지를 간결하게 제작하는 것이 중요했다.

또한 페이지 구성, 각 페이지 간의 연결과 흐름이 유저 입장에서 자연스러운지 UX까지 고려해야 했기 때문에 단순히 디자인을 그려보는 것 이상으로 신경쓸게 많았던 것 같다.

아직 피그마로 각 요소들을 냅다 그리고만 있는데, 좀 더 익숙해지면 적절하게 그룹화를 해가면서 체계적으로 사용해볼 수 있을 것 같다.



🖥 react-native, expo

react-native app은 이번 프로젝트를 통해 처음 개발해보게 되었는데, 처음치고 매우 빡시게(?) 경험할 수 있었다. 기본적으로 react와 거의 흡사해서 적응이 어렵지는 않았는데, CSS가 내 마음처럼 구현되지 않는 경우가 많아 고생한 것 같다.

있을 것 같은데? 싶은 라이브러리들은 대부분 이미 존재해서 가져다 쓰기만 하면 됐기 때문에 개발이 크게 어렵지는 않았다. 가끔 docs 설명이 미흡한 라이브러리들이 있어 헤매기는 했지만 냅다 하나하나 처음부터 개발하는 것보다 확실히 개발이 간단하고 시간도 적게 들었다.

또한 react-native의 복잡한 빌드 과정 등을 대신해주고 앱 개발을 훨씬 빠르고 편하게 할 수 있게 해주는 expo를 사용했다. 이는 우리 앱을 실제 서비스로 배포할 필요가 없었기 때문에 가능했다. 실 서비스용으로 개발하여 앱스토어나 플레이스토어에 올릴 예정이라면 바닐라 react-native-cli로 개발해야 한다. 또한 편리한 만큼 native 차원의 개발이 어려운 제약사항이 많다.

📎 dependencies

"dependencies": {
    "@expo-google-fonts/montserrat": "^0.2.2",
    "@expo-google-fonts/slackey": "^0.2.2",
    "@expo/vector-icons": "^13.0.0",
    "@expo/webpack-config": "~0.16.2",
    "@react-native-community/slider": "^4.2.2",
    "@react-native-picker/picker": "^2.4.1",
    "@react-navigation/native": "^6.0.10",
    "@react-navigation/native-stack": "^6.6.2",
    "expo": "~45.0.0",
    "expo-app-loading": "^2.0.0",
    "expo-asset": "^8.5.0",
    "expo-camera": "~12.2.0",
    "expo-font": "^10.1.0",
    "expo-location": "~14.2.2",
    "expo-media-library": "~14.1.0",
    "expo-random": "^12.2.0",
    "expo-secure-store": "~11.2.0",
    "expo-splash-screen": "~0.15.1",
    "expo-status-bar": "~1.3.0",
    "expo-system-ui": "^1.2.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-native": "0.68.2",
    "react-native-confirmation-code-input": "^1.0.4",
    "react-native-crypto-js": "^1.0.0",
    "react-native-gesture-handler": "^2.4.2",
    "react-native-get-random-values": "^1.8.0",
    "react-native-keycode": "^1.1.2",
    "react-native-maps": "^0.31.1",
    "react-native-mime-types": "^2.3.0",
    "react-native-modal": "^13.0.1",
    "react-native-pager-view": "^5.4.15",
    "react-native-stopwatch-timer": "^0.0.21",
    "react-native-tab-view": "^3.1.1",
    "react-native-url-polyfill": "^1.3.0",
    "react-native-web": "0.17.7",
    "react-redux": "^8.0.2",
    "styled-components": "^5.3.5",
  },
  },

이번 프로젝트를 진행하면서 react-native/expo 관련하여 설치한 라이브러리만 추려보아도 이정도이다. 이만큼 미리 만들어진 라이브러리들이 많기 때문에 그 중 적절한 것을 잘 골라서(npm에서 주간 다운로드수가 많은 라이브러리 위주로 선택한다) 사용하면 생각보다 더 쉽게 앱 개발을 할 수 있다.



🖥 React Navigation

React Navigation은 native 전용은 아니지만, native에서 페이지간 이동을 구현하기 위해서는 필수적으로 사용해야 한다. 개인적으로 docs가 정말 잘 정리되어 있다고 생각한다.

일반적으로 tab이나 stack을 가장 많이 사용하는데 이번 프로젝트에는 하단 Nav Bar가 필요가 없었기 때문에 stack navigation 만으로 충분히 페이지간 이동을 구현할 수 있었다.

📎 Stack.js

Stack.js에 네비게이션을 아래와 같이 정의해준다. 스택에 쌓아서 내가 왔다갔다 할 페이지들을 전부 집어넣어주면 된다.

import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import Intro from "../screens/Intro";
import ConnectWallet from "../screens/ConnectWallet";
import MapOrFeeds from "../screens/MapOrFeeds";
import Map from "../screens/Map";
import Feeds from "../screens/Feeds";
import Feed from "../screens/Feed";
import MintNFT from "../screens/MintNFT";
import MyPage from "../screens/MyPage";
import Setting from "../screens/Setting";
import TakePhoto from "../screens/TakePhoto";
import ExistedWallet from "../screens/wallet/ExistedWallet";
import NewWallet from "../screens/wallet/NewWallet";
import MakePassword from "../screens/MakePassword";
import BuyBucket from "../screens/BuyBucket";
import CheckMnemonic from "../screens/setting/CheckMnemonic";
import ChangePassword from "../screens/setting/ChangePassword";
import ResetWallet from "../screens/setting/ResetWallet";

const NavigateStack = createNativeStackNavigator();

const Stack = () => {
  return (
    <NavigateStack.Navigator
      screenOptions={{
        headerShown: false,
        contentStyle: {
          backgroundColor: "#C3BD2E",
        },
      }}
    >
      <NavigateStack.Screen name="Intro" component={Intro} />
      <NavigateStack.Screen name="ConnectWallet" component={ConnectWallet} />
      <NavigateStack.Screen name="ExistedWallet" component={ExistedWallet} />
      <NavigateStack.Screen name="MakePassword" component={MakePassword} />
      <NavigateStack.Screen name="NewWallet" component={NewWallet} />
      <NavigateStack.Screen name="BuyBucket" component={BuyBucket} />
      <NavigateStack.Screen name="MapOrFeeds" component={MapOrFeeds} />
      <NavigateStack.Screen name="Map" component={Map} />
      <NavigateStack.Screen name="Feeds" component={Feeds} />
      <NavigateStack.Screen name="Feed" component={Feed} />
      <NavigateStack.Screen name="MintNFT" component={MintNFT} />
      <NavigateStack.Screen name="MyPage" component={MyPage} />
      <NavigateStack.Screen name="Setting" component={Setting} />
      <NavigateStack.Screen name="CheckMnemonic" component={CheckMnemonic} />
      <NavigateStack.Screen name="ChangePassword" component={ChangePassword} />
      <NavigateStack.Screen name="ResetWallet" component={ResetWallet} />
      <NavigateStack.Screen name="TakePhoto" component={TakePhoto} />
    </NavigateStack.Navigator>
  );
};

export default Stack;

그리고 App.js에 다음과 같이 Stack을 넣어주고 NavigationContainer로 감싸기만 하면 된다.

import { NavigationContainer } from "@react-navigation/native";
import Stack from "./navigation/Stack";

export default function App() {
  return (
    <NavigationContainer>
    	<Stack />
    </NavigationContainer>
  );
}

🖍 stack navigation 안에 tab navigation 넣기?

엄청나게 리서치를 해본 결과, 불가능하다! (가능한데 방법을 못찾았을 수도)

우리 앱은 stack navigation으로 페이지들이 연결되어 있다. 그리고 특정 하나의 페이지 안에 작은 tab을 넣고 싶었다. 자세하게는 My Page에서 중첩된 2개의 탭을 넣고 싶었다.

아래 사진은 최종적으로 원하는 것을 구현해낸 결과이다. My Page라는 하나의 페이지 안에 My NFTs - My Wallet - My Txs 를 왔다갔다 하는 Tab이 있고, My NFTs 탭 안에 Created - Collected 를 왔다갔다 하는 하위 Tab이 또 하나 있다.

나는 stack navigation 안에 tab navigation을 그 안에 또 tab navigation을 넣으면 될 줄 알았다. 하지만 그런 방식으로 구현해낼 수 없었다. 이걸 해결하는 데에 가장 오랜 시간이 걸렸던 것 같다.

결국엔 react-native-tab-view라는 라이브러리를 적용해서 해결할 수 있었다. My Page 안에 Tab-view를 중첩으로 넣은 것이다. 이 Tab-view 관련해서도 다양한 라이브러리들이 있었는데 나는 가장 기본이 되는 라이브러리를 선택했고, 문제를 해결할 수 있었다.


🖍 tab-view 사용으로 발생한 또 다른 문제

My NFTs에는 내가 발행한(혹은 소유한) NFT들이 썸네일로 뜨고 있는데, 썸네일을 클릭하면 해당하는 NFT의 상세 정보 페이지로 이동되도록 구현하고 싶었다.

React navigation의 정말 편리한 점 중 하나가, stack이나 tab의 정의만 잘 해두면 해당 네비게이션에 포함된 모든 페이지들에 자동으로 navigation이라는 props를 전달할 수 있다. 이 navigation props로 원하는 페이지로 이동하거나(navigate), 뒤로 가기(goBack)를 아주 쉽게 구현할 수 있다.

const MyPage = ({ navigation: { goBack, navigate } }) => {

  // ... //
  const goToBuyBucket = () => {
    navigate("BuyBucket", { fromSignUp: false });
  };
  // ... //
}

하지만 내가 누를 썸네일사진들은 tab-view component 안에 있고, 이 tab-view는 navigation props를 받아올 수 없었다...! 여기서도 정말 많은 시간을 허비했는데, 결과적으로는 useNavigation을 위해 손쉽게 해결할 수 있었다. 어이가 없을 정도(ㅋㅋㅋㅋ)

import { useNavigation } from "@react-navigation/native";

const MyNFTsCreated = () => {
  // useNavigation으로 navigation을 하나 만들어주고,
  const navigation = useNavigation();

  return (
    
		// ... //
    
        {reverse.map((img, index) => {
          return (
            <TouchableOpacity
              key={index}
		      // 아래와 같이 사용하기만 하면 됨....!!!
              onPress={() => navigation.navigate("Feed", { feedId: img.id })}
            >
              <View
                style={[
                  { width: imgSize },
                  { height: imgSize },
                  { marginBottom: 2 },
                  index % 3 !== 0 ? { paddingLeft: 2 } : { paddingLeft: 0 },
                ]}
              >
                <Image
                  source={{ uri: img.imageUrl, headers: { Accept: "*/*" } }}
                  style={{ flex: 1 }}
                />
              </View>
            </TouchableOpacity>
          );
        })}
      </View>
    </ScrollView>
  );
};

export default MyNFTsCreated;



🖥 Xcode, Android Studio

스마트폰에 expo 앱을 설치하여 스마트폰으로 렌더링 결과를 확인해가며 개발했는데, PC 화면을 공유하며 협업할 때 너무 답답하게 느껴져서 결국 PC에 시뮬레이터를 설치해주었다. 상당히 번거롭고 귀찮은 과정이었는데 M1 Mac에 시뮬레이터를 설치하는 방법을 잘 적어주신 블로그 글(여기!)을 발견해서 편하게 진행할 수 있었다.




3. 이번 협업을 통해 배운 점


  • 기획의 어려움
    프로젝트 1,2는 주제가 정해진 클론코딩 프로젝트였기 때문에 기획에 오랜 시간이 걸리지 않았다. 하지만 이번 프로젝트는 온전히 우리의 아이디어로 앱을 기획해야 했기 때문에 기획에만 일주일 이상이 걸렸다. 개발/코딩은 명확하다. 정확한 결과가 나오기 때문이다. 하지만 기획은 각자가 생각하기 나름이기 때문에 아주 사소한 의사결정이더라도 오랜 협의가 필요하다. 아이디어를 구체화하고 또 논의를 통해 새로운 아이디어를 발견하는 과정이 재미있기도 했지만, 역시 나에게는 기획보다는 개발이 더 잘 맞는다는 생각이 확고해졌다.

  • 컨디션 관리
    이번 프로젝트를 하면서 밤을 새는 날이 많았는데(물론 그러고 낮에 많이 잠), 건강에 좋지 않은 것은 물론이고 여러모로 지양해야 할 습관이라고 생각된다. '이것만 해결하고 자야지' 하는 생각으로 해가 뜰 때까지 문제를 붙잡고 있고는 했다. 하지만 오히려 중간에 끊고 적당히 리프레시한 뒤 다시 문제를 볼 때 쉽게 해결할 수 있는 경우가 더 많았다. 개발자가 되어 현업에서 일을 할 때에도 정해진 근무 시간에 맞추어 일을 해야 할테니 맺고 끊음을 잘 해가며 시간을 쓰는 버릇을 들이는 게 좋겠다.




4. 보완해야 할 사항들

구현하고 싶었던 부분은 대부분 구현되었다. 앱의 구성과 디자인도 기획과 거의 흡사하게 완성되었다. 하지만 시간적 여유가 부족해 적용하지 못한 몇 가지가 있다. (클라이언트 한정)


  • 무한 스크롤
    웹이 아닌 앱이기 때문에 pagenation 보다는 무조건 무한 스크롤을 구현하는 것이 맞다. 두 번째 NGNG 프로젝트에서도 무한 스크롤 구현을 못했는데, 이번 앱의 Feeds 페이지에서도 구현하지 못했다. FlatListonEndReached, onEndReachedThreshold, ListFooterComponent 3가지 props를 사용하면 될 듯 하다. 어려워서는 아니고 시간이 없어서 구현하지 못했다 😭 추후에 꼭 완성해보는걸로..!
    (관련 내용은 이곳을 참고해보자!)

  • 성능 최적화
    이 항목은 프로젝트 1,2에 이어서 여전히 보완사항으로 등장하고 있다.. 이쯤되면 아직 성능 최적화에 대한 필요성을 절실히 느끼지 못한 것도 같다. 무조건 해야하는 거라면 어떻게든 하려고 했을 텐데, 그렇지 않은 걸 보니 그냥 성능 최적화가 필요한 사례를 절실히 경험해봐야 할 듯 하다.

  • Modal의 늪
    굉장히 많은 모달이 필요했다. 일부 alert가 사용된 곳도 있지만 유저와 상호작용이 필요한 곳에서는 최대한 모달을 사용해주었다. 문제는, 모달을 냅다 하나하나 만들어주었다는 점.. 대부분의 모달이 CSS는 물론 그 구성이 비슷하기 때문에 하나의 포맷으로 모듈화해서 안의 내용만 바꾸는 방식으로 활용했어야 했는데, 처음에는 이렇게 많은 모달을 만들어야 할 줄 몰랐지.. 다음부터 모달은 모듈화를 통해 더 효율적으로 개발하고 관리하는 것으로!

  • keypair 관리
    현재 유저가 로그인 할 때마다 유저의 keypair(public/private)redux store에 저장되어 앱 전역에서 사용되고 있다. 하지만 private key를 이렇게 redux로 관리하는 것은 보안에 매우 취약하기 때문에 실제 서비스 개발이라면 다른 안전한 관리 방법을 찾아야 할 것이다.

  • setting page 구현
    스테픈 어플리케이션을 참고하여 니모닉 코드 확인, 비밀번호 변경, 지갑을 관리하는 setting 페이지를 만들 예정이었는데 시간이 부족해 이번에는 구현하지 못했다. 사실 이미 앞에서 작성한 니모닉 코드와 비밀번호, 지갑 관련 개발 코드를 참고하면 어렵지 않게 구현이 가능할 것 같다.




5. 아쉬웠던 점

크게 아쉬웠던 점은 우선 시간이 부족했던 것이다. 애정과 욕심을 갖고 진행한 프로젝트였기 때문에 시간이 더 주어진다면 실제 서비스 앱 처럼 더욱 완성도를 높여보고 싶다. 또한 react-native에 대한 기본 지식이 거의 없는 상태에서 개발을 시작했다는 점이다. 배우면서 진행해야 했기에 조금 서툴렀고 또 여러모로 비효율적인 코드가 많이 작성된 듯 하다. 다시 앱을 개발할 기회가 생긴다면 이번보다 훨씬 효율적으로 개발할 수 있을 것 같다.



profile
멋쟁이 코린이

0개의 댓글