반응형

앱에서 고유 ID(Unique ID)를 생성해야 할 때, 가장 널리 쓰이는 방식이 바로 UUID (Universally Unique Identifier)입니다.
Flutter에서는 uuid 패키지를 통해 간편하게 다양한 방식의 UUID를 생성할 수 있습니다.

이 글에서는:

  • uuid 패키지 설치 방법
  • UUID 버전(v1, v4, v5)의 차이점
  • 실전 예제와 사용 팁

까지 하나씩 정리해보겠습니다.


📦 1. uuid 패키지 설치하기

flutter pub add uuid

혹은 pubspec.yaml에 직접 추가:

dependencies:
  uuid: ^4.2.1  # 최신 버전 확인

그리고 import:

import 'package:uuid/uuid.dart';

🧬 2. UUID 버전별 차이점 정리

버전 방식 설명 특징

v1 Time-based 시간 + 기기(MAC 주소) 기반 순차적 생성 가능, MAC 유출 위험
v2 DCE Security v1 + 사용자/그룹 ID 거의 사용되지 않음
v3 Name-based (MD5) 문자열 + 네임스페이스 → MD5 해시 입력이 같으면 UUID도 같음
v4 Random 무작위(random) 값 기반 일반적으로 가장 많이 사용됨
v5 Name-based (SHA-1) v3와 동일하나 SHA-1 해시 사용 더 강력한 보안 해시

⚙️ 3. UUID 생성 예제 (Dart 코드)

final uuid = Uuid();

// ✅ v1 – 시간 + MAC 주소 기반
String idV1 = uuid.v1(); // 예: 'f64c8b26-3e46-11ec-8d3d-0242ac130003'

// ✅ v4 – 랜덤 기반 (가장 많이 사용)
String idV4 = uuid.v4(); // 예: 'c3c3bc94-b070-4787-9f7d-b2592b4d51ef'

// ✅ v5 – 고정된 문자열로 UUID 생성 (SHA-1)
String idV5 = uuid.v5(Uuid.NAMESPACE_URL, 'https://example.com');

📌 v3도 동일 방식으로 가능:

String idV3 = uuid.v3(Uuid.NAMESPACE_URL, 'https://example.com'); // MD5 기반

✅ 실전에서는 언제 어떤 버전?

상황 추천 버전 이유

단순한 고유 ID가 필요할 때 v4 간단하고 충돌 확률 매우 낮음
시간 순서대로 정렬이 필요할 때 v1 생성 시간 정보 포함됨
입력값이 항상 같을 때 동일한 ID가 필요할 때 v5 (또는 v3) 해시 기반 고정 UUID 생성

❗️주의할 점 (v1의 MAC 주소 노출)

  • v1 UUID는 내부적으로 기기의 MAC 주소를 사용합니다.
  • 이 정보는 유출되면 기기 식별 가능성이 있으므로 보안이 중요한 앱에서는 피하는 것이 좋습니다.
  • 대부분의 경우 v4를 사용하는 것이 안전하고 충분합니다.

✨ 실전 사용 예: 모델 ID 생성

class Task {
  final String id;
  final String title;

  Task({required this.title}) : id = const Uuid().v4();
}

→ 매 Task 인스턴스를 생성할 때 자동으로 고유한 UUID를 부여합니다.


🧩 요약

  • uuid 패키지를 설치하면 다양한 방식으로 고유 식별자를 생성할 수 있음
  • 대부분의 경우 v4 (랜덤 기반)가 안정적이고 보편적
  • v5는 동일 입력 → 동일 UUID가 필요할 때 매우 유용
반응형
반응형

📝 요약 설명 

Flutter로 macOS 앱을 개발할 때 기본 창 크기를 설정하고, 앱을 "항상 위에 고정(Always on Top)"으로 띄우는 방법을 소개합니다. 에디터와 앱을 나란히 띄우기 위한 실사용 팁!


🖥️ 문제 상황

Flutter로 macOS 앱을 개발할 때, 앱을 항상 에디터 옆에 띄워놓고 싶었어요.
하지만 앱을 실행하면 매번 이상한 위치에 뜨거나 다른 창 뒤로 가버리는 게 너무 불편했습니다.

그래서 macOS 앱을 항상 위에 고정시키고, 기본 창 크기도 지정하는 방법을 정리해봤습니다.


