본문 바로가기
Coding

[스파르타코딩클럽] APP 개발 종합반 - 4주차 개발일지

by 준아이덴티티 2022. 11. 30.
SMALL

 

 

 

 

 

 

두둥탁(?) 앱개발 종합반 4주차 종료. 개발일지를 쓸 때가 되었다.

좌충우돌 비전공자 개발러의 개발일지 시작.

 

 

 

 


 

 

 

🦜 수업 목표

  1. 앱과 서버에 대한 이해
  2. 서버리스에 대한 이해
  3. 파이어베이스를 이용한 서버 구성

 

 

 


 

 

1. 앱과 서버

 

  앱에 모든 데이터를 담을 순 없다. 앱의 용량이 커질 수도 있고 앱 개발자가 새로운 데이터를 사용자에게 제공하려면 새로운 데이터를 담아 재배포해야한다.

 

  앱에서 서버에 데이터를 요청하거나 데이터를 보내는 대화를 하려면 서버가 정한 규칙에 따라 대화 요청(Request)을 해야한다. 정한 규칙에 따라 요청을 하지 않으면 응답(Response)이 오지 않는다. 서버쪽에서 정한 규칙을 보통 API(Application Programming Interface)라고 부르며 그 형태는 다음과 같다.

 

 

서버가 제공하는 도메인일 수도 있고,

 

서버가 만들어 놓은 함수를 이용하기도 한다.

 

 

▲ 서버에서 주는 데이터 형식 JSON

 

 

서버가 앱에 데이터를 줄 땐 JSON 형태의 데이터를 전달하며,

JSON 형태는 리스트와 딕셔너리의 복합 구조다.

 

 

 


 

 

2. 파이어베이스

  서버리스란 이름 그대로 서버가 없다는 뜻이 아닌 '서버를 직접 만들 필요가 없다'는 것을 의미. 서버를 직접 구현, 구성할 필요없이 필요한 서버기능을 제공하는 곳에서 서비스를 사용하기만 하면 된다.

 

  파이어베이스는 구글에서 만든 서버리스 서비스다. 서버에 대한 지식의 깊이가 그렇게 깊지 않아도 서버적인 기능들을 사용할 수 있게끔 도와준다.

 

 

 

  한 서비스를 만드는데 이렇게도 많은 서버적 기능들을 제공한다. 내가 필요한 데이터베이스, 이미지 파일 서버는 물론 푸시 알람기능, 로그인 인증 기능 등등 아주 다양한 기능들이 준비되어 있다.

 

 

  파이어베이스를 이용하는 절차는 게임 캐릭터를 생성하는 것과 유사하다.

  1. 게임 가입
  2. 캐릭터 생성
  3. 필요한 장비 착용

 

  파이어베이스에 대입해보면,

  1. 파이어베이스 가입
  2. 프로젝트 생성
  3. 필요한 서비스 활성화

 

 


 

 

 

  파이어베이스에 가입하는 절차는 어렵지 않다. 구글 계정을 준비한 후 다음 링크에 접속해 시작하기를 누르면 바로 프로젝트를 만들 수 있다. 파이어베이스 링크 : https://firebase.google.com/?hl=ko

 

Firebase

Firebase는 고품질 앱을 빠르게 개발하고 비즈니스를 성장시키는 데 도움이 되는 Google의 모바일 플랫폼입니다.

firebase.google.com

 

 

 

  프로젝트가 생성되었으면 파이어베이스에 iOS를 개발하고 있는지, 안드로이드를 개발하고 있는지, 웹을 개발하고 있는지 알려줘야 개발 중인 앱에 파이어베이스를 코드단에서 연결할 수 있는 연결정보를 준다.

 

  나는 튜터의 강의에 따라 안드로이드, iOS 앱 전용 개발을 Expo에서 따로하지 않고 자바스크립트로만 개발을 하고 있기 때문에 웹 SDK를 이용해 쉽게 파이어베이스 사용이 가능했다.

 

 

 

호스팅 설정 체크하지 말고 진행

 

 

 

 

이렇게 파이어베이스 프로젝트 내에서 앱을 생성하면 최종적으로 연결정보를 준다.

앱이 완성되면, 아래와 같이 콘솔로 이동 후 설정 버튼 클릭

 

 

 

 

 

 

