개발

ChatGPT 로 React Native 앱 만들기

TL;DR

  • ChatGPT 만을 이용해서 React Native 프로젝트 세팅부터 UI 코드 작성까지 진행해봤다.
  • 몇번의 과정을 거치니 꽤 그럴듯한 결과물을 얻었다.
  • 코드는 https://github.com/colonist4/RemoteAirconGPT 에서 볼 수 있다.

Outline

ChatGPT 만을 사용해서 React Native 앱을 만든 기록을 공유한다.

Why?

왜 이런 프로젝트를 시작했나. 나는 지금 "인코딩플러스" 라는 코딩 학원에서 일 하고 있다. 정규직은 아니고, 이전에 해놨던 일이 있어서 기존 구성원들과 매주 1회씩 정기 회의 정도만 하고 있다.

이 학원의 주요 과목은 중학생 코딩 포트폴리오 (실적물) 제작인데, 최근 좋은 코딩 강사를 구하기가 어려워 기존에 수학, 과학을 가르치던 선생님이 이 수업을 담당해야 하는 일이 생겼다.

앞으로도 좋은 코딩 강사를 구하기는 어렵다는 판단 하에 포트폴리오 제작 수업을 ChatGPT 를 적극 활용하는 건 어떻겠냐 라는 아이디어를 냈고, 이를 실험해보기 위해 우리가 직접 ChatGPT 로 프로젝트를 만들어보기로 했다.

Rule

이번 프로젝트의 주요 룰은 아래와 같다.

  • 구글 검색 금지.
  • 언어 문법, 프로젝트 세팅, 각종 코드들 ChatGPT 에게만 물어보기.
  • 파파고 번역기 사용 가능.
  • 프로젝트 아이디에이션 등 ChatGPT 에게 물어볼 수 있음.

주제

그래서 내가 만들기로 한 앱은 "에어컨 원격 조종 앱" 이다. 사실 이 주제는 아두이노 프로젝트로 만들려 정한 것인데, 내가 React Native 로 앱을 짜보고 싶어서 그냥 RN 으로 짜려고 한다 ㅎㅎ.

그러다보니 실제로 에어컨을 조종하는건 안되고, (적외선 송신기가 없기에) UI 수준에서 머물 것 같다. 혹은 구글 홈 등을 연동해 조종하는건 가능할 수도?

계획

아래 계획을 따라 앱을 만들어 볼 계획이다.

  1. GPT 와 대화하며 프로젝트 아이디어 디벨롭. 1 . 주요 기능, UI 구성 요소, 등등..
  2. RN 프로젝트 세팅
  3. Boiler plate 코드 생성
  4. 메인 UI 구성 요소 구체화.
  5. 메인 UI 코드 작성
  6. 메인 UI 에서 사용될 더미 데이터 생성
  7. UI에 더미 데이터 적용

GPT 와 대화하며 프로젝트 아이디어 디벨롭.

[Me]
에어컨 원격조종 프로젝트를 리액트 네이티브로 만드려고 하는데, 프로젝트의 아이디에이션을 같이 해줘.

[GPT]
에어컨 원격 조정 프로젝트를 위한 리액트 네이티브 애플리케이션 아이디에이션을 시작해보겠습니다. 다음은 프로젝트 구상 단계에서 고려해볼 수 있는 몇 가지 요소입니다:

1. **사용자 인터페이스(UI) 디자인**:
   - **메인 화면**: 현재 온도, 설정 온도, 에어컨의 상태(켜짐/꺼짐)를 보여주는 대시보드.
   - **온도 조절**: 슬라이더 또는 '+' 및 '-' 버튼을 사용하여 온도를 조절할 수 있는 인터페이스.
   - **모드 선택**: 냉방, 난방, 제습, 자동 등 다양한 운전 모드를 선택할 수 있는 옵션.

2. **기능성**:
   - **스마트 스케줄링**: 사용자가 특정 시간에 에어컨을 자동으로 켜거나 끌 수 있도록 하는 스케줄러.
   - **에너지 절약 모드**: 에너지 사용을 최소화하는 설정을 제안하고, 사용자가 쉽게 활성화할 수 있게 함.
   - **실내 공기 질 모니터링**: 연결된 센서를 통해 실내 공기 질을 모니터링하고, 필터 교체 시기 등을 알려주는 기능.

3. **사용자 경험(UX)**:
   - **간편한 설치 과정**: 최초 앱 사용 시 에어컨과의 페어링을 쉽게 할 수 있는 가이드 제공.
   - **커스터마이징**: 사용자가 자주 사용하는 설정을 즐겨찾기로 저장하거나 빠른 접근을 위한 위젯 제공.
   - **멀티 디바이스 지원**: 한 앱으로 여러 에어컨을 제어할 수 있는 기능.

