[React-native] 앱 다크모드 전환 회고

HongDuHyeon·2024년 8월 19일
1
post-thumbnail
달려가는 차에 도색하기

💡 글을 읽기 전..

이 글은 정보 제공일 수도 있고 회고일 수도 있는 글입니다.
험난 했던 3주간의 여정을 마친 채 정신줄을 꽉 잡고 쓰는 글이라 글의 서두가 불안정할 수도 있으니
양해 부탁드립니다. 🙇🏻


이번 마이루틴앱에 없던 다크모드를 맡게 되었습니다 !
작고 소중해보일 수 있는 앱이지만 생각보다 코드가 많고 복잡하지만 추상화와 구조화가 꽤 되어있답니다.

💡 수면 위로 떠오른 계기

글로벌까지 진출한 앱인데 다크모드가 없다구!?

전반적으로 동일 조명 조건에서 서양인이 동양인보다 공간의 밝기를 더 밝게 지각하는 것으로 나타났다.

라는 결과가 있더라구요 ! 그래서 서양인들이 빛의 민감도가 높아서 그런지 간접 조명도 쓰고 좀 더 어둡게 조명을 하는 것 같습니다.

사실 글로벌 유저들만의 문제가 아니라 CS로 종종 다크모드가 당연히 있는 앱인 줄 알고 있는 유저분들이 많이 계셨습니다. 하지만 팀 내에서 계속 치고나가야하는 feature들이 있거나 이슈가 있는 부분을 처리하다보니 계속 늦어지더라구요.

이젠 정말 해야겠다 라고 판단이 설 때쯤 우선 순위의 온도를 조금 더 올리기 위해 한 말 중

"다크모드는 오늘 하는 게 제일 빠릅니다."

라는 말이 생각이 나는 것 같아요.

왜냐하면 미뤄지다보면 새로운 기능이나 스크린이 추가 되는 경우도 있기 때문에 지금이 가장 빠를 때다! 라는 마음을 항상 갖고 있었습니다.
(사실 디자인 관련 작업엔 항상 진심이라 제가 하고 싶기도 했구요 ㅎㅎ..)

💡 고난

우리... 컬러 시스템 어떻게 쓰고 있지..?
굉장히 걱정 되었습니다. 디자이너 분께서 빠르게 디자인 파일을 전달해주셨고 색 팔레트를 다 뽑아서 전달해주셨거든요. 하지만 어떻게 작업을 해야할지 모르고 먼저 홈하면이나 주로 사용되는 스크린만 작업해서 느낌을 보여드리는게 낫겠지 ? 하며 안일하게 작업을 했습니다.

예를 들어 아래와 같은 코드입니다.

const App: React.FC = () => {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider theme={colorScheme === 'light' ? light : dark}>
      <Provider store={store}>
        <ErrorBoundary>
          <RootNavigation />
        </ErrorBoundary>
      </Provider>
    </ThemeProvider>
  );
};

// 컬러를 받아오는 함수 및 인터페이스
export type Theme = {
  // background color
  backgroundColor: string;
  homeFilterActiveBackgroundColor: string;
  bottomTabBarIconActiveBackgroundColor: string;
  bottomTabBarIconInactiveBackgroundColor: string;
  actionButtonBackgroundColor: string;
  homeTrafficLightBackgroundColor: string;
  mainTabNavigatorBackgroundColor: string;
  homeTabBarBackgroundColor: string;
  timeFilterButtonActiveBackgroundColor: string;
  timeFilterButtonBackgroundColor: string;
  ... 
}

export const light: Theme = {
  // background color
  backgroundColor: gray5,
  homeFilterActiveBackgroundColor: oliveGreen5,
  bottomTabBarIconActiveBackgroundColor: routineGreen60,
  bottomTabBarIconInactiveBackgroundColor: gray80,
  actionButtonBackgroundColor: routineGreen60,
  homeTrafficLightBackgroundColor: white,
  mainTabNavigatorBackgroundColor: white,
  homeTabBarBackgroundColor: gray5,
  timeFilterButtonActiveBackgroundColor: gray70,
  timeFilterButtonBackgroundColor: gray10,
  ...
}
  
export const dark: Theme = {
  // background color
  backgroundColor: '#2b2b2b',
  homeFilterActiveBackgroundColor: '#505050',
  bottomTabBarIconActiveBackgroundColor: gray10,
  bottomTabBarIconInactiveBackgroundColor: gray30,
  actionButtonBackgroundColor: '#505050',
  homeTrafficLightBackgroundColor: '#2b2b2b',
  mainTabNavigatorBackgroundColor: '#2b2b2b',
  homeTabBarBackgroundColor: '#2b2b2b',
  timeFilterButtonActiveBackgroundColor: '#505050',
  timeFilterButtonBackgroundColor: white,
  ...
}

