1. 게시판 Fragment화

앱에는 자유게시판과 익명게시판, 2개의 게시판이 존재한다.

 

그런데 기존 코드는 자유게시판/익명게시판 버튼을 터치할 때마다

새로운 Intent를 만들어 페이지 이동을 해서 Intent가 스택처럼 계속 쌓여나갔다.

 

이 점이 비효율적이라고 생각해서 Intent 대신 Fragment를 이용해서

한 Intent에서 자유게시판/익명게시판 Fragment를 호출하여 각 게시판을 이동할 수 있도록 수정했다.

 

 

Firebase DB와 연결하지 않아 실제 게시물 내용이 반영되진 않았지만,

각 게시판에 따라 서로 다른 RecyclerView 내용이 출력되는 것은 확인했다.

 

 

2. 게시글 작성 기능 구현 (DB 데이터 추가)

Firebase DB에 작성한 게시글 데이터를 등록하는 기능을 구현했다.

일기장 기능을 구현하면서 DB에 데이터 추가하는 코드는 작성했었기 때문에

게시글도 그와 유사하게 코드를 작성했다.

 

3. 게시글 조회 기능 구현 (DB 데이터 조회)

DB에서 데이터를 조회하면서 고민할 부분이 2가지가 있었다.

 

첫번째는 Firebase DB가 트리 구조이기 때문에

전체 일기 데이터를 어떻게 불러올 수 있는지 고민되어 찾아보았다.

구글링 한 결과 다음과 같은 코드를 작성했다.

 

freeBoardRef.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                    for(DataSnapshot dateSnap: dataSnapshot.getChildren()){
                        for(DataSnapshot snap: dateSnap.getChildren()){
                            Post value = snap.getValue(Post.class);
                            adapter.addItem(new Post(value.title, value.content, value.writer, value.writeDate));
                        }
                    }
            }
            ...
}

 

Firebase Reference에 ValueEventListener을 추가하는데 이때 onDataChange 메소드를 작성했었다.

여기에서 파라미터로 DataSnapshot을 가지는데, for Each문을 중첩해서 사용함으로써

트리 안쪽에 있는 데이터까지 접근할 수 있었다.

 

두번째로 고민된 것은, 자유/익명게시판을 Fragment로 구현해서 그런지

view를 return하는 것보다 DB에서 데이터를 가져오는 것이 더 나중에 실행되는 것이었다.

 

https://t-okk.tistory.com/12

구글링하다가 찾은 위의 포스팅을 참고하여 구현했다.

 

결과적으로 PostAdapter.java에 작성했었던 setItems와,

public void setItems(ArrayList<Post> items){
        this.items = items;
    }

 

포스팅에서 참고한 adapter.notifyDataSetChanged(); 를 이용했다.

 

1. 게시판 게시물 목록 구현 (RecyclerView 이용)

구현 방법

1) 기존 화면에 RecyclerView 태그 추가

2) 게시물.java 생성 <- 속성, 생성자, Get/Set 함수 생성

3) 게시물_item.xml 생성

  -현재는 CardView 사용, 추후에 레이아웃 수정할 예정

4) 게시물Adapter.java 생성

5) 기존 화면 관련 java 파일에 코드 추가

  -setLayoutManager, 게시물Adapter 이용

 

2. 자체 테스트 문항 목록 구현 (RecyclerView 이용)

구현 방법은 게시판과 동일함

 

3. 마이페이지 레이아웃 수정

-스마트폰 화면 크기와 상관 없이 화면에 내용이 꽉 차도록 수정

날씨 서버 외부 API 사용하기

  • API 유형
    1. 서버가 제공하는 도메인 사용
    1. 서버가 만들어놓은 함수 사용
  • 이번에 사용할 날씨 서버 API → 외부 API를 도메인 형태로 요청하는 방식
  • 다음 순서로 API를 통해 날씨를 가져올 것
    1. 현재 위치(좌표) 데이터를 가져오기
    1. 위치 데이터를 이용해서 현재 위치 날씨 데이터 가져오기

 

위치 데이터 가져오기

💡
expo-location 도구를 활용할 것!

expo-location 설치하기

expo install expo-location

 

코드 작성

import * as Location from "expo-location";
  • expo-location은 Location 이라는 이름으로 사용할 것이다.

 