드래그해서 아래를 보면 내가 사용할 접속 정보가 코드로 존재한다.

 

 

 

앱에 파이어베이스 도구 설치 및 연결 시의 모습

 

 


 

  가장 먼저 사용하게 되는 파이어베이스 서비스는 파일 스토리지(Storage)다. 간단히 파일 저장소라고 여기면 충분하며 멀리있는 파일 저장소에 이미지 및 사용할 파일을 올려두고, 필요할 때마다 꺼내쓰는 식이다.(아래 참고)

 

 

 

 

 

이렇게 파일을 올리기만 해도 이미지가 저장된 주소를 제공해준다.

 

 

 


 

 

 

  리얼타임 데이터베이스는 리스트, 딕셔너리 구조 즉 JSON 형태로 저장/ 관리되는 데이터베이스 서비스다. 이 서비스를 사용할 땐 파이어베이스에서 제공해주는 함수들을 이용하기만 하면 데이터 저장/ 수정/ 삭제가 가능하다. 이름이 리얼타임 데이터베이스인 이유는, '플랫폼과 실시간 데이터 주고받기에 특화'되어 그렇다.

 

  생성 시 권한을 모두 공개로 바꿔주어야 한다. 만약 리얼타임 데이터베이스 탭이 왼쪽에서 보이지 않는다면, Cloud Firestore를 선택한 후 진행해야 한다.

 

 

 

 

 

  규칙으로 들어와 Read, Write를 모두 True로 바꿔주자. 수정이 완료되면 게시를 눌러준다.

 

 

 

 

 

 

여기까지 완료되면 준비 끝

 

 

 


 

 

 

다음은 데이터를 서버에 올려 관리하는 방법이다.

 

 

 

▲ 우측에 위치한 토글 버튼을 눌러 JSON 가져오기 선택

 

 

 

 

 

  이렇게 내가 가진 팁들이 딕셔너리 구조로 저장이 되며, 숫자(0,1,2,3,4...)는 리스트의 형태로 문제가 차곡차곡 쌓여있는 모습을 볼 수 있다. 따라서, 이 데이터를 앱에 가져오게 되면 data.json 데이터를 이용했던 것과 동일하게 사용이 가능하다.

 

  image의 값 부분을 눌러보면 값을 수정할 수 있게 활성화되는데 이 부분엔 수정하고싶을 때 파일 저장소에 올렸던 이미지 주소를 넣으면 된다.

 

 

 


 

 

  

  파이어베이스를 이용해 전체 데이터를 조회하려면 전체 데이터를 가져올 수 있게 해주는 '제공 함수 사용법'과 가져올 데이터가 '어떤 이름으로 리얼타임 데이터베이스에 저장되어 있는지' 가 중요하다.

 

  데이터의 저장위치를 알았다면 파이어베이스의 리얼타임 데이터베이스 전용함수에 데이터 저장위치를 알려주어 데이터를 가져올 수 있다.

 

 

 

 

 

  ref('/tip') 이 부분에서 /tip 영역에 가져오고자 하는 데이터 주소를 넣어주면 된다. 이 주소 앞부분에는 기본주소가 생략되어있다. firebaseConfig.js에서 이미 파이어베이스 계정을 세팅했기 때문에, 기본주소와 정보들은 앱 내에서 사용하는 파이어베이스 함수들이 알고있는 상태.

 

 

 

 

  이 코드는 서버리스를 이용하여 데이터베이스를 조회하기 위해 파이어베이스측에서 정해둔 API 사용법이다. 따라서 우린 공식문서 그대로 사용방법을 적용해야한다.

 

  조회한 데이터는 snapshot 부분에 담겨서 {} 내부에서 사용할 수 있는데 그 중 실제 우리에게 필요한 데이터는 snapshot.val()로 가져와 변수에 담아 사용할 수 있다.

 

 


 

 

  리얼타임 데이터베이스에는 특정 데이터를 읽는 기능도 있다. 디테일 화면의 데이터엔 자세한 내용에 대한 데이터, 댓글 정도의 데이터가 담겨있는데 이 데이터들 또한 실시간으로 변경될 수 있다.

 

  따라서, 그때그때 변경된 데이터가 항상 반영되는 파이어베이스 데이터베이스로부터 가져와야한다. 큰 데이터들이 이동하는 것은 앱 퍼포먼스 저하의 원인이 된다. 그래서 idx 번호만 넘겨서 필요한 데이터는 서버로부터 그때그때 가져오게 해야한다.

 

 


 

 

  마지막으로, 데이터를 저장해야하는 상황에서 쓸 수 있는 쓰기 기능도 있다. 앱에서 파이어베이스로 데이터를 보내 저장하는 상황은 언제일까. 바로 '찜'버튼을 눌렀을 때다. '특정'유저가 내가 만든 앱을 이용하다 '찜'버튼을 눌렀다면 파이어베이스에 어떤 식으로 저장을 해야할까?

 

  유저가 어떤 것을 '찜'했는지 저장을 하고, 실제 찜 페이지에서 어떤 것인지 나열해주려면 다음과 같이 전체 데이터를 저장하면 된다.

 

  • 번호 : idx
  • 이미지 : image
  • 제목 : title
  • 내용 : desc

 

  내가 만든 앱은 많은 사람들이 사용할 것이다. 그러면 내 데이터베이스에도 여러 유저의 데이터가 쌓일 것이다. 이때 유저마다 고유한 정보를 구분/ 관리하려면 최소한 유저 ID 값 정도의 데이터가 필요하다.

 

  Expo는 내가 만든 앱의 유저 고유 ID를 생성해 알려준다. 아래 코드를 사용하면 '앱 어디서든지' 동일한 사용자 유니크 아이디를 생성 및 사용할 수 있다.

 

 

