스파르타코딩클럽 내일배움단 앱개발 종합반 5주차 개발일지

Bluewiz_YSH·2022년 5월 31일
0

1. 5주차 배울 내용

1주차 기초 문법부터 4주차 앱과 서버 연결까지 강사님은 이제 필요한 앱의 모든 기능들을 직접 다루고 구현하는 연습을 완료했다고 하셨다. 이제 이 만든 앱 결과물을 수익성이 있는 광고와 함께 직접 앱 마켓에 올려서 배포까지 완료해보는 작업만 남았다고 하셨다. 정리하자면 아래 내용과 같다.

필요한 앱 기능이 모두 갖춰졌습니다
열심히 만든 앱인 만큼, 수익성도 갖추고 배포가 잘되면 완벽한 마무리가 될 것 같습니다.

1) 수익성 앱: 앱에서 수익을 내는 방법
2) 구글광고,애드몹: 구글을 광고 플랫폼을 이용한 수익창출
3) 배포하기: 리액트 네이티브&Expo 앱 쉽게 배포하기


2. [수익형 앱]앱에서 수익을 내는 방법

먼저 강사님은 앱이 어떻게 수익을 벌어들이는지에 대한 구조들을 유형으로 묶은 정리 내용을 아래처럼 보여주셨다.

  • 앱 마켓에 유료앱 배포 수익 모델
  • 앱 내 배너 광고 수익 모델
  • 앱 콘텐츠 판매 수익 모델: 인 앱 결제
  • 구독 수익 모델
  • 앱 개발 용역 수익 모델
  • 외부 브랜드 광고 수익 모델

그 중에서 강사님이 우리는 초보자가 해볼수 있는 두번째, 앱 내 버내 광고 수익 모델을 한번 해본다고 하셨다. 배너 수익 방법도 아래 3가지 방식으로 나뉜다고 하셨다.

  1. 배너 클릭
  2. 배너 광고 시청
  3. 배너 광고 사용자 참여

3. [구글광고] 애드몹(AdMob) 배너 추가

그럼 본격적으로 구글 애드몹을 이용해 앱 광고를 다는 연습을 강사님과 함께 해보았다.

(아래는 구글 애드몹 광고 유형들이다.)

이 중 우리는 배너, 전면 두개를 다뤄본다고 하셨다.

expo에서도 이 구글 애드몹을 지원하기에 아래와 같은 코드를 앱이 있는 터미널에 입력하면 라이브러리 설치가 일단은 완료된다. (공식 설명 링크)

expo install expo-ads-admob

또한, 애드몹은 아래 사진처럼 웹에서 사용이 불가능하다고 알려져있다.

설치가 완료되면 app.json 파일을 열어 아래 코드를 "ios", "android" 항목에 맞춰 수정하면 된다.

"ios": {
      "supportsTablet": true,
      "buildNumber": "1.0.0",
      "bundleIdentifier": "",
      "config": {
        "googleMobileAdsAppId": ""
      }
    },
    "android": {
          "package": "",
          "versionCode": 1,
          "config": {
            "googleMobileAdsAppId": ""
          }
  },

다만 여기서 강사님은 ios의 bundleIdentifier와 googleMobileAdsAppId 값, android의 package와 googleMobileAdsAppId 값은 각각 사람마다 다르기에 이 값은 자기가 직접 찾아서 넣거나 수정해주셔야 한다고 하셨다.(이 중 googleMobileAdsAppId는 구글 애드몹을 쓰기 위한 키 값이라고 보면 된다. 파이어베이스의 apikey, firebaseConfig.js 설정값과 같은 맥락이다.)

(ios의 bundleIdentifier와 android의 package 값은 com.company.appname 형식으로 ios 앱스토어나 구글 플레이 스토어에 앱 이름을 알려주는 역할을 한다.)

그렇게 app.json 파일을 이대로 저장하고 구글 애드몹 가로 배너를 설정하는 법을 강사님이 알려주셨다.