🔧 해결 방법 요약

  • MainFlutterWindow.swift에서 macOS 앱의 창 속성을 직접 설정할 수 있습니다.
  • 여기서 창 크기, 창 위치, 항상 위에 고정(floating)을 커스터마이징하면 됩니다.

📁 수정할 파일

macos/Runner/MainFlutterWindow.swift


✅ 최종 코드 예시

import Cocoa
import FlutterMacOS

class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    let flutterViewController = FlutterViewController()

    // 원하는 창 크기
    let windowWidth: CGFloat = 800
    let windowHeight: CGFloat = 600

    // 화면 중앙에 창 위치 설정
    if let screenFrame = NSScreen.main?.frame {
      let originX = (screenFrame.width - windowWidth) / 2
      let originY = (screenFrame.height - windowHeight) / 2
      let newFrame = NSRect(x: originX, y: originY, width: windowWidth, height: windowHeight)
      self.setFrame(newFrame, display: true)
    }

    // 항상 위에 설정
    self.level = .floating

    self.contentViewController = flutterViewController

    RegisterGeneratedPlugins(registry: flutterViewController)

    super.awakeFromNib()
  }
}

📌 추가 팁

  • self.level = .floating → 이걸 설정하면 앱 창이 항상 다른 창 위에 떠 있어요.
    특히 코드 에디터 + 앱 창 나란히 테스트할 때 엄청 편합니다.
  • self.setFrame(...) → 앱의 기본 위치와 크기를 지정할 수 있습니다.
반응형

✨ 마무리

에디터와 앱을 동시에 띄워놓고 작업할 때, 이 설정 하나로 스트레스가 확 줄었어요.
macOS 앱에서 UI 디버깅 자주 하시는 분들께 꼭 추천드립니다.


필요하면 이미지나 gif 첨부용 예시도 만들어줄 수 있어.
티스토리에 바로 붙여넣을 HTML 포맷도 원해?

반응형
반응형

🔍 문제 상황

React에서 컴포넌트 리스트를 .map()으로 렌더링할 때, 다음과 같은 에러가 자주 발생합니다:

Warning: Each child in a list should have a unique 'key' prop.

그래서 보통은 아래처럼 key를 추가하죠.

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

하지만! 정상적으로 key를 넣었음에도 불구하고 위와 같은 경고가 계속 발생하는 경우가 있습니다.


❓ 대체 왜?

이 문제의 진짜 원인은… 바로 Fragment (<>...</>) 때문입니다.


💥 문제 코드 예시

items.map((item) => (
  <>
    <MyComponent key={item.id} item={item} />
  </>
))

위 코드는 MyComponent에 key를 넣었지만,
사실상 무시되고 있습니다.

왜냐하면, React는 .map()으로 직접 리턴하는 최상위 노드에 key가 있는지를 보기 때문입니다.

즉, 위의 코드는:

  • Fragment가 최상위인데 key 없음 → ❌
  • 내부 MyComponent에 있는 key → 무시됨 → ❌ 경고 발생

✅ 해결 방법

방법 1: Fragment 없애기 (권장)

items.map((item) => (
  <MyComponent key={item.id} item={item} />
))

방법 2: Fragment에 key 넣기 (가능하지만 비권장)

items.map((item) => (
  <React.Fragment key={item.id}>
    <MyComponent item={item} />
  </React.Fragment>
))

💡 실전 예: React Native ScrollView 안에서 발생한 사례

<ScrollView>
  {items.map((item) => (
    <>
      <TimelineItem key={item.id} item={item} />
    </>
  ))}
</ScrollView>

이 구조에서 Fragment가 key를 무시하면서 경고가 발생했습니다.
→ Fragment를 제거하거나, key를 Fragment로 올리면 해결됩니다!


🧠 정리

구조 key 인식 여부 추천 여부

<Component key={...} /> ✅ 강력 추천
<><Component key={...} /></> ❌ 경고 발생
<Fragment key={...}><Component /></Fragment> ⚠️ 가능하지만 가독성 저하 우려

✅ 마무리

React에서 key 경고가 사라지지 않을 때는
"내가 넣은 key가 최상위 엘리먼트에 있는가?" 를 꼭 확인하세요!

특히, 아무 생각 없이 쓴 <> ... </> 가 문제의 원인일 수 있습니다 😉

 

 

반응형
반응형