Expo-application 공식문서 : https://docs.expo.dev/versions/latest/sdk/application/#api

 

Application - 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

 

 

 

 

 


 

 

 

4주차 끝 그리고 숙제풀이👾

 

 

 

숙제 1. LikePage.js

더보기
import React,{useState, useEffect} from 'react';
import {ScrollView, Text, StyleSheet} from 'react-native';
import LikeCard from '../components/LikeCard';
import Card from '../components/Card';
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';
import {firebase_db} from "../firebaseConfig"

export default function LikePage({navigation,route}){
    
    const [tip, setTip] = useState([])

    useEffect(()=>{
        navigation.setOptions({
            title:'꿀팁 찜'
        })
        getLike()
    },[])

    const getLike = async () => {
        let userUniqueId;
        if(isIOS){
        let iosId = await Application.getIosIdForVendorAsync();
            userUniqueId = iosId
        }else{
            userUniqueId = await Application.androidId
        }

        console.log(userUniqueId)
        firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
            console.log("파이어베이스에서 데이터 가져왔습니다!!")
            let tip = snapshot.val();
            setTip(tip)
        })
    }

    return (
        <ScrollView style={styles.container}>
           {
               tip.map((content,i)=>{
                   return(<LikeCard key={i} content={content} navigation={navigation}/>)
               })
           }
        </ScrollView>
    )
}

const styles = StyleSheet.create({
    container:{
        backgroundColor:"#fff"
    }
})

 

숙제 2: LikePage.js

더보기
import React,{useState, useEffect} from 'react';
import {ScrollView, Text, StyleSheet} from 'react-native';
import LikeCard from '../components/LikeCard';
import Loading from '../components/Loading';
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';
import {firebase_db} from "../firebaseConfig"

export default function LikePage({navigation,route}){
    
    const [tip, setTip] = useState([])
    const [ready,setReady] = useState(true)

    useEffect(()=>{
        navigation.setOptions({
            title:'꿀팁 찜'
        })
        getLike()
    },[])

    const getLike = async () => {
        let userUniqueId;
        if(isIOS){
        let iosId = await Application.getIosIdForVendorAsync();
            userUniqueId = iosId
        }else{
            userUniqueId = await Application.androidId
        }

        console.log(userUniqueId)
        firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
            console.log("파이어베이스에서 데이터 가져왔습니다!!")
            let tip = snapshot.val();
						// tip이 null도 아니고(실제 값이 존재 하고)
						// tip의 갯수가 0개 이상! 즉 있을때만 상태 변경하여 화면을 다시 그리기!
            if(tip && tip.length > 0){
                setTip(tip)
                setReady(false)
            }
            
        })
    }

    return (
        <ScrollView style={styles.container}>
           {
               tip.map((content,i)=>{
                   return(<LikeCard key={i} content={content} navigation={navigation}/>)
               })
           }
        </ScrollView>
    )
}