아래 사진처럼 구글 애드몹에 들어가서 광고를 달고자 하는 앱을 왼쪽에서 클릭 후 중앙 상단에 파란 버튼 광고 단위 추가 버튼을 누르라고 하셨다. 그 뒤 아래 사진 순서에 따라 진행하면 된다.

그러면 위 사진과 같이 앱 ID 키 값과 광고 단위 ID 키 값이 나오는데 첫번째 키 값은 app.json에서 ios, android 각 플랫폼에 필요한 googleMobileAdsAppId 키 값이고 두번째 키 값은 앱 화면 본체인 MainPage.js에 구글 애드몹 가로배너를 적용할때 쓰는 키 값이라고 강사님이 말씀하셨다.

ios, android 둘 다 똑같이 광고단위 추가해주고, 강사님 지시로 아래 코드단으로 MainPage.js를 수정해주었다.

import React,{useState,useEffect} from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView, PlatForm} from 'react-native';
import data from '../data.json';
import Card from '../components/Card';
import Loading from '../components/Loading';
import { StatusBar } from 'expo-status-bar';
import * as Location from "expo-location";
import axios from "axios"
import {firebase_db} from "../firebaseConfig"

import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
} from 'expo-ads-admob';

export default function MainPage({navigation,route}) {
  console.disableYellowBox = true;
  //return 구문 밖에서는 슬래시 두개 방식으로 주석

  //기존 꿀팁을 저장하고 있을 상태
  const [state,setState] = useState([])
  //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태
  const [cateState,setCateState] = useState([])

  //날씨 데이터 상태관리 상태 생성!
  const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
  })

  //컴포넌트에 상태를 여러개 만들어도 됨
  //관리할 상태이름과 함수는 자유자재로 정의할 수 있음
  //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음.
  const [ready,setReady] = useState(true)

  useEffect(()=>{
	   
    //뒤의 1000 숫자는 1초를 뜻함
    //1초 뒤에 실행되는 코드들이 담겨 있는 함수
    setTimeout(()=>{
        //헤더의 타이틀 변경
        navigation.setOptions({
            title:'나만의 꿀팁'
        })
        firebase_db.ref('/tip').once('value').then((snapshot) => {
          console.log("파이어베이스에서 데이터 가져왔습니다!!")
          let tip = snapshot.val();
          setState(tip)
          setCateState(tip)
          getLocation()
          setReady(false)
        });
        // setTimeout(()=>{
        //     let tip = data.tip;
        //     setState(tip)
        //     setCateState(tip)
        //     getLocation()
        //     setReady(false)
        // },500)
    },1000)

    
  },[])

  const getLocation = async () => {
    //수많은 로직중에 에러가 발생하면
    //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
    try {
      //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
      await Location.requestPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      const latitude = locationData['coords']['latitude']
      const longitude = locationData['coords']['longitude']
      const API_KEY = "api 마스터 키";
      const result = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );

      const temp = result.data.main.temp; 
      const condition = result.data.weather[0].main
      
      console.log(temp)
      console.log(condition)

      //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
      //잘 기억이 안난다면 1주차 강의 6-5를 다시 복습해보세요!
      setWeather({
        temp,condition
      })


    } catch (error) {
      //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
    }
  }

    const category = (cate) => {
        if(cate == "전체보기"){
            //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
            setCateState(state)
        }else{
            setCateState(state.filter((d)=>{
                return d.category == cate
            }))
        }
    }


  //실제 데이터를 넣을 예정이므로 주석!
	// let todayWeather = 10 + 17;
  // let todayCondition = "흐림"

	//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨
  //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)
  return ready ? <Loading/> :  (
    /*
      return 구문 안에서는 {슬래시 + * 방식으로 주석
    */
    <ScrollView style={styles.container}>
        <StatusBar style="black" />
        {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
        <Text style={styles.weather}>오늘의 날씨: {weather.temp + '°C   ' + weather.condition} </Text>
        <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
          <Text style={styles.aboutButtonText}>소개 페이지</Text>
        </TouchableOpacity>
        <Image style={styles.mainImage} source={main}/>
        <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
            <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
            <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
            <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
            <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
            <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
        </ScrollView>
        <View style={styles.cardContainer}>
            {/* 하나의 카드 영역을 나타내는 View */}
            {
            cateState.map((content,i)=>{
                return (<Card content={content} key={i} navigation={navigation}/>)
            })
            }
            {Platform.OS === 'ios' ? (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="플랫폼 별 광고 배너 단위 ID 키 값"
                  style={styles.banner}
                />
            ) : (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="플랫폼 별 광고 배너 단위 ID 키 값"
                  style={styles.banner}
                />
            )}
        </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    //앱의 배경 색
    backgroundColor: '#fff',
  },
  title: {
    //폰트 사이즈
    fontSize: 20,
    //폰트 두께
    fontWeight: '700',
    //위 공간으로 부터 이격
    marginTop:50,
    //왼쪽 공간으로 부터 이격
    marginLeft:20
  },
weather:{
    alignSelf:"flex-end",
    paddingRight:20
  },
  mainImage: {
    //컨텐츠의 넓이 값
    width:'90%',
    //컨텐츠의 높이 값
    height:200,
    //컨텐츠의 모서리 구부리기
    borderRadius:10,
    marginTop:20,
    //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
    //각 속성의 값들은 공식문서에 고대로~ 나와 있음
    alignSelf:"center"
  },
  middleContainer:{
    marginTop:20,
    marginLeft:10,
    height:60
  },
  middleButtonAll: {
    width:100,
    height:50,
    padding:15,
    backgroundColor:"#20b2aa",
    borderColor:"deeppink",
    borderRadius:15,
    margin:7
  },
  middleButton01: {
    width:100,
    height:50,
    padding:15,
    backgroundColor:"#fdc453",
    borderColor:"deeppink",
    borderRadius:15,
    margin:7
  },
  middleButton02: {
    width:100,
    height:50,
    padding:15,
    backgroundColor:"#fe8d6f",
    borderRadius:15,
    margin:7
  },
  middleButton03: {
    width:100,
    height:50,
    padding:15,
    backgroundColor:"#9adbc5",
    borderRadius:15,
    margin:7
  },
  middleButton04: {
    width:100,
    height:50,
    padding:15,
    backgroundColor:"#f886a8",
    borderRadius:15,
    margin:7
  },
  middleButtonText: {
    color:"#fff",
    fontWeight:"700",
    //텍스트의 현재 위치에서의 정렬 
    textAlign:"center"
  },
  middleButtonTextAll: {
    color:"#fff",
    fontWeight:"700",
    //텍스트의 현재 위치에서의 정렬 
    textAlign:"center"
  },
  cardContainer: {
    marginTop:10,
    marginLeft:10
  },
  aboutButton: {
    backgroundColor:"pink",
    width:100,
    height:40,
    borderRadius:10,
    alignSelf:"flex-end",
    marginRight:20,
    marginTop:10
  },
  aboutButtonText: {
    color:"#fff",
    textAlign:"center",
    marginTop:10
  }


});