⚙️ 시작: uuid 설치 및 사용

React Native 앱에서 일정 추가 시 고유 ID가 필요해서
보통 많이 사용하는 uuid 패키지를 설치하고 다음처럼 사용했습니다:

pnpm add uuid
import { v4 as uuidv4 } from 'uuid';

const id = uuidv4();

그리고 이 값을 일정 객체에 사용했죠:

addSchedule({
  id: uuidv4(), // ❌ 여기서 에러 발생
  title: '주사 맞기',
  ...
});

그런데 앱을 실행하자 다음과 같은 에러가 발생했습니다:

Error: crypto.getRandomValues() not supported.

👀 이 에러는 웹 브라우저에서 제공하는 crypto.getRandomValues()를
React Native에서 사용할 수 없기 때문에 발생한 것입니다.


🔍 원인 요약

  • uuid 패키지는 웹 브라우저 환경을 기준으로 만들어졌고
  • 내부적으로 crypto.getRandomValues()를 사용함
  • 하지만 React Native는 웹이 아니므로 해당 API가 없음

⚠️ 원인: uuid 패키지의 내부 동작

많은 개발자들이 사용하는 uuid 패키지는 브라우저 환경에서 잘 작동합니다.
하지만 이 패키지의 v4 구현은 내부적으로 crypto.getRandomValues()를 사용하기 때문에 React Native에서는 동작하지 않습니다.


💡 해결 방법: React Native에 맞는 UUID 패키지 사용

✅ 방법 1: react-native-uuid 사용 (권장)

  1. 설치
pnpm add react-native-uuid
  1. 사용법
import uuid from 'react-native-uuid';

const id = uuid.v4() as string;

uuid.v4()는 string | number[] 형태를 반환하므로, 타입스크립트에서는 as string으로 명시해주는 것이 좋습니다.


✅ 방법 2: expo-crypto 사용 (Expo 사용자)

  1. 설치
pnpm add expo-crypto
  1. 사용법
import * as Crypto from 'expo-crypto';

const id = await Crypto.getRandomUUID();

이 방식은 async/await이 필요하기 때문에 함수 구조를 비동기적으로 변경해야 합니다.


🧠 정리

패키지 RN 지원 여부 특징

uuid ❌ (웹 전용) crypto.getRandomValues() 사용 → 에러 발생
react-native-uuid 동기 방식, 간단하게 사용 가능
expo-crypto ✅ (Expo 전용) 공식 지원, 비동기 방식 필요

✨ 마무리

React Native에서는 브라우저의 crypto API를 사용할 수 없습니다.
따라서 UUID가 필요할 경우, RN에 맞는 패키지를 선택해서 사용하는 것이 중요합니다.

이번 경험을 통해 웹과 네이티브의 차이를 잘 이해하게 되었고, 앞으로는 환경에 맞는 라이브러리 선택이 얼마나 중요한지 다시 한 번 느꼈습니다. 💪

 

반응형
반응형

 

🔍 에러 상황

React Native 프로젝트에서 아래와 같은 코드를 작성했습니다:

import { colors } from '@repo/constants';

const styles = StyleSheet.create({
  container: {
    backgroundColor: colors.green[900],
  },
});

그런데 앱을 실행했더니 다음과 같은 에러가 발생했습니다:

❌ Cannot read property 'green' of undefined

🤔 왜 이런 에러가 발생할까?

에러의 원인은 default export를 named import로 불러왔기 때문입니다.

📁 colors.ts

const colors = {
  green: {
    900: '#064E3B'
  },
  ...
};

export default colors;

📁 index.ts

export * from './colors';

colors.ts는 default export지만,
index.ts에서는 named export로 넘기지 않았기 때문에
import { colors } from '@repo/constants'는 undefined가 됩니다.


✅ 해결 방법 3가지


✅ 방법 1. default → named export로 바꾸기 (가장 추천)

// colors.ts
export const colors = {
  ...
};
// index.ts
export * from './colors';
// 사용처
import { colors } from '@repo/constants';

✅ 가장 직관적이고 안전한 방식입니다.


✅ 방법 2. default export 유지하면서 export 이름 지정

// colors.ts
const colors = { ... };
export default colors;
// index.ts
export { default as colors } from './colors';
// 사용처
import { colors } from '@repo/constants';

✅ default export를 유지하면서도 named import처럼 쓸 수 있습니다.