useEffect(()=>{
    setTimeout(()=>{
				...
        getLocation()
    },1000)
  },[])
  • 앱이 실행됐을 때 getLocation() 함수를 이용해 현재 위치 데이터를 가져온다.

 

const getLocation = async () => {
    try {
      //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
      await Location.requestPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      console.log(locationData)

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

 

  • requestPermissionAsync() : 권한을 물어보는 팝업
  • 허용 선택 → 아래 코드 진행
  • getCurrentPositionAsync() : 현재 위치 좌표를 가져오는 함수

 

💡
함수 실행 순서를 정해주는 async / await
  • 무거운 기능(네트워크, 파일 시스템 접근 등)을 다룰 때 코드를 작성한 순서대로 실행하지 않을 수 있다.
  • 자바스크립트는 비동기 특성을 가지고 있어서 먼저 끝나는 함수부터 결과값을 가져온다.
  • 순차적으로 함수를 실행시키기 위해 async, await을 사용한다.
  • 함수 앞에 async를 붙이고 그 내부에서 사용될 함수 앞에 await를 붙인다.

 

날씨 데이터 가져오기

💡
openweathermap api 를 사용할 것!

 

axios 도구 설치

  • 서버가 제공하는 도메인 형식의 API를 사용하려면 axios 도구가 필요하다.
yarn add axios

 

코드 작성

import axios from "axios"
  • 설치한 axios 도구 import

 

const getLocation = async () => {
    try {
      await Location.requestPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      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

    } catch (error) {
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
    }
  }
  • expo-location을 이용하여 경도, 위도 가져오기
  • axios.get(도메인 주소)을 이용해서 날씨 데이터를 가져온다.
  • 도메인 주소에는 경도, 위도, 발급받은 키가 들어간다.

 

const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
})
  • 날씨 정보를 상태(state)로 관리

 

const getLocation = async () => {
	try{
		...
		setWeather({
			temp,condition
		 })
	}
	...
}
  • getLocation 함수 내에서 날씨 정보를 가져온 뒤 상태 반영

 

파이어베이스 (Firebase) 사용하기

서버리스 (serverless)

  • 직접 서버를 구현, 구성할 필요 없이 서버 기능을 제공하는 곳에서 서비스를 사용하면 된다.

 

파이어베이스 (Firebase) 란?

  • 구글에서 만든 서버리스 서비스
  • 실시간 데이터베이스, 인증, 스토리지, 호스팅 등 많은 기능 제공
  • 이용 절차
    1. 파이어베이스 가입하기
    1. 파이어베이스 프로젝트 생성
    1. 사용할 파이어베이스 서비스 활성화

 

파이어베이스를 앱에 연결하기

  • 자바스크립트(웹 개발 언어)로 리액트 네이티브 프로젝트 진행 중 → 웹 앱 선택
  • 스크립트를 복사하여 Firebase SDK 추가하기

 

expo 도구 설치

expo install firebase

 

firebaseConfig.js 생성

import firebase from 'firebase/app';

// 사용할 파이어베이스 서비스 주석을 해제해서 사용
//import "firebase/auth";
import "firebase/database";
//import "firebase/firestore";
//import "firebase/functions";
import "firebase/storage";

// Initialize Firebase
const firebaseConfig = {
  apiKey: "AIzaSyBMXK-Vpv5wW6TYEKxMdzLGsKn4UY-vNX0",
  authDomain: "sparta-myhoneytip-yewon.firebaseapp.com",
  projectId: "sparta-myhoneytip-yewon",
  storageBucket: "sparta-myhoneytip-yewon.appspot.com",
  messagingSenderId: "44731016366",
  appId: "1:44731016366:web:3e1b8f1f29b3440ddbb93f",
  measurementId: "G-E4WCLCL8P6"
};

if (!firebase.apps.length) {
    firebase.initializeApp(firebaseConfig);
}

export const firebase_db = firebase.database()

 

파일 스토리지 (storage)

  • 파이어베이스 사이트에서 Storage 생성
  • 폴더 생성, 파일 업로드 가능

 

리얼타임 데이터베이스

  • JSON 형태로 데이터를 저장/관리한다.
  • 리얼타임 데이터베이스 생성 후 data.json 파일 가져오기

 