새롭게 추가된 점은 import문에 expo-ads-admob에서 임포트해온 수많은 기능들이 있는데, 이 중에서 우리가 쓰는 가로배너용 기능은 adMobBanner이며, 광고 배너를 넣기 위한 코드문은 카드의 내용물을 구현하는 map 반복문 코드단 바로 아래, Platform.OS로 시작하고 있다.(카드 View 태그 안에 존재)

하나 하나 천천히 보면, Platform.OS == ios ? () : ()로 삼항연산자를 통해 사용자가 접속한 플랫폼이 ios인지 아닌지 판단후 ios면 처음 괄호를 실행하고 아니라면 (android라면) 두번째 괄호를 실행하는 구조로 되어있었다. 또한, 중간에 보면 사용자별/플랫폼별 다른 광고단위 마다 다른 값이 존재하는 adUnitID에다가 해당 광고단위에 접속할수 있게 해주는 키 값을 넣어줘야 한다. (아까 우리가 했었던 광고 단위 추가 마지막 장면에서 두번째 키 값)

그리고 bannerSize 속성값에 fullBanner를 줘 배너크기가 모두 나오게끔 하고 servePersonalizedAds에 true 값을 줘 우리가 평소에 다니는 사이트 기록을 보고 광고를 그에 맞게 맞춤으로 보여주도록 하고 있다. 또한 마지막에 style 값을 가지고 있어 따로 css 디자인 스타일을 줄수도 있다.

