본문 바로가기

코딩 개발일지

[개발일지]스파르타코딩클럽_앱개발 종합반 5주차 애드몹

스파르타코딩 앱개발 종합반 대망의 마지막 주차

앱을 만드는데있어서 소소한 수익을 위한, 아니 중요한 동기부여 부분인 광고를 붙이는 강좌 편이다

 

1. 우선 애드몹 사이트에 가서 계정부터 만든다.

https://apps.admob.com/v2/home

 

AdMob

이메일 또는 휴대전화

accounts.google.com

안드로이드 / iOS 둘다 앱 생성이 가능하다.

 

2. 앱에서도 애드몹을 설치해야한다.

엑스포 공식문서.

https://docs.expo.dev/

 

Expo Documentation

Expo is an open-source platform for making universal native apps for Android, iOS, and the web with JavaScript and React.

docs.expo.dev

 

강의에선 엑스포에서 지원하므로 설치하면 된다고 한다.

하지만..

엑스포 공식문서에서 애드몹 항목을 찾을수가 없다.

 

아무튼 강의에선 설치해서 진행하는걸로 나오므로 진행해보자

expo install expo-ads-admob

다른것들과 마찬가지로 이렇게 설치를 하고.

 

3. app.json 파일에서 코드를 추가해준다.

{
  "expo": {
    "name": "sparta-myhoneytip-gun",
    "slug": "sparta-myhoneytip-gun",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "ios": {
      "supportsTablet": true,
      "buildNumber": "1.0.0",
      "bundleIdentifier": "com.myhoneytip.gun",
      "config": {
        "googleMobileAdsAppId": ""
      }
    },
    "android": {
          "package": "com.myhoneytip.gun",
          "versionCode": 1,
          "config": {
            "googleMobileAdsAppId": ""
          }
    },
  }
}

ios의 bundleIdentifier와 googleMobileAdsAppId
android의 package와 googleMobileAdsAppId는 자신의 계정의 값을 넣어준다.

 

4. 광고단위 설정

 

애드몹사이트 - 생성한 앱 선택 - 광고단위 탭 - 광고단위추가 - 어떤 배너를 쓸지 선택 - 광고단위만들기

 

광고단위 생성 후 두개의 ID를 받게 되는데,

//위 ID는 강의노트에서 퍼온 예시id임

첫 번째 키 값은 app.json에 비워 뒀던 android의 googleMobileAdsAppId 에 적용

 

5. Main.js에 코드 적용 (가로배너)

광고 단위 생성 후 부여 받은 두 번째 키값은 app.json이 아닌 실제 Main.js 코드 단에서 사용될 키 값입니다.
구글에서 부여한 가로배너를 앱과 연결하는 정보가 담겨 있는데요! 다음 Main.js 코드를 적용해주세요
적용하기 전에 코드 단에서 부르는 가로 배너의 이름은 AdMobBanner 입니다.
광고 이름들이 정해져 있어요!

반드시 애드몹의 여러분들 키 값을 적용해야 보여요!

적용된 MainPage.js

import React,{useState,useEffect} from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
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}) {
  //useState 사용법
	//[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
  //setState는 state를 변경시킬때 사용해야하는 함수

  //모두 다 useState가 선물해줌
  //useState()안에 전달되는 값은 state 초기값
  const [state,setState] = useState([])
  const [cateState,setCateState] = useState([])
  //날씨 데이터 상태관리 상태 생성!
  const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
  })

	//하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
  //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
  const [ready,setReady] = useState(true)

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

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

      console.log(result)
      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
        }))
    }
}

  //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
  // let tip = state.tip;
  let todayWeather = 10 + 17;
  let todayCondition = "흐림"
  //return 구문 밖에서는 슬래시 두개 방식으로 주석
  return ready ? <Loading/> :  (
    /*
      return 구문 안에서는 {슬래시 + * 방식으로 주석
    */

    <ScrollView style={styles.container}>
      <StatusBar style="light" />
      {/* <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={{uri: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}/>)
          })
        }
        
      </View>
      {/* 
        ca-app-pub-5579008343368676/9202552776
        ca-app-pub-5579008343368676/6885179499
      */}
      {Platform.OS === 'ios' ? (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="ca-app-pub-5579008343368676/6885179499"
                  style={styles.banner}
                />
            ) : (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="ca-app-pub-5579008343368676/9202552776"
                  style={styles.banner}
                />
            )}
   
    </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
  },
  banner:{
    width:"100%",
    height:100
  }


});

 

*여기서부터 적용 안됨!

에러발생으로 더이상 진행이 안되어서 즉문즉답을 확인해보니 나와 같은 에러로 질문한 사람들이 있다