⚠️ 방법 3. default import로 직접 경로 import (비추천)

import colors from '@repo/constants/colors';

⚠️ 경로를 직접 노출하게 되어 리팩토링이 어려워질 수 있습니다.


💡 결론

목적 추천 방식

import { colors } 하고 싶다 export const colors = ... 사용
default export 꼭 유지하고 싶다 export { default as colors } 추가
하위 경로로 import 가능은 하지만 비추천

 

반응형
반응형

React Native로 앱을 만들다 보면 버튼이나 카드처럼 누를 수 있는 영역을 만들 때 TouchableOpacity 또는 Pressable 중 어떤 걸 써야 할지 고민될 때가 있다.

두 컴포넌트 모두 유저의 터치 입력을 감지하는 역할을 하지만, 사용 방식과 유연성에서 차이가 있다.
이 글에서는 두 컴포넌트의 차이점과 사용 시점을 정리해 본다.


🔸 TouchableOpacity

TouchableOpacity는 React Native 초창기부터 존재한 컴포넌트로,
사용자가 터치하면 **불투명도(opacity)**가 낮아지는 시각적 피드백을 준다.

import { TouchableOpacity, Text } from 'react-native';

<TouchableOpacity onPress={() => alert('Pressed')}>
  <Text>Click me</Text>
</TouchableOpacity>

✅ 장점

  • 사용법이 매우 간단하다
  • 기본적인 눌림 효과(투명도 변화)가 내장돼 있어 빠르게 UI 구성 가능

❌ 단점

  • 스타일 커스터마이징이 제한적이다
  • 눌림 효과는 opacity 하나뿐

🔸 Pressable

Pressable은 React Native 0.63부터 도입된 최신 터치 컴포넌트로,
눌림 상태에 따라 스타일을 완전히 커스터마이징할 수 있는 것이 가장 큰 특징이다.

import { Pressable, Text } from 'react-native';

<Pressable
  onPress={() => alert('Pressed')}
  style={({ pressed }) => ({
    backgroundColor: pressed ? '#eee' : '#fff',
    padding: 10,
  })}
>
  <Text>Click me</Text>
</Pressable>

✅ 장점

  • pressed 상태를 이용해 배경색, 그림자, 테두리 등 다양한 스타일 변경 가능
  • 눌렀을 때 외에도 onPressIn, onPressOut 등 다양한 콜백 제공

❌ 단점

  • 코드가 상대적으로 길고 복잡할 수 있다
  • 간단한 버튼엔 오히려 과할 수 있음

🆚 한눈에 비교

항목 TouchableOpacity Pressable
도입 시기 오래됨 최신 (RN 0.63+)
눌림 효과 opacity 자동 적용 자유롭게 스타일 커스터마이징
스타일 제어 제한적 pressed로 조건부 스타일링
콜백 이벤트 기본적 onPressIn, onPressOut 등 다양
추천 사용처 간단한 터치 UI 다양한 효과가 필요한 경우

💡 언제 어떤 걸 써야 할까?

  • 단순한 버튼, 리스트 아이템 → TouchableOpacity
  • 눌렀을 때 배경색, 테두리 등 커스텀 UI 효과 → Pressable
  • ✅ 최신 React Native 기준의 코드 스타일을 따르고 싶다면 → Pressable 선호

✍️ 마무리

React Native에서 두 컴포넌트는 결국 터치 이벤트 처리를 위한 도구지만,
개발자의 의도에 따라 적절히 선택하는 것이 중요하다.

  • 빠르게 만들고 싶다면 TouchableOpacity
  • 정교한 UI 피드백이 필요하면 Pressable

 

반응형
반응형

 

🎨 Tailwind 색상 팔레트를 React Native로!

TailwindCSS의 색상 시스템은 직관적인 이름(blue_500, gray_900)과 단계별 명확한 색감으로 많은 개발자에게 사랑받고 있습니다.
React Native에서도 동일한 색상 시스템을 구성하면 일관된 디자인 시스템 구축, 다크모드 대응, alpha 값 처리 등 많은 이점을 누릴 수 있습니다.


🌈 전체 색상표 (100 ~ 900)

아래는 TailwindCSS 기준으로 React Native에서 사용할 수 있도록 구성한 색상 팔레트입니다.

글 하단에 요약된 코드를 넣어 두었습니다.