이 모든걸 하고 앱 화면을 스마트폰에서 실행해보면 아래 그림처럼 나온다.

이렇게 메인페이지에 하단 가로 배너 광고를 달았다면, 이제 맘에 드는 팁을 눌렀을때 디테일 페이지로 넘어가기전에 전면으로 광고를 보여주는 기능을 강사님하고 함께 구현했다. 순서를 정리하자면 아래와 같다.

1) 메인페이지에서 꿀팁을 하나 누르면
2) 전면 광고가 뜨고
3) 전면 광고 노출 일정시간 후
4) 디테일 페이지로 이동!

그래서 일단 구글애드몹에 다시 가서 아까 가로 배너처럼 아래 사진처럼 전면 광고 단위를 추가했다.

그리고 팁들을 구현하는 코드가 담긴 Card.js로 넘어가서 아래와 같은 코드로 수정했다. (코드에서 전면광고의 이름은 interstitial이었다.)

import React, { useEffect } from 'react';
import {View, Image, Text, StyleSheet,TouchableOpacity,Platform} from 'react-native'
import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
} from 'expo-ads-admob';

//MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
export default function Card({content,navigation}){

    useEffect(()=>{
        // Card.js에 들어오자마자 전면 광고 준비하느라 useEffect에 설정
        //애드몹도 외부 API 이므로 실행 순서를 지키기위해 async/await 사용!
        //안드로이드와 IOS 각각 광고 준비 키가 다르기 때문에 디바이스 성격에 따라 다르게 초기화 시켜줘야 합니다.
        Platform.OS === 'ios' ? AdMobInterstitial.setAdUnitID("ios 전면광고 광고단위 키 값") : AdMobInterstitial.setAdUnitID("android 전면광고 광고단위 키 값")

        AdMobInterstitial.addEventListener("interstitialDidLoad", () =>
            console.log("interstitialDidLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidFailToLoad", () =>
            console.log("interstitialDidFailToLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidOpen", () =>
            console.log("interstitialDidOpen")
        );
        AdMobInterstitial.addEventListener("interstitialDidClose", () => {
              //광고가 끝나면 다음 코드 줄이 실행!
            console.log("interstitialDidClose")
          
        });
    },[])
    const goDetail = async () =>{
      try {
        await AdMobInterstitial.requestAdAsync({ servePersonalizedAds: true});
        await AdMobInterstitial.showAdAsync();
        await navigation.navigate('DetailPage',{idx:content.idx})
      } catch (error) {
        console.log(error)
        await navigation.navigate('DetailPage',{idx:content.idx})
      }
    }

    return(
        //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
        <TouchableOpacity style={styles.card} onPress={()=>{goDetail()}}>
            <Image style={styles.cardImage} source={{uri:content.image}}/>
            <View style={styles.cardText}>
                <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                <Text style={styles.cardDate}>{content.date}</Text>
            </View>
        </TouchableOpacity>
    )
}


const styles = StyleSheet.create({
    
    card:{
      flex:1,
      flexDirection:"row",
      margin:10,
      borderBottomWidth:0.5,
      borderBottomColor:"#eee",
      paddingBottom:10
    },
    cardImage: {
      flex:1,
      width:100,
      height:100,
      borderRadius:10,
    },
    cardText: {
      flex:2,
      flexDirection:"column",
      marginLeft:10,
    },
    cardTitle: {
      fontSize:20,
      fontWeight:"700"
    },
    cardDesc: {
      fontSize:15
    },
    cardDate: {
      fontSize:10,
      color:"#A6A6A6",
    }
});

임포트문에는 아까와 같이 expo-ads-admob에서 끌어온 광고 기능들이 임포트 되어있는데, 이번 전면광고에서는 adMobInterestitial을 사용한다. 그리고 Card.js가 로딩되자마자 전면 광고가 준비되어야 하기 때문에 useEffect 함수에 또 아까와 같이 Platform.os = ios ? 삼항 연산자가 들어가있다. 그 부분을 더 자세히 보면, AdMobInterstitial.setAdUnitID에 각각 ios android 플랫폼에 맞춰서 아까 생성한 광고단위 키 값을 넣어주면 되고

그 바로 아래 interestitialDidLoad/FailToLoad/Open/Close는 광고가 로딩후/로딩에 에러 실패후/열린후/닫힌후 어떤것이 실행되어야 하는지 적는 코드단이고 우리는 광고가 닫힌후 DetailPage로 이동해야 하니까 interestitialDidClose 단에 navigation.navigate('DetailPage',{idx:content.idx})을 넣어주어 네비게이션 기능으로 광고가 끝나면 DetailPage로 이동하게끔 해놓았다.

(기존에는 이런식으로 했지만 2022-05-29 수강하는 현재 시점으로 애드몹 함수 업데이트로 인해 자꾸 피자 팁만 나오는 오류가 있고 자꾸 ad is already loaded 오류도 있어서 goDetail 함수 안에 navigation.navigate('DetailPage',{idx:content.idx})를 try-catch문 안에 함께 넣었다.)

그럼 마지막으로 광고를 실행하는 트리거는 return 첫 줄 TouchAbleOpacity 태그 onPress 속성값에서 goDetail 함수가 걸려있는걸 보면 된다. goDetail 함수에 가보면 interestitial 기능에 requestAdAsync({ servePersonalizedAds: true}) 값을 주어 개인 맞춤형 광고가 표시되도록 해놓았고 showAdAsync(); 값을 주어 카드를 누르면 전면광고가 실행되도록 해놓은 것을 알수 있다. 또한 이 모든게 순서대로 실행되어야 하기에 async, await까지 붙여주었다.

그럼 아래 그림처럼 스마트폰에서 앱 화면을 실행후 팁 카드를 눌렀을때 전면광고가 실행되는것을 확인할수 있다.


4. [배포하기] 배포를 위한 스플래시 스크린, 로고 준비, 배포 준비 : 최종 앱 파일 생성 및 개발자 가입, 안드로이드 & iOS(자료참고) 배포

강사님은 이제 배포하는것만 남았다며 원래 배포까지 하려면 별도의 수고와 시간이 들어가는데 다행히 expo에서는 배포 그리고 수정후 재배포 기능까지 지원하고 있어 다른 방법보다 조금은 쉽다고 하셨다.

그리고 강사님은 앱 배포전에 아래 항목과 같은 것들이 필요하다고 하시면서 흔히 앱 마켓에 올라와 있는 앱 상세페이지 사진을 보여주셨다.

앱을 배포하기 위해서는 필요한 것들이 있습니다.

1) 앱 로고
2) 스플래시 스크린(앱 시작 초기 화면)
3) 앱 마켓에 올릴 설명 이미지