const styles = StyleSheet.create({
    container:{
        backgroundColor:"#fff"
    }
})

 

숙제 3~4: LikeCard.js

 

더보기
import React from 'react';
import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native'

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

    const detail = () => {
        navigation.navigate('DetailPage',{idx:content.idx})
    }

    const remove = () => {

    }
    return(
        //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
        <View style={styles.card}>
            <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 style={styles.buttonGroup}>
                    <TouchableOpacity style={styles.button} onPress={()=>detail()}><Text style={styles.buttonText}>자세히보기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.button} onPress={()=>remove()}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
              
                </View>
            </View>
        </View>
    )
}


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",
    },
    buttonGroup: {
        flexDirection:"row",
    },
    button:{
        width:90,
        marginTop:20,
        marginRight:10,
        marginLeft:10,
        padding:10,
        borderWidth:1,
        borderColor:'deeppink',
        borderRadius:7
    },
    buttonText:{
        color:'deeppink',
        textAlign:'center'
    }
});

 

숙제 5: LikePage.js

 

더보기
import React,{useState, useEffect} from 'react';
import {ScrollView, Text, StyleSheet,Platform} from 'react-native';
import LikeCard from '../components/LikeCard';
import Loading from '../components/Loading';
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';
import {firebase_db} from "../firebaseConfig"

export default function LikePage({navigation,route}){
    
    const [tip, setTip] = useState([])
    const [ready,setReady] = useState(true)

    useEffect(()=>{
        navigation.setOptions({
            title:'꿀팁 찜'
        })
        getLike()
    },[])

    const getLike = async () => {
        let userUniqueId;
        if(isIOS){
        let iosId = await Application.getIosIdForVendorAsync();
            userUniqueId = iosId
        }else{
            userUniqueId = await Application.androidId
        }

        console.log(userUniqueId)
        firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
            console.log("파이어베이스에서 데이터 가져왔습니다!!")
            let tip = snapshot.val();
            let tip_list = Object.values(tip)
            if(tip_list && tip_list.length > 0){
                setTip(tip_list)
                setReady(false)
            }
            
        })
    }

    return (
        <ScrollView style={styles.container}>
           {
               tip.map((content,i)=>{
                   // LikeCard에서 꿀팀 상태 데이터(==tip)과 꿀팁 상태 데이터를 변경하기 위한
                   // 상태 변경 함수(== setTip)을 건네준다.
                   //즉 자기 자신이 아닌, 자식 컴포넌트에서도 부모의 상태를 변경할 수 있다.
                   return(<LikeCard key={i} content={content} navigation={navigation} tip={tip} setTip={setTip}/>)
               })
           }
        </ScrollView>
    )
}

const styles = StyleSheet.create({
    container:{
        backgroundColor:"#fff"
    }
})

 

숙제 5: LikeCard.js

 

더보기
import React from 'react';
import {Alert,View, Image, Text, StyleSheet,TouchableOpacity,Platform} from 'react-native'
import {firebase_db} from "../firebaseConfig"
const isIOS = Platform.OS === 'ios';
import * as Application from 'expo-application';
//MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
export default function LikeCard({content,navigation,tip, setTip}){

    const detail = () => {
        navigation.navigate('DetailPage',{idx:content.idx})
    }

    const remove = async (cidx) => {
      let userUniqueId;
      if(isIOS){
      let iosId = await Application.getIosIdForVendorAsync();
          userUniqueId = iosId
      }else{
          userUniqueId = await Application.androidId
      }

      console.log(userUniqueId)
      firebase_db.ref('/like/'+userUniqueId+'/'+cidx).remove().then(function(){
        Alert.alert("삭제 완료");
        //내가 찝 해제 버튼을 누른 카드 idx를 가지고
        //찝페이지의 찜데이터를 조회해서
        //찜해제를 원하는 카드를 제외한 새로운 찜 데이터(리스트 형태!)를 만든다
        let result = tip.filter((data,i)=>{
          return data.idx !== cidx
        })
        //이렇게 만들었으면!
        //LikePage로 부터 넘겨 받은 tip(찜 상태 데이터)를
        //filter 함수로 새롭게 만든 찜 데이터를 구성한다!
        console.log(result)
        setTip(result)

      })
      
    }

    return(
        //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
        <View style={styles.card}>
            <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 style={styles.buttonGroup}>
                    <TouchableOpacity style={styles.button} onPress={()=>detail()}><Text style={styles.buttonText}>자세히보기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.button} onPress={()=>remove(content.idx)}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
              
                </View>
            </View>
        </View>
    )
}


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",
    },
    buttonGroup: {
        flexDirection:"row",
    },
    button:{
        width:90,
        marginTop:20,
        marginRight:10,
        marginLeft:10,
        padding:10,
        borderWidth:1,
        borderColor:'deeppink',
        borderRadius:7
    },
    buttonText:{
        color:'deeppink',
        textAlign:'center'
    }
});

 