✅ 각 색상은 colors.색상이름[단계] 형태로 접근합니다.
예: colors.blue[500], colors.red[900]


🔵 blue

단계 HEX 코드

100 #DBEAFE
200 #BFDBFE
300 #93C5FD
400 #60A5FA
500 #3B82F6
600 #2563EB
700 #1D4ED8
800 #1E40AF
900 #1E3A8A

⚫ gray

단계 HEX 코드

100 #F3F4F6
200 #E5E7EB
300 #D1D5DB
400 #9CA3AF
500 #6B7280
600 #4B5563
700 #374151
800 #1F2937
900 #111827

🔴 red

단계 HEX 코드

100 #FEE2E2
200 #FECACA
300 #FCA5A5
400 #F87171
500 #EF4444
600 #DC2626
700 #B91C1C
800 #991B1B
900 #7F1D1D

🟢 green

단계 HEX 코드

100 #D1FAE5
200 #A7F3D0
300 #6EE7B7
400 #34D399
500 #10B981
600 #059669
700 #047857
800 #065F46
900 #064E3B

🟡 yellow

단계 HEX 코드

100 #FEF9C3
200 #FEF08A
300 #FDE047
400 #FACC15
500 #EAB308
600 #CA8A04
700 #A16207
800 #854D0E
900 #713F12

🟣 purple

단계 HEX 코드

100 #F3E8FF
200 #E9D5FF
300 #D8B4FE
400 #C084FC
500 #A855F7
600 #9333EA
700 #7E22CE
800 #6B21A8
900 #581C87

🌸 pink

단계 HEX 코드

100 #FCE7F3
200 #FBCFE8
300 #F9A8D4
400 #F472B6
500 #EC4899
600 #DB2777
700 #BE185D
800 #9D174D
900 #831843

🧊 cyan

단계 HEX 코드

100 #CFFAFE
200 #A5F3FC
300 #67E8F9
400 #22D3EE
500 #06B6D4
600 #0891B2
700 #0E7490
800 #155E75
900 #164E63

 


반응형

 

const colors = {
  gray: {
    100: '#F3F4F6',
    200: '#E5E7EB',
    300: '#D1D5DB',
    400: '#9CA3AF',
    500: '#6B7280',
    600: '#4B5563',
    700: '#374151',
    800: '#1F2937',
    900: '#111827',
  },
  blue: {
    100: '#DBEAFE',
    200: '#BFDBFE',
    300: '#93C5FD',
    400: '#60A5FA',
    500: '#3B82F6',
    600: '#2563EB',
    700: '#1D4ED8',
    800: '#1E40AF',
    900: '#1E3A8A',
  },
  green: {
    100: '#D1FAE5',
    200: '#A7F3D0',
    300: '#6EE7B7',
    400: '#34D399',
    500: '#10B981',
    600: '#059669',
    700: '#047857',
    800: '#065F46',
    900: '#064E3B',
  },
  red: {
    100: '#FEE2E2',
    200: '#FECACA',
    300: '#FCA5A5',
    400: '#F87171',
    500: '#EF4444',
    600: '#DC2626',
    700: '#B91C1C',
    800: '#991B1B',
    900: '#7F1D1D',
  },
  yellow: {
    100: '#FEF9C3',
    200: '#FEF08A',
    300: '#FDE047',
    400: '#FACC15',
    500: '#EAB308',
    600: '#CA8A04',
    700: '#A16207',
    800: '#854D0E',
    900: '#713F12',
  },
  indigo: {
    100: '#E0E7FF',
    200: '#C7D2FE',
    300: '#A5B4FC',
    400: '#818CF8',
    500: '#6366F1',
    600: '#4F46E5',
    700: '#4338CA',
    800: '#3730A3',
    900: '#312E81',
  },
  purple: {
    100: '#F3E8FF',
    200: '#E9D5FF',
    300: '#D8B4FE',
    400: '#C084FC',
    500: '#A855F7',
    600: '#9333EA',
    700: '#7E22CE',
    800: '#6B21A8',
    900: '#581C87',
  },
  pink: {
    100: '#FCE7F3',
    200: '#FBCFE8',
    300: '#F9A8D4',
    400: '#F472B6',
    500: '#EC4899',
    600: '#DB2777',
    700: '#BE185D',
    800: '#9D174D',
    900: '#831843',
  },
  teal: {
    100: '#CCFBF1',
    200: '#99F6E4',
    300: '#5EEAD4',
    400: '#2DD4BF',
    500: '#14B8A6',
    600: '#0D9488',
    700: '#0F766E',
    800: '#115E59',
    900: '#134E4A',
  },
  orange: {
    100: '#FFEDD5',
    200: '#FED7AA',
    300: '#FDBA74',
    400: '#FB923C',
    500: '#F97316',
    600: '#EA580C',
    700: '#C2410C',
    800: '#9A3412',
    900: '#7C2D12',
  },
  lime: {
    100: '#ECFCCB',
    200: '#D9F99D',
    300: '#BEF264',
    400: '#A3E635',
    500: '#84CC16',
    600: '#65A30D',
    700: '#4D7C0F',
    800: '#3F6212',
    900: '#365314',
  },
  cyan: {
    100: '#CFFAFE',
    200: '#A5F3FC',
    300: '#67E8F9',
    400: '#22D3EE',
    500: '#06B6D4',
    600: '#0891B2',
    700: '#0E7490',
    800: '#155E75',
    900: '#164E63',
  },
  zinc: {
    100: '#F4F4F5',
    200: '#E4E4E7',
    300: '#D4D4D8',
    400: '#A1A1AA',
    500: '#71717A',
    600: '#52525B',
    700: '#3F3F46',
    800: '#27272A',
    900: '#18181B',
  },
};