//이 에러창 스샷도 퍼옴.. 어차피 같은 증상이라

질문 답변을 보니

expo 자체에서 9월 부터는
import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
from 'expo-ads-admob';


이 패키지에 대해서 동작하지 않도록 버전이 수정되었어요!
그래서 현재는 expo-ads-admob은 더 이상 사용할 수 없는 기술 입니다.
저희도 이 부분에 대해서 다르게 광고를 적용할 수 있는 강의를 제작하고 있습니다.


이 부분 자체는 최근에 지금 대처할 만한 다른 방법을 제공하는 방법이 강의가 완성되어 대체하는 것 외에 방법이 없기 때문에
다른 해결책을 안내해드리긴 어려울 것 같습니다.
다만 강의가 업데이트 되기 전까지는 admob관련 코드는 주석 처리해주신 후에
일단 광고를 붙이는 강의 3개의 강의는 일단 내용을 뛰어 넘어 주셔야 할 것 같습니다.


이 부분 양해 말씀 드리며
이후 새롭게 업데이트 되면 그 부분의 강의를 들어주시면 감사하겠습니다!

2022.10월기준 해당 방법으론 광고를 붙일수 없는것 같다.


그렇담 일단 이론적으로만 알고 넘어가보자.

어차피 이게 어떻게 돌아가는지 이해하는게 가장 중요하니 머릿속에 어떤 방식으로 했었는지 알고 있는것만으로도 충분히 도움이 될것 같다.

 

//애드몹 설정을 위한 엑스포 애드몹 라이브라리 임포트!
import {
  setTestDeviceIDAsync,
  AdMobBanner,
  AdMobInterstitial,
  PublisherBanner,
  AdMobRewarded
} from 'expo-ads-admob';
{Platform.OS === 'ios' ? (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="ca-app-pub-3271224099084995/4041258226"
                  onDidFailToReceiveAdWithError={bannerError}
                  style={styles.banner}
                />
            ) : (
                <AdMobBanner
                  bannerSize="fullBanner"
                  servePersonalizedAds={true}
                  adUnitID="ca-app-pub-3271224099084995/5120026862"
                  onDidFailToReceiveAdWithError={bannerError}
                  style={styles.banner}
                />
            )}

아이폰이냐 안드로이드냐에 따라 광고 설정을 달리할 수 있기 때문에 두 플랫폼에 대응할 수 있는 광고도 설정이 가능합니다. 또한 배너에 스타일을 적용하여 앱 디자인에 알맞게 적용할 수 있습니다.

banner: {
 //배너 스타일!   
}

 

6. 전면배너 만들기

애드몹사이트에서 전면배너 생성하여 광고단위 ID를 부여받는다.

이 전면 광고 배너 실행 순서는 이렇습니다

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

그럼 Card.js에 기능을 달아야 겠죠?

전면 광고를 사용자들에게 보여주는 상황은 
" 팁을 누르고! 디테일 화면으로 넘어가기 바로전!" 입니다.
그럼 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("ca-app-pub-5579008343368676/6838730428") : AdMobInterstitial.setAdUnitID("ca-app-pub-5579008343368676/4903859898")

        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",
    }
});
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})
      }
    }

이 부분을 보면 애드몹 API를 추가했습니다.
이 부분은 실행 순서가 지켜져야 하기 때문에 async await 자바스크립트 문법을 사용했습니다

 

그리고 useEffect에는 Card.js에 들어오자마자 광고를 준비하는 코드를 그대로 가져와 복붙했고, 안에 키값만 여러분들의 키값으로 변경했습니다. 그리고 보여지는 AdMobInterstitial.addEventListener 코드들은

광고를 실행했을때
광고 실행이 실패했을때
광고가 열리고 난다음
광고가 끝난다음

각각 어떻게 처리할까?에 대한 애드몹 제공함수인데요!
우리가 정작쓰는건 광고가 끝난 다음의 모습이지만 여러분들이 뭘 좋아하실지 몰라 다 가져왔습니다

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

        AdMobInterstitial.addEventListener("interstitialDidLoad", () =>
            console.log("interstitialDidLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidFailToLoad", () =>
            console.log("interstitialDidFailToLoad")
        );
        AdMobInterstitial.addEventListener("interstitialDidOpen", () =>
            console.log("interstitialDidOpen")
        );
        AdMobInterstitial.addEventListener("interstitialDidClose", () => {
              //광고가 끝나면 다음 코드 줄이 실행!
            console.log("interstitialDidClose")
          
        });
    },[])

 

 


강의노트를 보니 새로운 내용이 추가되어있다.