이렇게 직접 준비해야하는 것들과
Expo의 도움을 빌려 쉽게 준비 가능한
앱 버전 관리, 안드로이드, iOS 인증서 관리 등이 있습니다.

스플래시 스크린은 우리가 항상 스마트폰에서 expo를 시험 테스트할때 나오는 그 하얀 배경화면이었다. 정확히는 앱을 켰을때 2~3초 정도 앱 로고나 인트로 페이지가 나오는 화면을 뜻한다고 하셨다.

먼저 로고와 스플래시 스크린부터 만들어보았는데 디자인 관련 툴이나 지식이 없더라고 쉽게 만드는 법을 강사님께서 알려주셨다. 아래 링크로 들어가서 온라인 포토샵을 이용하는거였다.

https://pixlr.com/kr/x/

열어서 아래 사진과 같이 이미지 열기 버튼을 누른 뒤 우리가 나만의 꿀팁 프로젝트를 저장했던 폴더에서 assets 폴더에 가서 expo에서 기본 제공하던 스플래시 스크린 이미지, splash.png를 열면 된다. (\sparta-study\sparta-myhoneytip-yun\assets)

그러면 아래와 같이 splash.png가 열린 화면이 뜰것이다.

그러면서 강사님은 각자 자기가 가지고 있는 이미지나 혹은 무료 이미지 사이트를 이용해서 한번 스플래시 스크린으로 이용할 이미지를 끌고 오라고 하셨다. (본인은 다음과 같은 이미지 링크를 사용했다.) 그리고 아래 사진과 같이 왼쪽 버튼 위에서부터 맨 첫번째 순서 버튼을 클릭 하면 이미지 조정이 가능해 이를 이용해 이미지 크기를 원래 이미지 사이즈까지 줄여서 아래와 같이 나왔다. (분홍색 십자선이 정중앙 표시다.)