전체 데이터 읽기

  • 리얼타임 데이터베이스 고유 주소를 이용해서 데이터를 가져온다.
  • 전체 데이터 읽는 코드 :
firebase_db.ref('/tip').once('value').then((snapshot) => {
   let tip = snapshot.val();
})
  • 실제 적용 :
import {firebase_db} from "../firebaseConfig"

useEffect(()=>{
    setTimeout(()=>{
				...
        firebase_db.ref('/tip').once('value').then((snapshot) => {
          console.log("파이어베이스에서 데이터 가져왔습니다!!")
          let tip = snapshot.val();
          setState(tip)
          setCateState(tip)
          getLocation()
          setReady(false)
    },1000)

    
  },[])
  • useEffect 함수 안에서 firebase 전체 데이터 읽어오기

 

특정 데이터 읽기

  • Card.js 수정
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',{idx:content.idx})}}>
  • 기존에는 navigate에 content를 넘겨주었음
  • content의 idx만 넘기도록 수정

 

  • DetailPage.js
import {firebase_db} from "../firebaseConfig"
...
useEffect(()=>{
		...
    const { idx } = route.params;
    firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
        let tip = snapshot.val();
        setTip(tip)
    });
},[])
  • route.params를 이용해 idx 받아서 firebase 데이터베이스에서 읽어오기

 

expo-contants

  • 사용자마다 고유한 ID값을 생성해준다.
expo install expo-constants
import Constants from 'expo-constants';

console.log(Constants.installationId)

 

데이터 쓰기

import {firebase_db} from "../firebaseConfig"
import Constants from 'expo-constants';

const like = () => {
        const user_id = Constants.installationId;
        firebase_db.ref('/like/'+user_idx+'/'+ tip.idx).set(tip,function(error){
            console.log(error)
            Alert.alert("찜 완료!")
        });
    }
  • 팁 찜하기 버튼을 클릭하면 firebase 데이터베이스에 유저의 아이디와 팁 인덱스를 이용하여 데이터를 추가한다.

 


API를 이용해서 서버에서 날씨 정보 가져오기!

expo-location을 이용해서 현재 위치를 받아오고

openweathermap api를 이용해서 현재 내 위치의 날씨 정보를 가져온다.

두번째 사진 오른쪽 상단에 실제 날씨 정보가 출력된 것을 확인할 수 있었다.

 

리액트와 expo, 그외 기타 사이트에서 다양한 API를 제공해줘서 앱 개발하는데 있어서 매우 편리한 것 같다.

API를 활용하니 생각보다 내 위치 및 날씨 데이터를 가져오는 절차가 간단했다!

앞으로 앱을 개발할 때 날씨 정보 뿐만 아니라 다양한 API를 찾아보고 활용하도록 해야겠다.

 

구글 파이어베이스 DB를 이용하여 팁 목록과 나의 찜 목록을 다룰 수 있었다.

사진은 4주차 숙제로 구현한 팀 찜하기 기능과 찜 목록이다.

 

파이어베이스는 한이음 프로젝트 하면서 이미 접해본 적이 있기 때문에 낯설지 않았다.

안드로이드 스튜디오에서는 파이어베이스와 연동하기 위한 절차가 꽤 길었는데 (파이어베이스 sdk 파일 추가, gradle 파일에 dependecy 추가 등등..)

리액트 네이티브는 웹앱 (자바스크립트를 기반으로 함) 이라 그런지 간단히 스크립트만 복붙하여 연동을 마칠 수 있었다.

 

대신 DB에서 데이터를 삭제할 때 함수를 잘못 작성했다가 console.log가 무한 반복된다거나 하는 오류들을 겪어서

다시 그런 실수를 반복하지 않도록 삭제할 때 작성할 코드를 조심히 작성해야겠다는 생각이 들었다.

remove 함수를 이용해서 DB 데이터를 삭제하는 방법은 공식 문서를 참고하여 이해했다.