4. **기술적 요소**:
   - **통신 프로토콜**: 에어컨과 앱 사이의 통신을 위해 Bluetooth, Wi-Fi, IR(Infrared) 등의 프로토콜 사용.
   - **보안**: 사용자의 데이터와 에어컨 제어 명령이 안전하게 전송되도록 암호화 기술 적용.
   - **백엔드 통합**: 사용자 설정을 저장하고, 에어컨의 상태를 실시간으로 동기화하기 위한 서버 구축.

5. **확장성**:
   - **스마트홈 통합**: 애플리케이션을 Google Home, Amazon Alexa 같은 스마트홈 시스템과 연동할 수 있게 구상.
   - **사용자 피드백**: 사용자 피드백을 수집하고 업데이트에 반영할 수 있는 시스템 구축.

이러한 요소들을 바탕으로 프로토타입을 제작하고, 사용자 테스트를 통해 개선점을 찾아나가는 것이 좋습니다. 프로젝트의 성공은 사용자 경험의 우수성에 크게 의존하므로, 사용자의 편의성과 만족도를 최우선으로 고려해야 합니다.

[Me]
좋아! 시작은 UI 구현 부터 하면 될 것 같아. UI 의 상세 구성 요소로 뭐가 들어가야 해?

[GPT]
리액트 네이티브를 이용한 에어컨 원격 조정 앱의 UI를 구현할 때 고려해야 할 상세 구성 요소는 다음과 같습니다:

1. **상태 표시**:
   - 온도 표시: 현재 실내 온도와 설정 온도를 크고 명확한 숫자로 표시.
   - 에어컨 상태 아이콘: 에어컨이 켜져 있는지, 꺼져 있는지를 나타내는 아이콘 또는 표시.

2. **제어 버튼 및 슬라이더**:
   - 온도 조절 버튼: 온도를 조절하기 위한 ‘+’와 ‘-’ 버튼.
   - 온도 조절 슬라이더: 드래그하여 온도를 조절할 수 있는 슬라이더.
   - 모드 전환 버튼: 냉방, 난방, 제습 등 다양한 모드를 선택할 수 있는 버튼 또는 토글 스위치.
   - 팬 속도 조절: 팬의 속도를 조절할 수 있는 버튼 또는 슬라이더.

3. **메뉴 및 설정**:
   - 설정 메뉴: 사용자 설정, 스케줄 설정, 에너지 절약 모드 등을 관리할 수 있는 메뉴.
   - 사용자 커스터마이징: 자주 사용하는 온도 설정을 저장하고 빠르게 접근할 수 있게 하는 ‘즐겨찾기’ 기능.
   - 멀티 디바이스 관리: 여러 에어컨을 추가하고 관리할 수 있는 인터페이스.

4. **피드백 및 알림**:
   - 상태 알림: 설정 변경이나 에어컨의 작동 상태 변경 시 사용자에게 피드백을 주는 알림.
   - 에러 메시지: 에어컨과의 연결 오류나 기타 문제가 발생했을 때 사용자에게 안내하는 메시지.

5. **도움말 및 지원**:
   - 사용자 가이드: 앱의 기능과 사용 방법에 대한 도움말 섹션.
   - 고객 지원: 문제 발생 시 연락할 수 있는 고객 지원 연결 옵션.

6. **추가 기능**:
   - 실내 공기 질 표시: 연결된 센서를 통해 실내 공기 질을 표시하는 기능.
   - 에너지 사용량 통계: 에어컨의 에너지 사용량을 시간, 일, 월 단위로 표시하는 그래프 또는 차트.

이러한 구성 요소들은 사용자의 편의성과 직관적인 조작을 위해 중요하며, 실제 앱을 디자인하고 개발할 때 필요에 따라 조정하거
나 추가할 수 있습니다. UI/UX 디자인 원칙을 따라 사용자의 행동 패턴과 요구를 고려하여 설계하는 것이 중요합니다.

UI 구성 요소에 대해 아이디에이션을 진행했고 상태 표시, 제어 버튼 및 슬라이더 를 메인 UI 에 넣기로 결정했다.

RN 프로젝트 세팅

[Me]
How can i set React Native project using expo? I already installed vscode, android studio, xcode.

[GPT]
Setting up a React Native project using Expo is a streamlined process because Expo provides a set of tools and services for React Native that simplify the development process. Here's a step-by-step guide on how to do it:
(중략...)
> npm install -g expo-cli
(중략...)
> expo init MyNewProject
(중략...)
> expo start

메인 UI 코드 작성