그런뒤 저장을 하고 원래 splash.png가 있던 폴더에 저장한 뒤 원래 splash.png를 삭제 후 새롭게 만든 이미지 이름을 splash.png로 변경한다.

그러면 다시 expo를 실행시키고 qr코드로 스마트폰에서 실행하면 아래 그림과 같이 splash 스크린이 변경된 앱 화면이 구동된다.

그 다음 해볼건 로고였다. 이것도 똑같이 아까 쓴 온라인 포토샵을 이용하였다. 그리고 아까와 똑같은 경로, \sparta-study\sparta-myhoneytip-yun\assets에서 icon.png 파일을 열었다.(expo 기본 로고 이미지)

그런뒤 아까 썼던 스플래시 스크린 이미지를 똑같이 가져다 쓰는걸로 하고 복사해 붙여넣기 하면 아까와는 다르게 딱 규격에 맞게 이미지가 들어가는 모습을 아래 사진에서 볼 수 있다.

그러면 아까와 같이 저장을 하되, png 확장자로 저장을 하고 icon.png로 바꿔준다.

그런뒤 다시 expo를 실행후 스마트폰에서 expo 앱을 열어보면 아래 사진과 같이 앱을 실행하는 버튼 왼쪽 옆에 변경된 로고가 나온다. (2022 05 03 현재 엑스포 버전 버그 때문에 엑스포를 다시 실행해봐도 로고가 안 뜨는 상황이라서 아래 사진은 강사님이 만드신 화면을 가져왔다.)

그리고 아래 사진처럼 스플래시 스크린, 로고 이 모든게 app.json에서 관리하는 형태로 되어있어 몇가지 수정하고 싶은게 있다면 app.json에서 코드를 바꾸면 된다.

이렇게 로고, 스플래시 스크린도 준비되었기에 이제 남은건 배포만 남았다고 강사님이 말씀하셨다.

먼저 배포 과정이 쉬운 안드로이드부터 배포하기로 하였다. (배포 완료에 걸리는 시간, 라이센싱 비용 등 안드로이드가 ios보다 더 낫다.)

배포 진행 순서는 아래와 같았다.

1) Expo를 통한 최종 앱 파일 생성
2) 구글 플레이 개발자 라이센스 가입 및 구입
3) 구글 플레이 스토어에 앱 배포

일단 expo를 이용해서 최종 앱 파일을 생성해야했는데, 최종 앱 파일은 app.json을 기준으로 생성이 된다. 안드로이드에선 최종 앱 파일을 AAB(App Bundle)이라고 부르고 ios에서는 IPA 파일이라고 부른다고 한다.
(안드로이드는 2021년 08월 03일부로 흔히들 쓰는 APK 파일이 아니라 AAB 파일로만 업로드하게끔 변경되었다고 한다. 아래 사진 참고)

그리고 앱 배포 과정에서 BACKGROUND LOCATION 문제로 배포가 안될수도 있다고 강사님이 말씀하시면서 아래 코드를 꼭, android 키 값 안에 넣어주라고 하셨다.

 "android": { ~
 
          "permissions": ["ACCESS_FINE_LOCATION", "ACCESS_COARSE_LOCATION"]
    ~
    },

최근 구글 플레이 정책이 바뀌어서 백그라운드에서 자동적으로 돌아가는 지역 기반 기능이 안드로이드 앱에 포함이 되어있으면 그걸 왜 쓰는지 알려줘야 한다는것이었다.