코드 전부를 보여드릴 순 없지만 느낌이 오실겁니다.
컴포넌트에서 사용하는 걸 하나하나씩 다... 적어준다고........?

비효율의 끝판왕이었습니다.

물론 되게 스마트하고 프래그래머틱하게 진행을 하고 싶었습니다.
왜냐? 개발자니까요

일단 그래도 1차 피드백을 요청 드렸습니다. 꿋꿋히 ^^...
디자이너분 👨🏻‍🎨 / 본인 🧑🏻‍💻
👨🏻‍🎨 : 사실 어느정도 아웃풋이 나왔을 거라 생각했는데 안되어있는 부분도 정말 많았고 색이 다른 부분이 정말 많다. 왜 이런 결과물이 나온거죠?
🧑🏻‍💻 : 음 먼저 느낌을 보여드리고 싶었어요. 이렇게 진행을 해도 될지?

과거 토스에서 꽤 오랜 기간 재직을 하셨던 분이라 그런지 대화를 주고 나눌 때마다 실시간으로 배움이 생겼습니다.

👨🏻‍🎨 : 분명 저 컬러값을 더 효율적으로 할 수 있는 방법이 있을 것 같다. 하지만 지금 상태로는 너무 오래 걸릴 것 같다.
🧑🏻‍💻 : 흠.... (침묵 후 생각 중)
🧑🏻‍💻 : 일단 알겠습니다. 그럼 저희가 어떻게든 금요일까지 다시 작업한 화면을 보여드릴게요.
👨🏻‍🎨 : 알겠습니다~ 감사합니다~!

같이 진행한 PM이자 CTO인 민식님과 방법을 고민하던 중 번뜩이는 아이디어가 떠올랐습니다.

💡 새로운 접근

그럼 이 컬러 데이터 값을 export 하는 곳에서 디바이스 스키마나 유저가 정한 값에 따라 바꾸면 되는거 아닌가...? 그럼 새롭게 import할 필요도 없고 기존에 있는 코드를 거의 유지하면서 안 바뀐 부분을 찾는 게 더 빠를 것 같은데 !? 라는 생각이 들어서 바로 실천에 옮겼습니다.

export const white = '#FFF';
export const black = '#000';

export const gray5 = '#BBBBBB';
export const gray10 = '#CCCCCC';
export const gray20 = '#DDDDDD';

현재 앱에선 이렇게 그냥 hex값을 변수에 담아 export를 해주는 방식이었습니다.
만약 이 코드안에 이름은 똑같지만 라이트/다크 모드에 맞게 파싱해서 값을 보내주면..?
이거... 달다..!

먼저 해야할 것을 생각했습니다. 디바이스의 디스플레이가 라이트/다크인지 먼저 가져오자!

export const THEME_KEY = 'THEME';
// AsyncStorage와 같다고 생각하시면 됩니다.
export const themePreference = kvStorage.getString(THEME_KEY) ?? 'system'; 
export const isLightTheme = themePreference === 'system' ? Appearance.getColorScheme() !== 'dark' : themePreference !== 'dark';

React-native에서 지원해주는 Appearance로 colorScheme를 가져옵니다. 그렇게 라이트/다크 모드를 판단하고 isLightTheme라는 boolean 값을 라이트 모드 일 때만 작성되어야하는 코드들을 판별하기 위해 다른 곳에서 사용이 되어야하니 export를 해줍니다.

그리고 LightColor일떄 white와 DarkColor일때 white를 디자이너분께서 주신 hex code대로 각각 객체의 key value로 만들어서 넣어줍니다.

export const DARK_COLORS = {
  // color
  white: '#1C1D1F',
  ...
}
export const LIGHT_COLORS = {
  // color
  white: '#FFFFFF',
  ...
}

export const white = isLightTheme ? LIGHT_COLORS.white : DARK_COLORS.white

마지막으로 white는 다른 곳에서 계속 import 되고 있으니 최대한 이 파일에서 모든 처리를 다 해줘서 export 해주면 끝 !

이렇게 되면 기존에 jsx나 ts에서 사용되어지는 파일의 경로, import name을 고칠 필요가 없고 라이트/다크 모드에 맞게 알아서 환경에 맞게 값을 가져오기 때문에 인지적으로 신경쓰며 작업해야할 부분을 많이 없애줬습니다.

💡 약간의.. 지뢰 찾기

이번엔 컬러 시스템의 정의가 갖춰지기 전에 있던 n년전 코드들이 속 썩였습니다.