[Me]
Please write React Native code that represent UI for remote air conditioner controller. UI is composed of 1. Status Indicators: - Temperature display: Present the current indoor temperature and the set temperature with large, clear numbers. - Air conditioner status icon: An icon or indicator to show whether the air conditioner is turned on or off. 2. Control Buttons and Sliders: - Temperature adjustment buttons: ‘+’ and ‘-’ buttons to adjust the temperature. - Temperature control slider: A slider that allows the temperature to be adjusted by dragging. - Mode switch buttons: Buttons or toggle switches to select different modes like cooling, heating, dehumidifying, etc. - Fan speed control: Buttons or a slider to adjust the fan speed. Please write React Native UI code following apple apps design protocol.

[GPT]
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Slider } from 'react-native';
import { FontAwesome5 } from '@expo/vector-icons';

const App = () => {
  const [isOn, setIsOn] = useState(false);
  const [currentTemp, setCurrentTemp] = useState(24);
  const [setTemp, setSetTemp] = useState(24);
  const [mode, setMode] = useState('cooling');
  const [fanSpeed, setFanSpeed] = useState(1);

  const modes = ['cooling', 'heating', 'dehumidify'];
  const fanSpeeds = [1, 2, 3];

  const increaseTemp = () => setSetTemp(prevTemp => prevTemp + 1);
  const decreaseTemp = () => setSetTemp(prevTemp => prevTemp - 1);

  return (
    <View style={styles.container}>
      <View style={styles.status}>
        <Text style={styles.tempDisplay}>{`${currentTemp}°C`}</Text>
        <TouchableOpacity onPress={() => setIsOn(!isOn)}>
          <FontAwesome5 
            name={isOn ? 'snowflake' : 'power-off'} 
            size={24} 
            color={isOn ? 'blue' : 'gray'} 
          />
        </TouchableOpacity>
        <Text style={styles.setTempDisplay}>{`${setTemp}°C`}</Text>
      </View>

      <View style={styles.controls}>
        <TouchableOpacity style={styles.tempButton} onPress={increaseTemp}>
          <Text style={styles.tempButtonText}>+</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.tempButton} onPress={decreaseTemp}>
          <Text style={styles.tempButtonText}>-</Text>
        </TouchableOpacity>

        <Slider
          style={styles.slider}
          minimumValue={16}
          maximumValue={30}
          step={1}
          value={setTemp}
          onValueChange={value => setSetTemp(value)}
        />

        <View style={styles.modeSwitch}>
          {modes.map((item, index) => (
            <TouchableOpacity 
              key={index} 
              style={[styles.modeButton, mode === item ? styles.selectedMode : null]}
              onPress={() => setMode(item)}
            >
              <Text style={styles.modeText}>{item}</Text>
            </TouchableOpacity>
          ))}
        </View>

        <View style={styles.fanSpeedControl}>
          {fanSpeeds.map((speed, index) => (
            <TouchableOpacity 
              key={index} 
              style={[styles.fanSpeedButton, fanSpeed === speed ? styles.selectedFanSpeed : null]}
              onPress={() => setFanSpeed(speed)}
            >
              <Text style={styles.fanSpeedText}>{`Speed ${speed}`}</Text>
            </TouchableOpacity>
          ))}
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
    backgroundColor: '#f5f5f7',
  },
  status: {
    alignItems: 'center',
    marginBottom: 20,
  },
  tempDisplay: {
    fontSize: 48,
    fontWeight: 'bold',
    color: 'black',
  },
  setTempDisplay: {
    fontSize: 32,
    fontWeight: 'bold',
    color: 'gray',
  },
  controls: {
    width: '100%',
    alignItems: 'center',
  },
  tempButton: {
    backgroundColor: 'lightgray',
    padding: 10,
    borderRadius: 10,
    marginVertical: 10,
  },
  tempButtonText: {
    fontSize: 24,
    color: 'black',
  },
  slider: {
    width: '100%',
    height: 40,
    marginVertical: 20,
  },
  modeSwitch: {
    flexDirection: 'row',
    marginVertical: 10,
  },
  modeButton: {
    backgroundColor: 'lightgray',
    padding: 10,
    borderRadius: 10,
    marginHorizontal: 5,
  },
  selectedMode: {
    backgroundColor: 'blue',
  },
  modeText: {
    color: 'white',
  },
  fanSpeedControl: {
    flexDirection: 'row',
    marginVertical: 10,
  },
  fanSpeedButton: {
    backgroundColor: 'lightgray',
    padding: 10,
    borderRadius: 10,
    marginHorizontal: 5,
  },
  selectedFanSpeed: {
    backgroundColor: 'blue',
  },
  fanSpeedText: {
    color: 'white',
  },
});

export default App;

Bug fix

위 코드를 복붙해서 실행하니 아래와 같은 에러가 떴다. pasted-image-20231109231813.png

물론, 동일하게 GPT 에게 물어봤다.

[Me]
There an error "Slider has been removed from react-native core. It can now be installed and imported form '@react-native-community/slider' instead of 'react-native'" How can i fix it?