export default colors;

 

🏁 마무리

Tailwind의 색상 시스템은 미적으로 뛰어나고 확장도 쉽습니다.
React Native에서도 이 구조를 도입하면 다크모드 대응은 물론, 협업 시에도 큰 도움이 됩니다.

반응형
반응형

🔥 소개

TailwindCSS 스타일의 색상 시스템은 직관적이고 확장성이 뛰어나 React Native에도 잘 어울립니다.
이번 글에서는 blue_100, gray_900처럼 단계별 색상 정의, 다크모드 대응, alpha 값 추가, 그리고 자동완성 가능한 타입 정의까지 한 번에 구성하는 방법을 소개합니다.


📁 폴더 구조

먼저, 색상 관련 파일을 모아둘 theme 디렉토리를 만들어 아래처럼 구성합니다.

src/
└── theme/
    ├── colors.ts              # Tailwind 색상 팔레트
    ├── darkColors.ts          # 다크모드 색상
    ├── alphaColor.ts          # alpha 유틸
    ├── theme.ts               # 라이트/다크 테마 설정
    ├── useThemeColor.ts       # 현재 테마 기준 색상 반환
    ├── tailwindColors.d.ts    # 타입 정의 (자동완성 지원)
    └── index.ts               # export 모음

🎨 Tailwind 색상 정의

colors.ts에서 Tailwind 스타일로 색상을 정의합니다. 아래는 일부 예시이며, 필요에 따라 추가할 수 있습니다.

const colors = {
  blue: {
    100: '#DBEAFE',
    500: '#3B82F6',
    900: '#1E3A8A',
  },
  gray: {
    100: '#F3F4F6',
    500: '#6B7280',
    900: '#111827',
  },
  red: {
    100: '#FEE2E2',
    500: '#EF4444',
    900: '#7F1D1D',
  },
  // 추가 색상: green, yellow, indigo, etc...
}

export default colors

🌙 다크모드 색상 정의

다크모드에서는 gray 계열을 반전시켜 사용하면 좋습니다.

const darkColors = {
  ...colors,
  gray: {
    100: '#18181B',
    500: '#71717A',
    900: '#F4F4F5',
  },
  background: '#0f172a',
  foreground: '#f8fafc',
}

export default darkColors

💧 Alpha 값 유틸 함수

투명도를 추가하고 싶다면 아래처럼 alpha를 붙여주는 유틸을 만듭니다.

export function withAlpha(hex: string, alpha: number): string {
  const bounded = Math.max(0, Math.min(1, alpha))
  const alphaHex = Math.round(bounded * 255).toString(16).padStart(2, '0')
  return hex + alphaHex
}

🧠 현재 테마 기반 색상 가져오기

React Native의 useColorScheme() 훅을 활용해 현재 테마에 맞는 색상을 가져옵니다.