( https://firebase.google.com/docs/reference/js/firebase.database.Reference?authuser=2#remove )

 

이제 리액트 네이티브에서 파이어베이스를 다루는 방법도 알게 되었으니

내 개인 프로젝트로 만들고 있는 일정 관리 앱에도 유용하게 사용할 수 있을 것 같다!

리액트 컴포넌트, 속성, 상태

리액트 기초지식

  1. 컴포넌트(Component) : 정해진 요소를 사용하여 만든 화면의 일부분
  1. 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 방법 = 데이터
  1. 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 = 데이터 전달
  1. useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳
  • Component에 있는 Props가 데이터를 전달 → Component 안의 State가 데이터를 관리

 

컴포넌트(Component)

  • UI 요소, 화면의 모든 부분

 

  • Card.js
//비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함
export default function Card({content}) {
    return (<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>
      </View>)
}
  • 비구조 할당 방식 이용

 

  • MainPage.js
import Card from '../components/Card';

...

<View style={styles.cardContainer}>
	{/* 하나의 카드 영역을 나타내는 View */}
  {
		 tip.map((content,i)=>{
	     return (<Card content={content} key={i}/>)
     })
  }    
</View>
  • MainPage.js에서 Card.js로 content라는 데이터를 넘겼다.

 

속성(Props)

  • 컴포넌트에 데이터를 전달하는 것
  • key와 value의 형태 (ex. content={content})
  • 컴포넌트에 대해 map으로 반복문 → 반드시 인덱스(i)를 key={i}로 속성 전달

 

상태(useState)와 useEffect

  • 컴포넌트마다 보유, 관리하는 데이터를 상태라고 한다.
  • 리액트에서 상태(state) → useState로 생성, setState로 변경 가능

useState

  • 리액트에서 화면은 데이터에 따라 변경되고, 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀐다.
  • UI = component(state)

useEffect

  • 화면이 로딩되고 가장 먼저 실행되는 함수
useEffect(()=>{
	화면이 그려지고 나서 가장 먼저 실행돼야 할 코드
},[])
  • 데이터를 준비(데이터를 받고 상태(state)에 반영)할 때 사용
  • 화면이 그려진다 → useEffect가 데이터를 준비 → 상태 데이터가 업데이트 되었으니 다시 화면이 그려진다

 

const [state, setState] = useState([])

useEffect(()=>{
	setState(data)
},[])
  • state : 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
  • setState : state를 변경시킬 때 사용해야하는 함수
  • useState() 안에 전달되는 것 → state 초기값
  • useEffect 함수로 data.json에서 가져온 데이터를 state에 담음

 

로딩 화면

  • useEffect는 화면이 그려지고 나서 실행되는데, 현재는 화면을 그릴 때 이용할 state에 값이 설정되어 있지 않은 상태임
  • 따라서 로딩 화면을 먼저 그린 후 → useEffect를 통해 state를 설정하고 나서 → 메인 화면을 그려야 오류가 나지 않음

 

  • Loading.js 파일 생성 후 MainPage.js를 다음과 같이 수정
import Loading from '../components/Loading';

const [state,setState] = useState([])
const [ready,setReady] = useState(true) //초기 ready : True

useEffect(()=>{
    setTimeout(()=>{
        setState(data)
        setReady(false)
    },1000) //1000 = 1초
 },[])
  • 상태는 여러개 만들어도 된다.
  • setTimeout으로 1초 뒤에 상태 관리에 들어가게 함

 

return ready ? <Loading/> :  (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>나만의 꿀팁</Text>
			 <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
      <Image style={styles.mainImage} source={main}/>
      <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
        <TouchableOpacity style={styles.middleButton01}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton02}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton03}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
        <TouchableOpacity style={styles.middleButton04}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
      </ScrollView>
      <View style={styles.cardContainer}>
         {
          tip.map((content,i)=>{
            return (<Card content={content} key={i}/>)
          })
        }
      </View>
    </ScrollView>
  );
  • 삼항연산자를 이용해서 ready가 true이면 로딩 화면을, false이면 메인 화면을 그린다.
  • 실행 순서
    1. 초기 ready 상태는 true여서 로딩 화면을 먼저 그림
    1. useEffect가 실행돼서 1초 뒤에 state 값을 설정, ready를 false로 변경
    1. ready 상태가 변경되었으니 자동으로 retrun ready ? ... 가 실행됨
    1. false이므로 메인 화면이 그려짐

 

카테고리 기능

MainPage.js

const [state,setState] = useState([]) //전체 리스트
const [cateState,setCateState] = useState([]) //카테고리 선택에 따른 리스트
const [ready,setReady] = useState(true)