[GPT]
To fix this error, you'll need to install the `@react-native-community/slider` package, since the `Slider` component has been removed from the core React Native package. Here's how you can update the code:

(중략...)

> npm install @react-native-community/slider

중간 결과

pasted-image-20231109232428.png

GPT 가 생성한 것 치곤 나쁘지 않은 UI 이나 아래 항목들을 수정하면 좋겠다 싶었다.

  1. 전원 버튼을 맨 위에 배치.
  2. +, - 버튼을 타겟 온도 좌우에 배치.
  3. Slider 삭제.
  4. cooling, heating, dehumidify 에 아이콘 추가.
  5. Speed 1, Speed 2, Speed 3 를 아이콘으로 변경.

수정 시작

  1. (전원 버튼을 맨 위에 배치)
[Me]
Please place power button at the upper of current temperature. Write code only this part.

[GPT]
(중략..)

<View style={styles.status}>
  <TouchableOpacity style={styles.powerButton} onPress={() => setIsOn(!isOn)}>
    <FontAwesome5 
      name={isOn ? 'snowflake' : 'power-off'} 
      size={24} 
      color={isOn ? 'blue' : 'gray'} 
    />
  </TouchableOpacity>
  <Text style={styles.tempDisplay}>{`${currentTemp}°C`}</Text>
  <Text style={styles.setTempDisplay}>{`${setTemp}°C`}</Text>
</View>

(중략..)
  1. +, - 버튼을 타겟 온도 좌우에 배치
Please move +, - button to left and right of target temperature. Write code only this part.
  1. Slider 제거
[Me]
I want to remove slider what code should i remove? Write code only this part.

[GPT]
// ... (other code remains unchanged)

<TouchableOpacity style={styles.tempButton} onPress={increaseTemp}>
  <Text style={styles.tempButtonText}>+</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.tempButton} onPress={decreaseTemp}>
  <Text style={styles.tempButtonText}>-</Text>
</TouchableOpacity>

{/* The Slider component has been removed here */}

<View style={styles.modeSwitch}>
  {modes.map((item, index) => (
    <TouchableOpacity 
      key={index} 
      style={[styles.modeButton, mode === item ? styles.selectedMode : null]}
      onPress={() => setMode(item)}
    >
      <Text style={styles.modeText}>{item}</Text>
    </TouchableOpacity>
  ))}
</View>

// ... (other code remains unchanged)
  1. cooling, heating, dehumidify 에 아이콘 추가.
[Me]
Please add appropriate icon to cooling, heating, dehumidify buttons. Write code only this part.

[GPT]

<View style={styles.modeSwitch}>
  <TouchableOpacity 
    style={[styles.modeButton, mode === 'cooling' ? styles.selectedMode : null]}
    onPress={() => setMode('cooling')}
  >
    <FontAwesome5 name="snowflake" size={18} color={mode === 'cooling' ? 'white' : 'black'} />
    <Text style={[styles.modeText, mode === 'cooling' ? styles.modeTextSelected : null]}>Cooling</Text>
  </TouchableOpacity>

  <TouchableOpacity 
    style={[styles.modeButton, mode === 'heating' ? styles.selectedMode : null]}
    onPress={() => setMode('heating')}
  >
    <FontAwesome5 name="fire" size={18} color={mode === 'heating' ? 'white' : 'black'} />
    <Text style={[styles.modeText, mode === 'heating' ? styles.modeTextSelected : null]}>Heating</Text>
  </TouchableOpacity>

  <TouchableOpacity 
    style={[styles.modeButton, mode === 'dehumidify' ? styles.selectedMode : null]}
    onPress={() => setMode('dehumidify')}
  >
    <FontAwesome5 name="water" size={18} color={mode === 'dehumidify' ? 'white' : 'black'} />
    <Text style={[styles.modeText, mode === 'dehumidify' ? styles.modeTextSelected : null]}>Dehumidify</Text>
  </TouchableOpacity>
</View>


styles

modeText: {
  marginLeft: 5,
  color: 'black',
},
modeTextSelected: {
  color: 'white',
},

  1. Speed 1, Speed 2, Speed 3 를 아이콘으로 변경.
[Me]
Change "Speed 1", "Speed 2", "Speed 3" texts in button into appropriate icons. Write code only this part.

최종 결과

여러번의 수정을 더해 아래 결과를 얻어냈다.

pasted-image-20231110000422.png

GPT 만으로 코드를 짜는 것도 꽤 괜찮은 것 같다.

부록

Minimalism 디자인으로 스타일 변경 프롬프트

전반적인 스타일 변경 요청

I will give you React Native code. You have to rewrite styles into Minimalism style.

특정 컴포넌트 스타일 변경 요청

I don't like button's design, please change these.