2022년 상반기 부터, expo에서 더이상 디폴트 기능으로써 google admob을 사용할 수 없게 되었습니다. 하지만! expo에 새로 생긴 배포 방식인 EAS 기능을 이용해 구글 애드몹을 설정할 수 있습니다.

** 일전에 같이 공부한적 있으신분들은 expo build 가 eas build 로 바뀐다고 보시면 됩니다 🙂

역시나 단어만 들으면 감이 안오니 하나하나 따라 해보시죠!

1. expo eas 설치

npm install -g eas-cli

그리고 네이티브 설정을 수정할수 있는 기능도 설치

npx expo install expo-build-properties

eas에 로그인 (expo login 했던 계정과 동일한 계정으로)

eas login

-------------------

- 내 앱은 eas 빌드 방식에 맞긴다!는 명령어 실행!

** 여기서 중요한 점은, Window 노트북 대상자분들은 반드시 다음 선택창에서 android만 한다고 누르셔야합니다

eas build:configure

 

Would you like to automatically create an EAS project for @glenncy/tip-example? › (Y/n)

여기서 Y선택

안드로이드로 선택하는건 방향키 아래 버튼을 눌러서 안드로이드 선택 후 엔터.

 

app.json에 다음 plugin 사용코드 삽입

 

"plugins": [
      [
        "expo-build-properties",
        {
          "ios": {
            "useFrameworks": "static"
          }
        }
      ]
]

2. 테스트 기기 설정 및 애드몹 광고 설정

  • 본격적으로 코드단 들어가기전, 본인 기기 테스트 기기 설정!
구글 애드몹에, 코드 단 설정시에 테스트로 사용할 기기가 테스트로 등록되어 있지 않으면, 구글 애드몹 자체에서 밴을 먹을 수 있는 불상사가 일어납니다.

애드몹 홈페이지 -  설정 탭 - 기기테스트 탭 - 테스트기기 추가

기기이름 -> 알아서 설정(폰 종류 갤럭시s20 등)

플랫폼 -> 안드로이드

광고 ID/IDFA -> 갤럭시 기준. 각자의 핸드폰에서 설정 창 들어가서 '광고' 검색하면 쉽게 찾을수 있음. - 개인정보 보호 탭-광고-> 들어가면 이 기기의 광고ID라고 aa0000aa-a000-00aa-a000-00aaa0a00000 방식의 Id가 있음. 

-> 추가 완료

------------------------

RN 용 구글 애드몹 라이브러리 설치

yarn add react-native-google-mobile-ads

 

애드몹 가입 및 프로젝트 생성 단계에서 확인이 가능했던, ios, android 앱 아이디를 app.json에 설정

// <project-root>/app.json
{
  "expo": {
    // ...
  },
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx",
    "ios_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx"
  }
}
//4. 앱 시작 하자마자 구글 애드몹 활성화 코드 심기!

꿀팁 앱이 실행되면 구글 애드몹 사용할 준비하기 코드작성!

App.js

import React, { useEffect } from 'react';
//이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로
//컴포넌트를 더이상 불러오지 않아도 됩니다.
// import MainPage from './pages/MainPage';
// import DetailPage from './pages/DetailPage';
import { StatusBar } from 'expo-status-bar';

//메인에 세팅할 네비게이션 도구들을 가져옵니다.
import {NavigationContainer} from '@react-navigation/native';
import StackNavigator from './navigation/StackNavigator'
import mobileAds from 'react-native-google-mobile-ads';

export default function App() {

  console.disableYellowBox = true;
  useEffect(()=>{
		//구글 애드몹은 실제 빌드된 앱에서만 가능하기 때문에! 조건부 처리!
    if(!__DEV__){
      mobileAds()
      .initialize()
      .then(adapterStatuses => {
        // Initialization complete!
      });
    }
  },[])
  
  return ( 
  <NavigationContainer>
    <StatusBar style="black" />
    <StackNavigator/>
 </NavigationContainer>);
}

애드몹 사이트에서 가로배너광고단위 생성- 두개의 ID를 받음.

첫번째 키 값은 app.json에 넣었음(위에 참고)

두번째 키값은 MainPage.js에서 실제 가로배너 설치위치에 둔다.

자 그런데 상단의 이 코드의 키 값은 여러분 것을 넣어야 합니다

const adUnitId = __DEV__ ? 
TestIds.BANNER : 'ca-app-pub-5579008343368676/9202552776';

(5) 번에서 진행된 가로배너 광고 생성으로 얻은 광고단위 키값을 반드시 넣어주세요

MainPage.js