그러면서 강사님은 우리가 저번에 expo-location 기능을 설치하면서 백그라운드 위치 지역 기반 기능이 자동적으로 돌아가게끔 되어 있다면서 이걸 제거하기위해 위의 코드를 넣어주는거라고 하셨다.

일단 이 상태에서 터미널에 아래 코드를 입력후 엔터를 해 최종 앱 파일을 AAB로 만들었다.

expo build:android -t app-bundle

그러면 터미널에서 아래 사진과 같은 앱 빌드 과정이 진행되는데, 이런 와중에 Project page 항목이 새로 쓰이면서 어떤 url 주소가 나온다. (정확히는 app.json의 slug 값이 url 주소의 뒷부분이 됨)

해당 주소를 웹 페이지로 열기 위해 컨트롤을 누른 상태에서 클릭하면 아래 사진과 같이 앱 소개 페이지가 따로 뜬다.

(중간 생략된 부분은 앱을 프리뷰할수 있게 도움을 주는 qr코드와 그 인터넷 링크 주소이다.)

또한, 2주차때 expo 가입할때 잠깐 들어갔던 expo 홈페이지에 들어가면 아래 사진처럼 앱 빌드 과정이 대시보드에 뜬다.

앱 빌드가 완료되기까지 시간이 좀 걸리기 때문에 아래 사진처럼 터미널에서 build finished 혹은 대시보드에 빌드 항목 알림 아이콘 옆 상태가 초록색 브이표시가 나오기까지 기달려야 한다.

그리고 이제 2단계, 구글 플레이 개발자 라이센스를 구입해야한다. 구글 플레이 스토어 해당 링크에 접속한다.
그러면 아래 사진과 같이 개발자 전용 구글 플레이 화면이 뜬다.

로그인을 눌러 구글 계정으로 가입을 하고, 결제는 해외카드로 25달러 결제를 하면 된다.

그러면 아래 사진과 같이 콘솔로 로그인되어 앱을 올릴수 있는 상태가 된다.

이제 정말로 배포를 해볼 시간이었다. 아래 사진처럼 expo 홈페이지에 들어가 우리가 여태까지 만들었던 프로젝트(빨간 상자)를 클릭해서 들어가자.

왼쪽 패널의 deploy-build 항목으로 들어가게끔 클릭한 후 아래 사진에서 빨간 상자 안에 있는 download를 클릭해 최종 앱 파일을 다운받는다.

그런뒤 최종 앱 파일을 아까 구글 플레이 개발자 콘솔에 올려야 하는데 방법은 아래 사진들로 잘 설명되어있어서 참고했다.

(데이터 보안도 하는게 있었는데 딱히 걸리는게 없는거 같아서 아니오 체크후 넘어갔다.)

위 순서대로 따라 해서 아래 사진처럼 앱 배포를 구글 플레이에 완료 했다. (1주일 정도 검토가 걸린다고는 한다. 그래도 일단 배포를 위해 할수 있는건 다 했다.)

(ios 앱 배포 과정 절차 및 순서는 5주차 강의자료 pdf에 따로 있다. 나중에 시간날때 참고할것)


5. 5주차 숙제 & 완강 소감

5주차 숙제는 최종 배포한 앱의 마켓 주소 url을 제출하는것이었다.

근데 필자가 제출한 최종 앱은 아직 검토중이라 url이 있어도 제대로 앱 마켓 페이지가 뜨지 않았다.

(최종 앱 마켓 url 주소)

아무튼 웹개발 종합반에 이어 앱개발 종합반도 완강했다.

웹개발 종합반과 같이 하는게 정말 힘들었는데 어찌저찌 끝내서 다행이었다.

내용도 훨씬 앱개발 종합반이 어려웠던거 같다. 하지만, 2022년 현재 스마트폰이 대중화되고

앱 마켓 시장도 엄청 커진 요즘, 앱개발하는 방법을 알아두는게 나중에도 도움이 될거 같아

신청한거였는데 해보길 잘한거 같다. 나중에 이 배운것을 살려 개발자 취업할때도 도움이 되었으면 한다.

0개의 댓글