DetailPage.js 수정사항

 

더보기
import React,{useState,useEffect} from 'react';
import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share,Platform } from 'react-native';
import * as Linking from 'expo-linking';
import {firebase_db} from "../firebaseConfig"
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';

export default function DetailPage({navigation,route}) {

    const [tip, setTip] = useState({
        "idx":9,
        "category":"재테크",
        "title":"렌탈 서비스 금액 비교해보기",
        "image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money1.png",
        "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ",
        "date":"2020.09.09"
    })
    
    useEffect(()=>{
        console.log(route)
        navigation.setOptions({
            title:route.params.title,
            headerStyle: {
                backgroundColor: '#000',
                shadowColor: "#000",
            },
            headerTintColor: "#fff",
        })
        //넘어온 데이터는 route.params에 들어 있습니다.
        const { idx } = route.params;
        firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
            let tip = snapshot.val();
            setTip(tip)
        });
    },[])

    const like = async () => {
        
        // like 방 안에
        // 특정 사용자 방안에
        // 특정 찜 데이터 아이디 방안에
        // 특정 찜 데이터 몽땅 저장!
        // 찜 데이터 방 > 사용자 방 > 어떤 찜인지 아이디
        let userUniqueId;
        if(isIOS){
        let iosId = await Application.getIosIdForVendorAsync();
            userUniqueId = iosId
        }else{
            userUniqueId = await Application.androidId
        }

        console.log(userUniqueId)
	       firebase_db.ref('/like/'+userUniqueId+'/'+ tip.idx).set(tip,function(error){
             console.log(error)
             Alert.alert("찜 완료!")
         });
    }

    const share = () => {
        Share.share({
            message:`${tip.title} \n\n ${tip.desc} \n\n ${tip.image}`,
        });
    }

    const link = () => {
        Linking.openURL("https://spartacodingclub.kr")
    }
    return ( 
        // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고
        // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. 
        // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. 
        <ScrollView style={styles.container}>
            <Image style={styles.image} source={{uri:tip.image}}/>
            <View style={styles.textContainer}>
                <Text style={styles.title}>{tip.title}</Text>
                <Text style={styles.desc}>{tip.desc}</Text>
                <View style={styles.buttonGroup}>
                    <TouchableOpacity style={styles.button} onPress={()=>like()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.button} onPress={()=>share()}><Text style={styles.buttonText}>팁 공유하기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.button} onPress={()=>link()}><Text style={styles.buttonText}>외부 링크</Text></TouchableOpacity>
                </View>
                
            </View>
            
        </ScrollView>
    
    )
}

const styles = StyleSheet.create({
    container:{
        backgroundColor:"#000"
    },
    image:{
        height:400,
        margin:10,
        marginTop:40,
        borderRadius:20
    },
    textContainer:{
        padding:20,
        justifyContent:'center',
        alignItems:'center'
    },
    title: {
        fontSize:20,
        fontWeight:'700',
        color:"#eee"
    },
    desc:{
        marginTop:10,
        color:"#eee"
    },
    buttonGroup: {
        flexDirection:"row",
    },
    button:{
        width:90,
        marginTop:20,
        marginRight:10,
        marginLeft:10,
        padding:10,
        borderWidth:1,
        borderColor:'deeppink',
        borderRadius:7
    },
    buttonText:{
        color:'#fff',
        textAlign:'center'
    }
})

 

 


 

 

끝으로.

 

 

 

  이번 주차도 참 다사다난했다... 이번 주차에도 설마 막힘이 있을까했는데 설마했던 일이 현실로 일어났다. 바로 즉문즉답을 이용했고 하... 갈수록 태산이다. 화이팅...

 

 

 

 

LIST