import React,{useState,useEffect} from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
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 { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads';

const adUnitId = __DEV__ ? TestIds.BANNER : 'ca-app-pub-5579008343368676/9202552776';


export default function MainPage({navigation,route}) {
  //useState 사용법
	//[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
  //setState는 state를 변경시킬때 사용해야하는 함수

  //모두 다 useState가 선물해줌
  //useState()안에 전달되는 값은 state 초기값
  const [state,setState] = useState([])
  const [cateState,setCateState] = useState([])
  //날씨 데이터 상태관리 상태 생성!
  const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
  })

	//하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
  //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
  const [ready,setReady] = useState(true)

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

  const getLocation = async () => {
    //수많은 로직중에 에러가 발생하면
    //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
    try {
      //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
      await Location.requestForegroundPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      // console.log(locationData)
      // console.log(locationData['coords']['latitude'])
      // console.log(locationData['coords']['longitude'])
      const latitude = locationData['coords']['latitude']
      const longitude = locationData['coords']['longitude']
      const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
      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
    

      //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
      //잘 기억이 안난다면 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
        }))
    }
}

  //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
  // let tip = state.tip;
  let todayWeather = 10 + 17;
  let todayCondition = "흐림"
  //return 구문 밖에서는 슬래시 두개 방식으로 주석
  return ready ? <Loading/> :  (
    /*
      return 구문 안에서는 {슬래시 + * 방식으로 주석
    */

    <View>
      {__DEV__ ? null : (<BannerAd
        unitId={adUnitId}
        size={BannerAdSize.FULL_BANNER}
        requestOptions={{
          requestNonPersonalizedAdsOnly: true,
        }}
      />)}
    <ScrollView style={styles.container}>
      <StatusBar style="light" />
      {/* <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={{uri: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}/>)
          })
        }
        
      </View>
  
    </ScrollView>
    </View>
    )
}

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
  }


});

내가 생성한 광고에서받은 키값으로 바꿔준다.

 왜 광고 코드마다 __DEV__ 로 if문 분기 처리를 하였을까요?
구글 애드몹 코드가 있을 경우, 우리가 일반적으로 로컬(여러분 컴퓨터)에서 expo start로 실행시켰을 경우에 광고가 나오지 않습니다. 더군다나 앱도 실행되지 않습니다.
즉 이제 구글 애드몹은 실제 빌드된 앱(마켓에 제출 할 앱)에서만 작동하게 변경되었기 때문인데요!. 그래서 항상 구글 애드몹 광고를 붙일때는, 
”지금 개발중이니까 광고는 잠시 꺼두고, 실제 앱을 빌드 할 경우 즉, __DEV__ 모드가 아닐 경우에만 동작하게 하자”라는 코드를 심은 겁니다

 

eas prebulild 해보기

eas prebuild란, 말그대로 먼저 한번 빌드 해본다, 즉 지금까지 만든 프로젝트로 앱을 만들어본다
라는 간단한 의미로 해석하셔도 좋습니다. 다음 명령어를 차례로 입력해봅시다

1) npx expo prebuild

What would you like your Android package name to be? 
> 주어진 디폴트 이름 그대로 사용하셔도 좋습니다 (엔터)

What would you like your iOS bundle identifier to be?
> 엔터!

...
처음 하시는 분들은 설치를 뭔가 많이 합니다.
그럼 그상태로 모두다 끝날 때까지 대기합니다

// 음.. 계속 에러가 떠서 뭘 어떻게 할수가 없다... 광고 붙여보고싶은데

어디서부터 잘못된지 모르겠다.

아마도 강의에 따라서 코드 입력했던 expo 에서 광고 붙이는 부분 때문일까?

강의가 다시 나오면 차분히 처음부터 따라해보는게 좋을것 같다.

 

 

아무튼.. 5주가 금방 지났다.

원하던 결과였던 광고까지 붙어있는 결과물을 볼수 없다는게 너무 아쉽다. 설령 강의에 나온 그대로를 따라해서 나온 결과일지라도 그 내부에 코드가 어떻게 돌아가는지 이해하고 있는거랑 엄청난 차이일텐데 직접 구현하지 못한건 아쉬울따름.

하필 내가 할때 광고가 지원을 안하게 바뀌다니? 

-> 이부분은 곧 업데이트가 될거라 생각된다

그래도 이론적으로 큰 도움이 됬다.  

 

찾아보니 리액트 네이티브같은 하이브리드 언어들이 한번에 안드로이드와 아이폰 둘다 사용할수 있어서 가성비가 좋다고는 하는데.. 에러를 해결한다거나 특정 상황에 대해선 네이티브 언어보다 안좋을수 있다는 그런 내용이 있다. 뭐 내가 아직 아는게 없어서 뭐라 판단할순 없다. 격어보면 알겠지

 

일단 내가 구현하고 싶은 앱은 리액트네이티브로도 가능할것 같다. 좀더 공부해 보는게 좋을것 같다.