useEffect(()=>{
	setTimeout(()=>{
		let tip = data.tip;
		setState(tip)
		setCateState(tip) //처음 로딩 시 전체 리스트로 초기화
		setReady(false)
	},1000)
},[])
  • 선택한 카테고리에 따라 보여줄 리스트를 저장하는 cateState
  • useEffect에서 cateState를 tip (전체 데이터) 으로 초기화

 

<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={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
</ScrollView>
  • 카테고리 버튼마다 onPress 함수를 설정함
  • category 함수에 카테고리 이름을 넘겨주도록 함

 

const category = (cate) => {
	if(cate == "전체보기"){
		//전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
		setCateState(state)
		}else{ //전체(state)에서 선택한 카테고리 데이터만 추출
			setCateState(state.filter((d)=>{
			return d.category == cate //true일때 cateSate에 넣는다
		}))
	}
}
  • category 함수에는 cate(카테고리 이름) 파라미터가 있음
  • 전체보기를 클릭했다면 cateState를 전체 데이터(state)로 변경
  • 그 외 카테고리 버튼을 클릭했다면 전체 데이터(state) 중 해당 카테고리에 속하는 데이터만 cateState에 저장 (filter 함수 이용)

 


 

스택네비게이터

네비게이션

  • 컴포넌트들을 페이지화 시켜서 해당 페이지끼리 이동을 가능하게 하는 라이브러리

 

도구 설치

yarn add @react-navigation/native
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

 

스택네비게이션

  • 컴포넌트에 페이지 기능 부여, 컴포넌트에서 컴포넌트로 이동할 수 있도록 해준다.
  • Stack.Screen : 페이지
  • Stack.Navigator : 책갈피 (모든 페이지를 등록시킬 곳)

 

설치

yarn add @react-navigation/stack

 

적용하기

  • StackNavigator.js
import { createStackNavigator } from '@react-navigation/stack';
//페이지로 만들 컴포넌트 불러오기
import DetailPage from '../pages/DetailPage';
import MainPage from '../pages/MainPage';

const Stack = createStackNavigator();
const StackNavigator = () =>{
	return (
		<Stack.Navigator
			screenOptions={{
				headerStyle: {
				backgroundColor: "black",
				borderBottomColor: "black",
				shadowColor: "black",
				height:100
			},
			headerTintColor: "#FFFFFF",
			headerBackTitleVisible: false
		}}
	  >
			//페이지로 만들 컴포넌트
	    <Stack.Screen name="MainPage" component={MainPage}/>
	    <Stack.Screen name="DetailPage" component={DetailPage}/>
    </Stack.Navigator>
	)
}

export default StackNavigator;

 

  • App.js
import React from 'react';
import { StatusBar } from 'expo-status-bar';
import {NavigationContainer} from '@react-navigation/native';
import StackNavigator from './navigation/StackNavigator'

export default function App() {
	console.disableYellowBox = true;

  return ( 
  <NavigationContainer>
    <StatusBar style="black" />
    <StackNavigator/>
  </NavigationContainer>);
}
  • 앱의 가장 최상위 컴포넌트인 App.js에 네비게이션 기능을 달아준 것
  • NavigationContainer 태그로 StatusBar(상태바)과 StackNavigator(직접 만든 스택네비게이션)을 감싼다.

 

💡
Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigationroute라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있다!
navigation.setOptions({
   title:'나만의 꿀팁'
})
navigation.navigate("DetailPage")
navigation.navigate("DetailPage",{
  title: title
})

const { title} = route.params; //비구조 할당방식
  • navigation
    1. setOptions : 제목, 헤더 옵션 스타일 등 바꿀 수 있음
    1. navigate : 해당 페이지로 이동하는 함수 (StackNavigator.js의 Stack.screen name 속성), 두번째 매개변수로 딕셔너리 데이터 전달
  • route : navigate로 인해 전달받은 데이터를 받는 딕셔너리

 


 

공유하기 기능

  • React Native 자체에서 제공하는 기능이다.
import { Share } from "react-native";

export default function DetailPage({navigation,route}) {
		...
    const share = () => {
        Share.share({
            message:`${tip.title} \n\n ${tip.desc} \n\n ${tip.image}`,
        });
    }
    return (
        <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={()=>popup()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.button} onPress={()=>share()}><Text style={styles.buttonText}>팁 공유하기</Text></TouchableOpacity>
                </View>
            </View>
        </ScrollView>
    )
}
  • share 함수를 이용해 공유할 때 전달할 제목, 설명, 이미지 등을 설정할 수 있음

 


 

외부 링크 클릭 이벤트

도구 설치

expo install expo-linking

 

적용하기

import * as Linking from 'expo-linking';

const link = () => {
	Linking.openURL("https://spartacodingclub.kr")
}

<TouchableOpacity style={styles.button} onPress={()=>link()}><Text style={styles.buttonText}>외부 링크</Text></TouchableOpacity>

 


리액트를 기반으로 하는 리액트 네이티브를 공부하고 있기 때문에

리액트의 기초 지식인 컴포넌트/속성/상태에 대해 공부했다.

상태의 개념이 처음 접했을 때 헷갈렸는데, 상태의 변화가 일어날 때 화면을 새로 그린다는 것에 유의하자.

스택네비게이터를 이용해 여러 페이지간의 이동을 구현하는 방법을 알게 되어 좀더 앱다운 앱을 만들 수 있게 되었다.

그 외 공유하기 및 외부 링크 클릭 이벤트를 구현하는 방법을 익혀서 추후에 앱을 출시하게 된다면 유용하게 사용할 수 있을 것 같다!

스택네비게이션의 이용을 여러 번 복습해서 익숙해질때까지 공부해야겠다.

 

카드를 클릭하여 해당 내용의 상세 페이지로 이동한 모습

 

이번 주 숙제 : 어바웃 페이지에서 외부 링크 열기, 꿀팁찜 페이지 만들어서 네비게이터에 연결하기

  • Adapter 패턴 사용

 

1. RecyclerView 태그 추가

  • 원하는 화면 xml 파일에 다음과 같이 추가
<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" />

 

2. 데이터.java 생성

  • model 폴더
  • 변수, 생성자, Get/Set 함수 만들기 (우클릭 → Generate)

 

3. 데이터_item.xml 생성

  • 각 데이터별 카드 디자인
  • CardView를 활용함
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardBackgroundColor="#FFFFFF"
        app:cardCornerRadius="10dp"
        app:cardElevation="5dp"
        app:cardUseCompatPadding="true">

    </androidx.cardview.widget.CardView>

</LinearLayout>

 

4. 데이터Adapter.java 생성

  • extends RecyclerView.Adapter<데이터Adapter.ViewHolder>
  • public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType){}
  • public void onBindViewHolder(ViewHolder viewHolder, int position){}
  • public int getItemCount(){}
  • static class ViewHolder extends RecyclerView.ViewHolder{} 등등 추가