export function useThemeColor(color: 'blue' | 'gray', shade: 100 | 500 | 900) {
  const theme = useColorScheme() === 'dark' ? 'dark' : 'light'
  return themes[theme].colors[color][shade]
}

🧾 타입 선언으로 자동완성 지원

타입스크립트를 쓰는 경우, 색상 이름과 단계 타입을 미리 정의해두면 IDE 자동완성과 에러 방지에 큰 도움이 됩니다.

export type TailwindColorName =
  | 'gray' | 'blue' | 'green' | 'red' | 'yellow' | 'indigo' | 'purple'

export type TailwindColorShade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900

✅ 사용 예시

const bgColor = useThemeColor('blue', 100)
const textColor = useThemeColor('gray', 900)
const transparent = withAlpha(bgColor, 0.5)

return (
  <View style={{ backgroundColor: transparent, padding: 16 }}>
    <Text style={{ color: textColor }}>Tailwind 스타일 색상 예제</Text>
  </View>
)

🏁 마무리

Tailwind 스타일 색상 시스템을 React Native에 도입하면 코드 일관성, 유지보수성, 확장성이 모두 향상됩니다.
이번 구성은 디자인 시스템의 기반이 되므로 프로젝트 초기부터 도입해보세요!

반응형
반응형

안드로이드 앱을 개발할 때, 삼성 갤럭시 기기에서 직접 테스트해보는 것은 매우 중요합니다. 다행히 안드로이드 스튜디오에서는 삼성에서 제공하는 갤럭시 에뮬레이터 스킨을 추가해 실제 기기와 유사한 테스트 환경을 구성할 수 있습니다. 이번 글에서는 각 단계별로 캡처 이미지를 함께 보며 갤럭시 에뮬레이터를 설정하는 방법을 안내드리겠습니다.


1️⃣ 삼성 개발자 사이트에서 스킨 다운로드

  • Samsung Developers - Galaxy Emulator Skin 페이지로 이동합니다.
  • 원하는 기종(예: Galaxy S25, Galaxy Z Flip 등)의 에뮬레이터 스킨을 선택하여 다운로드합니다. - 가입 필요


2️⃣ 압축 해제 및 스킨 폴더 복사

  • 다운로드한 .zip 파일의 압축을 해제합니다.
  • 해제된 폴더 전체를 Android SDK 디렉토리 내 skins 폴더에 복사합니다.
    • Windows: C:\Users\사용자명\AppData\Local\Android\Sdk\skins
    • macOS: /Users/사용자명/Library/Android/sdk/skins
  • 저는 편의를 위해 Documents 폴더에 압축을 해제하여 사용했습니다.


3️⃣ Android Studio에서 가상 디바이스 생성

  • Android Studio를 실행한 후 Projects > More Actions > Virtual Device Manager로 이동합니다.
  • 상단의 Create Device (+버튼) 을 클릭합니다.
  • 원하는 디바이스 (예: Phone > Pixel 5)를 선택하거나 New Hardware Profile을 통해 새 디바이스를 생성합니다.

좌측 상단 + 버튼 클릭
New Hardware Profile 클릭

반응형

4️⃣ 스킨 설정 적용하기

 

Galaxy S | Samsung Developer

The world runs on you.

developer.samsung.com

 

 


5️⃣ 시스템 이미지 선택 및 AVD 생성 완료

  • 원하는 Android API 버전의 시스템 이미지를 선택하고 다운로드합니다.
  • 이름과 설정을 확인한 후 Finish를 클릭해 AVD를 생성합니다.


6️⃣ 에뮬레이터 실행 및 확인

  • 생성한 갤럭시 에뮬레이터 옆의 ▶ 아이콘을 클릭하여 실행합니다.
  • 실제 갤럭시 디바이스처럼 UI 프레임이 보이며 실행되는 것을 확인할 수 있습니다.


🏁 마무리

이제 Android Studio에서도 삼성 갤럭시 디바이스와 유사한 환경에서 앱을 테스트할 수 있게 되었습니다. 특히 국내 사용자 비중이 높은 갤럭시 스마트폰을 기준으로 테스트하면 더 안정적인 품질 확보가 가능하겠죠.

이 과정을 잘 따라오셨다면, 갤럭시 에뮬레이터를 여러 기종으로 추가해가며 다양한 해상도 및 성능 조건도 함께 점검해 보세요! 🚀

반응형

+ Recent posts