예를 들어 rgba(0, 0, 0, 1)이나 backgroundColor: 'white' 등 vscode의 참조로 찾을 수 없는 string 값들이 꽤나 많이 존재했습니다.

이 부분에 대해선 사실 하나하나 찾아가며 바꿔줬습니다. 하지만 조금이라도 효율적으로 해보자해서 검색할 때 'white' 로 검색하거나 rgba( 이렇게 포괄적으로 검색함으로써 많은 걸 찾아낼 수 있었습니다. 물론 정말 힘들었던건 'white'가 backgroundColor만 들어가는게 아니라 스크린명을 보고 찾고 또 찾고 찾고.... 반복이었지만 방법을 바꾸고 신경 써야할게 많이 줄어들어서 충분히 리소스를 투자할 수 있었던 것 같습니다.

물론 지뢰를 찾으면서도 배움이 있었던 게
아무렇지 않게 'white'처럼 냅다 string으로 작성한 컬러값들은 유지보수에 정말 쥐약이구나! 라는 걸 깨닫게 되는 ^^.... 뭐 그런 ....

💡 개발 플로우 변화 / 공유

팀에 개발자가 5명인데 한명씩 다크모드를 작업할 때 플로우를 따르지 않으면 큰 재앙이 되겠죠?

작업하는 것도 중요하지만 공유를 해서 현상을 유지하는 것도 굉장히 중요하다고 생각합니다.

  1. 디자이너와 협의되지 않은 다크모드에서의 LIGHT_COLORS의 import는 절대 안된다.
    • 이걸 무시하게 된다면 테스트 용으로 다크모드 라이트 모드에서 같은 hex 값을 봐보자 ! 하고 계속 작업을 했다가 어디서 왜 이 컬러가 나오는지에 대해 또 리소스를 낭비하게 됩니다.
  2. 앞으로 코드상의 Hex Color는 더이상 없어야한다.
    • Hex color가 있다는 것은 지정되지 않은 색상이며 다크모드가 대응되지 않았다. 라고 판단하거나 충분히 의심 해야합니다.

가벼워 보이지만 무거운 2개의 룰을 제가 팀에 공유하려 합니다.
우린 앞으로 일을 더 잘해야만 하기 때문이죠.

💡 회고

이렇게까지 길게 글을 쓰면서도 회고 포인트가 자꾸 생각이 나는군요.
1. 항상 왜?를 고민하자 내가 작업하고 있는 방식이 100% 맞을 순 없습니다. 터널 시야에서 빠져나오려면 메타인지 버튼을 자꾸 눌러줘야한다고 생각합니다. 관련이 없는 제 3자의 시선이 분명히 제게 도움이 될 수도 있으니까요.
2. 섣부르게 추상화/구조화 하지 말자. 섣부른 추상화는 오히려 독이 될 수도 있음을 느꼈습니다. 어? 이렇게 하면 지금 작업은 편하게 할 수 있잖아 ! 라고 했지만 가면 갈수록 추상화를 했는데 반복적으로 쓰는 코드가 많이 지고 나중에 돼서야 아.. 이렇게 하면 내가 내 부하를 높히는구나를 경험했습니다.

가장 크게 느꼈던 두가지인데 텍스트로 보면 그렇게 크게 느껴지지 않을 수도 있지만 정말 현타 많이 왔습니다 ㅎㅎ....

그럼에도 앱 다크모드 스프린트를 성공적으로 마칠 수 있었던 건 앱을 사용하는 유저들이 더 편하게 앱을 사용하게 되면 어떨까? cs에 제가 개발한 것에 대해 감사하다는 말을 들으면 기분이 정말 좋더라구요. 배포된지 3일 정도 지났으니 다음주 주간회의 cs 모음에 칭찬 하나쯤은 있었으면 좋겠습니다 ㅎㅎ

추가로 이 게시글을 보고 계실지 모르겠지만 다크모드를 만들 때 계속 제 메타인지를 도와주셨던 CTO 민식님 ! 항상 너무 배울게 많고 잘 가르쳐주시는데 제가 많이 못따라가는 것 같습니다. 매번 표현은 적게 하지만 너무 감사해요 🙏🏻 그리고 흔들리는 멘탈 꽉 잡아준 개발팀 모두 감사하고 테스트 트랙에서 계속 디자인 이슈 잡아주신 기획팀 너무 감사했습니다 👍 마지막으로 다크 모드 스크린샷과 함께 타이밍 맞춰서 해외 마케팅 돌려주신 마케팅팀도 너무 고맙습니다 😭

profile
마음이 시키는 프론트엔드.. RN과 IOS를 곁들인..

1개의 댓글

comment-user-thumbnail
2024년 8월 19일

잘보고갑니다 😎

답글 달기