package org.techtown.recyclerview;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class 데이터Adapter extends RecyclerView.Adapter<데이터Adapter.ViewHolder> {
    ArrayList<데이터> items = new ArrayList<데이터>();

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
        View itemView = inflater.inflate(R.layout.person_item, viewGroup, false);

        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
        데이터 item = items.get(position);
        viewHolder.setItem(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        TextView textView; //데이터_item.xml 내부의 요소

        public ViewHolder(View itemView){
            super(itemView);

            textView = itemView.findViewById(R.id.textView);
        }

        public void setItem(데이터 item){
            textView.setText(item.getName());
        }
    }

    public void addItem(데이터 item){
        items.add(item);
    }

    public void setItems(ArrayList<데이터> items){
        this.items = items;
    }

    public 데이터 getItem(int position){
        return items.get(position);
    }

    public void setItem(int position, 데이터 item){
        items.set(position, item);
    }
}

 

5. 원하는 화면(RecyclerView를 포함한 화면)과 관련된.java에 코드 추가

  • LayoutManager 이용해서 recyclerView 레이아웃 설정
  • Adapter 생성 후 addItem
  • RecyclerView에 Adapter을 이용해서 데이터 넘기기
RecyclerView recyclerView = findViewById(R.id.recyclerView);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        PersonAdapter adapter = new PersonAdapter();

        adapter.addItem(new 데이터("데이터1","데이터1속성"));
				adapter.addItem(new 데이터("데이터2","데이터2속성"));
				adapter.addItem(new 데이터("데이터3","데이터3속성"));
        recyclerView.setAdapter(adapter);

 

+ Recent posts