keyframes

  • styled-components 안에 들어있음
  • 웹에서 애니메이션을 구현할 때 transition과 animation이라는 스타일 속성을 많이 사용함
  • transition은 단순한 엘리먼트 상태 변화 시, animation은 다이나믹한 효과를 줄 때 적합
  • keyframes는 animation에서 사용하는 속성 중 하나!
  • css에서 keyframes를 사용하는 방법
@keyframes boxFade {
	0% {
		opacity: 1;
	}
	50% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

 

styled-components에서 keyframes 사용하기

styled-components 패키지 설치

yarn add styled-components

위 아래로 움직이는 애니메이션

const Box = styled.div`
	width: 100px;
	height: 100px;
	border-radius: 50px;
	background: green;
	position: absolute;
	top: 20px;
	left: 20px;
	animation: ${boxFade} 2s 1s infinite linear alternate;
`;
  • styled 안에서 변수를 이용하기 위해 ${변수이름} 형식으로 작성
  • animation duration : 2s, 1s
  • animation-iteration-count : infinite
const boxFade = keyframes`
	0% {
		opacity: 1;
		top: 20px;
	}
	50% {
		opacity: 0;
		top: 400px;
	}
	100% {
		opacity: 1;
		top: 20px;
	}
`;

 

스크롤 움직이기

  • window.scrollTo() 이용
<button onClick={() => { window.scrollTo(0, 0); }}>위로가기</button>
  • 웹페이지 상단으로 스크롤x 이동함
<button onClick={() => { window.scrollTo({top: 0, left: 0, behavior: "smooth"}); }}>위로가기</button>
  • behavior을 추가해서 자연스러운 스크롤 구현
  • 좌표뿐만 아니라 ref를 통해 특정 엘리먼트의 위치로 스크롤 시킬 수도 있다.

며칠 전에 YouTube API 연결을 시도할 때만 해도 갈 길이 멀다고 생각했는데

생각보다 빠른 기간 안에 기능을 거의 다 구현한 듯 하다.

 

오늘은 주로 영상 시청 관련 디테일을 추가하고, 디버깅을 하였다.

개발 내용에 앞서 에뮬레이션 이용 후기를 적도록 하겠다.

 

에뮬레이션 이용 후기

한이음 ICT 멘토링에서 에뮬레이션 이용을 지원해줘서 우리 팀은 8월 2주차 이용을 신청했다.

지원받은 에뮬레이션은 클라우드 기반 테스트 서비스인 Remote TestKit인데 Web 사용 방법과 Client 사용 방법이 있었다.

안드로이드 스튜디오에서 에뮬레이션을 연결하기 위해서는 가상 ADB(Android Debug Bridge) 기능을 이용해야 하므로

Client 사용 방법에 따라 응용 프로그램을 설치하고 이용해보았다.

 

 

응용 프로그램을 실행해보면 대여 가능한 단말기 리스트가 쭉 나오고 단말기 정보로는 통신사, 제조사, 단말기명, OS, 단말 위치 등이 나와있었다.

대여 시간은 30분 또는 60분으로 선택 가능했고 여러 단말기를 대여해서 테스트를 해봤다.

평소에 개발할 땐 하나의 기기에서 테스트를 하는데, 에뮬레이션을 이용하면 다양한 기종의 스마트폰에서 앱을 테스트해볼 수 있다는 점이 좋았다!

 

 

개발 내용

1. 시청한 영상 개수 출력

총 몇 개의 영상이 있고 그 중에서 몇 개의 영상을 시청했는지 사용자에게 보여주도록 하였다.


2. 마음 온도 60점 달성 시 게시판 기능 해제 알림 팝업

마음 채우기 영상을 시청하여 마음 온도가 60점 이상이 될 경우, 게시판 기능이 해제된다.

영상 시청 화면에서 뒤로 가기 버튼을 클릭할 때 영상 시청 완료 팝업 이후 해당 팝업을 띄운다.


3. 이전에 시청했던 영상 팝업 추가

이미 시청했던 영상은 다시 본다고 해도 마음 온도가 오르지 않는다.

따라서 영상 시청 전에 미리 팝업을 띄워 사용자에게 알려주도록 하였다.

다만 마음 채우기 화면은 AppCompatActivity가 아닌 YouTubeBaseActivity를 상속받기 때문에

팝업에 기본 폰트가 적용되지 않는다... 폰트 적용 방법을 찾아봐야겠다.

4. 화면 회전 방지
에뮬레이션으로 테스트하며 이것저것 버튼을 눌러보다가 화면 회전 버튼을 눌렀더니

회전이 되어 화면에 있는 버튼과 각종 레이아웃이 찌부(...)되는 것이다..

그래서 회전 방지하는 방법을 찾아봤는데 AndroidManifest.xml에서 각 액티비티마다 android:screenOrientation="portrait" 를 추가하면 된다고 한다.

<activity android:name=".MainActivity" android:screenOrientation="portrait"/>

이런 식으로 모든 액티비티에 대해 screenOrientation을 추가해줬더니 가로로 회전이 되지 않았다.

 

5. 일기장 캘린더 사라지는 문제 해결

메인화면에서 일기장 화면으로 넘어갈 때 어떨 때는 캘린더가 뜨고, 어떨 때는 안뜨는 버그가 있었다.

열심히 콘솔 찍어보면서 로직 상 잘못된 부분이 없는지 확인해봤는데

로직이 잘못된건 아니었고 스레드를 생성해서 그 안에서 어댑터의 notifyDataSetChanged() 메소드를 실행하며 해결!!

https://stackoverflow.com/questions/24740557/notifydatasetchanged-not-working-on-recyclerview/24740763

 

notifyDataSetChanged not working on RecyclerView

I am getting data from server and then parsing it and storing it in a List. I am using this list for the RecyclerView's adapter. I am using Fragments. I am using a Nexus 5 with KitKat. I am using

stackoverflow.com

이 답변 적어주신 분께 진심 감사합니다...... 스레드 안에서 작동해야 제대로 결과가 반영된다는 사실을 알게 되었다.

안드로이드 스튜디오에서 YouTube API를 이용하여 유튜브 영상을 앱 화면에 띄우는 것은 어제 완료했었다.

오늘은 본격적으로 마음 채우기 (영상 시청) 기능을 구현했다.

 

구현하면서 고려한 점

1. 영상 관련 데이터를 어디에, 어떻게 저장할지

  • 영상 정보(id, 제목, 설명)는 json 파일로 프로젝트 자체에 저장
  • 사용자의 영상 시청 기록은 firebase DB에 저장하기로 함

2. 영상 시청 완료 기준

  • 해당 영상의 길이만큼 시청해야 완료
  • 단, 영상 하단의 스크롤바를 이용하여 시간을 건너뛰는 것을 고려하여 실제로 영상이 재생된 시간을 구해야함

3. 마음 온도 높이는 기준

  • 시청 기록이 없는 영상에 대해서만 마음 온도 증가, 이미 시청 완료한 영상은 마음 온도가 오르지 않음
  • 하나의 영상을 시청했을 때 5점 증가

 

개발 내용

1. 마음 채우기 영상 목록 ListView 구현

지금까지 앱을 개발하면서 RecyclerView, ListView를 많이 이용하였기 때문에

이번에도 Model을 만들고 Adapter을 생성해서 ListView를 만들었다.

 

2. 영상 누적 시청 시간 체크

이 부분을 구현하면서 시간이 꽤 걸렸는데, 앞서 고려한 점에서 언급한 것처럼

영상을 제대로 시청하지 않고 타임라인을 이동하여 영상 시청을 끝내는 것을 방지해야 했다.

실제로 재생된 시간만 리턴하는 메소드를 찾아보았으나 YouTube API에서는 해당 메소드를 제공하지 않는 것으로 보였다.

따라서 스레드를 이용하여 직접 구현하였다.

 

스레드는 1초마다 cnt 값을 높이면서 재생한 시간(초)을 높인다.

이것이 영상의 길이와 일치하게 되면 스레드는 종료되고 영상 시청 완료 처리를 하는 것이다.

영상 재생 시작, 일시정지 후 다시 재생을 할 때 스레드가 동작하도록 하고

일시정지 (또는 정지) 시에는 스레드를 일시중지시키도록 하였다.

 

YouTube API 관련 메소드들은 공식 문서에서 찾아볼 수 있었다

https://developers.google.com/youtube/android/player/reference/com/google/android/youtube/player/YouTubePlayer?hl=ko 

 

YouTubePlayer  |  YouTube for Android  |  Google Developers

Javadoc API documentation for YouTube Android Player API.

developers.google.com

 

3. 영상 시청 기록 DB 저장

정상적으로 영상 시청을 완료하고 스레드가 종료되면 firebase DB에 사용자의 uid와 영상의 id 정보를 저장한다.

 

4. 시청 완료한 영상 DB 조회 후 화면에 표시

firebase DB를 조회하여 시청 완료한 영상에 대해서 시청 완료 표시를 한다.

 

5. 영상 시청 화면에서 뒤로가기 버튼 클릭 시 팝업 띄우기

영상 시청을 완료한 후 눌렀을 땐 마음 온도가 올랐다는 팝업을 띄우고

영상 시청 중간에 뒤로가기를 눌렀으면 시청을 완료하지 않았음(마음 온도가 오르지 않음)을 알리는 팝업을 띄운다.

 

6. 메인 화면 메뉴 이미지 추가

메인 화면 디자인을 보완하기 위해 팀원분이 직접 그린 이미지를 추가하였다.

 


 

마음 채우기 화면 (영상 목록 화면), 영상의 시청 완료 여부 확인 가능

 

영상 시청 화면, 시청 시간을 seekbar을 이용해서 나타냈다.

 

영상을 끝까지 시청했을 때 시청 완료 팝업(왼), 중간에 나가는 경우엔 완료되지 않았다는 팝업(오)을 띄운다.

 

이미지를 추가한 메인 화면

회의 내용

오늘 오전 9시에 회의에서 마음 채우기 프로그램의 영상 개수, 마음 온도 높이는 기준과 게시판에 대해 얘기했다.

 

교화 프로그램(마음 채우기)에서 제공할 영상의 개수는 10개,

사용자의 영상 시청 시간을 측정하여 마음 온도를 높이기로 했다.

각 영상마다 시청했는지 체크하는 것을 구현해야하는데 이 부분 구현이 조금 오래 걸릴지도 모르겠다.

 

주요 변동사항은 게시판 기능에 관한 것이었는데,

기존에 있는 자유게시판/익명게시판의 분리, 게시판 기능 제한의 모호성에 대해 논의한 결과

게시판의 카테고리를 세분화하여 질문게시판과 꿀팁게시판을 추가하기로 했고

마음 온도가 60 보다 낮은 사용자는 아예 게시판에 접근이 불가능 (조회도 불가능) 하게 제한하는 것으로 결정했다.

 

그 외 회원가입 시 이메일 인증과 닉네임 중복 체크 추가, 메인 화면의 마음 온도 실시간 반영 등을 구현하기로 했다.

 

개발 내용

1. YouTube API를 이용한 YouTubePlayerView 초기화 문제 해결

- 갤럭시S20+로 테스트를 해봤는데 유튜브 플레이어의 초기화에서 오류가 발생했었음

- 다른 팀원들은 오류가 발생하지 않았는데 어째서 나만...? 고민

   → 구글링 결과, Android 11 (API 30+)는 AndroidManifest.xml에 추가적으로 코드를 작성해야했음

https://github.com/PRNDcompany/YouTubePlayerView/issues/12

 

YouTubePlayerView initialise error : SERVICE_MISSING · Issue #12 · PRNDcompany/YouTubePlayerView

Hi all, yesterday I use this player and got the error hint "error when initial You Tube Player" on the YouTubePlayerView screen, but few days ago it worked fine. So I added YouTubePlayerV...

github.com

 

2. YouTube API 로드 후 썸네일 불러오기, 재생/일시정지 버튼 구현

- cueVideo(String videoId) 메소드를 이용하여 썸네일을 로드할 수 있었음 (아래 링크 참고)

https://developers.google.com/youtube/android/player/reference/com/google/android/youtube/player/YouTubePlayer?hl=ko 

 

YouTubePlayer  |  YouTube for Android  |  Google Developers

Javadoc API documentation for YouTube Android Player API.

developers.google.com

마음 채우기 화면에 접속했을 때 영상 썸네일이 자동으로 로드된 모습 (고앵이는 테스트용 영상..ㅎㅎ)

 

3. 챗봇 상담 기관 전화, 웹사이트 링크 활성화

- 챗봇의 응답 중 학교 외에 학교폭력 관련 도움을 받을 수 있는 센터 등의 전화번호, 웹사이트 주소를 포함하는 것이 있음

- 전화번호, 링크를 클릭하여 전화 화면 또는 인터넷으로 이동할 수 있도록 autoLink 속성을 추가함

https://saeatechnote.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-link%EA%B1%B8%EA%B8%B0

 

[android/안드로이드] link걸기

android link걸기 예제 /res/layout/activity_main.xml //자바 파일은 따로 안해도 됨. autoLink : TextView의 속성중 하나로 자동으로 링크가 걸린다. autoLink="none" -..

saeatechnote.tistory.com

전화번호, 웹사이트 url을 터치했을 때 연결 프로그램으로 넘어가는 모습

 

4. 게시판 카테고리 세분화

- 회의 결과에 따라 자유게시판/질문게시판/꿀팁게시판/익명게시판 으로 카테고리를 세분화함

- 익명게시판만 닉네임이 안보이고 댓글 기능을 제공하지 않음

질문게시판, 꿀팁게시판이 추가된 게시판 화면

 

5. 게시판 파이어베이스 DB 구조 변경

기존에 DB 구조는 다음과 같이 루트 하위 구조로 각 게시판이 있었는데

루트

ㄴ자유게시판

ㄴ익명게시판

게시판 카테고리가 늘어나면서 하나로 묶는게 나을 것 같다는 생각이 들었음

 

변경된 구조 :

루트

ㄴ게시판

  ㄴ자유게시판

  ㄴ질문게시판

  ㄴ꿀팁게시판

  ㄴ익명게시판

 

6. 게시판 이용 안내 팝업 추가

- 각 게시판의 용도를 안내하는 내용을 포함

게시판 화면에서 이용 안내를 확인할 수 있음

 

7. 마음 온도가 낮은 사용자의 게시판 기능 제한

- 메인 화면에서 게시판 버튼 클릭 시, 테스트 결과 유무 및 마음 온도 확인

- 마음 온도가 60 보다 낮은 사용자는 게시판을 이용할 수 없으며, 마음 채우기 기능을 이용할 것을 권장하는 팝업을 띄움

마음 온도가 낮은 상태로 게시판 이동 버튼을 클릭했을 때 나오는 팝업

 


 

오전 일찍 회의하고 회의 끝나자마자 개발했더니 이것저것 많이 구현한 듯 하다.

유튜브 API 연결을 하루만에 끝낸게 다행스럽다. (파이어베이스, 다이얼로그 플로우 연결할 때 꽤나 오래 걸렸던 기억이...)

기능 제한도 끝냈으니 이제 마음 채우기 페이지에 영상 목록을 띄우고,

각 영상을 시청할 때마다 마음 온도를 높이는 것 구현을 시작해야겠다!!

테스트 결과를 바탕으로 마음 온도를 산출하고, 마음 채우기 페이지를 안내하는 것을 중점적으로 구현했다!!😎

 

 

1. 테스트 결과 확인 후 마음 온도 DB에 저장

 

2. 테스트 결과 화면에 "마음 채우기 기능 해제" 팝업 띄우기

- 뒤로가기 버튼 클릭 시 팝업 출력

- 이동하기 버튼 클릭 시 마음 채우기 안내 페이지로 이동

 

3. 마음 채우기 안내 페이지 레이아웃 틀 잡기

- 마음 채우기 프로그램의 목적과 방법을 안내할 예정

- 마음 채우러 가기 버튼 클릭 시 영상 시청 화면으로 이동

 

4. 팝업(AlertDialog) 디자인 커스텀

- 커스텀 폰트 지정

- 직사각형 모양 -> 둥근 사각형

 

5. 메인화면 디자인 수정

- 각 버튼마다 아이콘 추가, 배경 색상 변경

 

6. 사이드바 헤더에 유저 닉네임, 학교 출력

 


 

테스트, 상담, 일기장, 게시판 기능을 다 구현해서 주요 기능 구현은 끝난 상태였는데,

생각보다 마음 채우기 (교화 프로그램 시청) 기능을 구현하기 위해 해야할 일들이 많다.

 

테스트 결과를 바탕으로 마음 온도 산출과 DB 저장은 끝냈으니

이제 본격적으로 Youtube API를 이용해서 영상 시청 기능을 제공하고,

게시판의 기능 제한을 구현해야겠다.

  • 리덕스 패키지 설치하기
yarn add redux react-redux

리덕스란?

  • 데이터를 한 곳에 몰아넣고 이곳저곳에서 꺼내볼 수 있게 하는, 상태 관리

State

  • 저장하고 있는 상태값(=데이터)
  • 딕셔너리 형태({[key]: value}) 형태로 보관

Action

  • 상태에 변화가 필요할 때 (가지고 있는 데이터를 변경할 때) 발생하는 것
{type: 'CHANGE_STATE', data: {...}} //type = 이름

ActionCreator

  • 액션 생성 함수, 액션을 만들기 위해 사용함
const changeState = (new_data) => { //액션을 리턴한다 return { type: 'CHANGE_STATE', data: new_data, } }

Reducer

  • 리덕스에 저장된 상태(=데이터)를 변경하는 함수
  • 액션 생성 함수 호출 → 액션을 만들면 → 리듀서가 현재 상태(=데이터)와 액션 객체를 받아서 → 새로운 데이터를 만들고 → 리턴
//기본 상태값을 임의로 지정했음
const initialState = {
	name: 'yewon'
}

function reducer(state = initialState, action) {
	switch(action.type){
		//action의 type마다 케이스문을 작성하면
		//액션에 따라 새로운 값을 리턴할 수 있음
		case CHANGE_STATE:
			return {name: 'garam'};
		default:
			return false;
	}
}

Store

  • 프로젝트에 리덕스를 적용하기 위해 만드는 것
  • 리듀서, 현재 애플리케이션 상태, 리덕스에서 값을 가져오고 액션을 호출하기 위한 내장 함수 포함

dispatch

  • 액션을 발생시키는, 스토어의 내장 함수

리덕스의 특징 3가지

  1. store은 프로젝트 하나 당 1개만 쓴다.
  2. store의 state는 오직 action으로만 변경할 수 있다.
  3. 어떤 요청이 와도 리듀서는 같은 동작을 해야한다.
  • 리듀서는 순수한 함수여야 한다.
    • 파라미터 외의 값에 의존하지 않는다.
    • 이전 상태는 수정하지(=건드리지) 않는다.
    • 파라미터가 같으면 항상 같은 값을 반환
    • 리듀서는 이전 상태와 액션을 파라미터로 받는다.

리덕스를 통한 리액트 상태 관리

  • 여러 컴포넌트가 동일한 상태를 보고 있을 때 유용하게 사용
  • 데이터를 관리하는 로직을 컴포넌트에서 빼면, 컴포넌트에서는 뷰만 관리할 수 있음 → 유지보수하기 좋다

상태 관리 흐름도

  1. 리덕스 store을 component에 연결
  2. component에서 상태 변화가 필요할 때 action 호출
  3. reducer을 통해 새로운 상태 값을 만듦
  4. 새 상태값을 store에 저장
  5. component는 새로운 상태값을 받아옴 (props를 통해 받아오므로 다시 렌더링됨)


덕스(ducks) 구조

  • 보통 리덕스를 사용할 때, action, actionCreator, reducer를 분리해서 작성한다.
  • 덕스 구조는 모양새로 묶는 대신 기능으로 묶어 작성한다.
  • (ex. 버킷리스트의 action, actionCreator, reducer을 한 파일에 넣는다.)

모듈 만들기

  • src 폴더 - redux 폴더 - modules 폴더 안에 bucket.js 만들기
  • bucket.js
//Action
const LOAD = 'bucket/LOAD';
const CREATE = 'bucket/CREATE';

//initialState
const initialState = {
	list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
};

//Action Creator
export const loadBucket = (bucket) => {
	return {type: LOAD, bucket};
}

export const createBucket = (bucket) => {
	return {type: CREATE, bucket};
}

//Reducer
export default function reducer(state = initialState, action = {}) {
	switch (action.type) {
		case 'bucket/LOAD':
			return state;

		case 'bucket/CREATE':
			const new_bucket_list = [...state.list, action.bucket];
			return {list: new_bucket_list};

		default:
			return state;
	}
}

 

  • src 폴더 - redux 폴더 - configStore.js 만들기
  • configStore.js
import {createStore, combineReducers} from 'redux';
import bucket from './modules/bucket';
import {createBrowserHistory} from 'history';

//브라우저 히스토리
export const history = createBrowserHistory();
//root 리듀서
const rootReducer = combineReducers({bucket});
//스토어
const store = createStore(rootReducer);

export default store;

리덕스와 컴포넌트 연결하기

  • 스토어를 불러오고 버킷리스트에 주입한다
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {BrowserRouter} from 'react-router-dom';

//버킷리스트에 리덕스를 주입해줄 프로바이더
import {Provider} from 'react-redux';
//연결할 스토어
import store from './redux/configStore';

ReactDOM.render(
	<Provider store={store}>
		<BrowserRouter>
			<App />
		</BrowserRouter>
	</Provider>,
	document.getElementById('root')
);

컴포넌트에서 리덕스 데이터 사용하기

1. 클래스형 컴포넌트에서 리덕스 데이터 사용하기

  • 리덕스 모듈과 connect 함수 불러오기
  • 상태값을 가져오는 함수(mapStateToProps)와 액션 생성 함수를 부르는 함수(mapDispatchToProps) 만들기
  • connect로 컴포넌트와 스토어 엮기
  • this.state에 있는 list를 지우고 스토어에 있는 값으로 바꾸기
  • setState를 this.props.create로 바꾸기
  • App.js
import React from 'react';
import {withRouter} from 'react-router';
import {Route, Switch} from 'react-router-dom';

//리덕스 스토어와 연결하기 위해 connect 호출
import {connect} from 'react-redux';
//리덕스 모듈에서 액션 생성 함수 가져오기
import {loadBucket, createBucket} from './redux/modules/bucket';

//스토어가 가진 상태값을 props로 받아오기 위해
const mapStateToProps = (state) => ({
	bucket_list: state.bucket.list,
});

//액션 생성 함수를 props로 받아오기 위한 함수
const mapDispatchToProps = (dispatch) => ({
	load: () => {
		dispatch(loadBucket());
	},
	create: (new_item) => {
		dispatch(createBucket(new_item));
	}
});

class App extends React.Component {
	constructor(props){
		super(props);
		this.state = {
		};
		this.text = React.createRef();
	}

	componentDidMount(){
		console.log(this.props);
	}

	addBucketList = () => {
		const new_item = this.text.current.value;
		this.props.create(new_item);
	};

	render(){
		return(
			<div className="App">
				<Container>
					<Title>내 버킷리스트</Title>
					<Line/>
					<Switch>
						<Route path="/" exact
							render={(props) => (
								<BucketList
									list={this.props.bucket_list}
									history={this.props.history}
								/>
							)}
						/>
						<Route path="/detail" component={Detail}/>
						<Route
							render={(props) => <NotFound history={this.props.history} />}
						/>
					</Switch>
				</Container>
				<Input>
					<input type='text' ref={this.text}/>
					<button onClick={this.addBucketList}>추가하기</button>
				</Input>
			</div>
		);
	}
}

//connect로 묶기
export default connect(mapStateToProps, mapDispatchProps)(withRouter(App));

 

2. 함수형 컴포넌트에서 리덕스 데이터 사용하기

  • 훅을 사용해서 액션 생성 함수를 부르고 스토어에 저장된 값을 가져온다
  • BucketList.js에 useSelector() 적용
import React from 'react';
//reudx hook 불러오기
import {useDispatch, useSelector} from 'react-redux';

const BucketList = (props) => {
	const bucket_list = useSelector(state => state.bucket.list);

	return (
		<ListStyle>
			{bucket_list.map((list, index) => {
				return (
					<ItemStyle
						className='list_item'
						key={index}
						onClick={() => {
							props.history.push('/detail');
						}}
					>
						{list}
					</ItemStyle>
				);
			})}
		</ListStyle>
	);
};

export default BucketList;

 

3. 상세페이지에서 버킷리스트 내용 띄우기

  • Detail.js
import React from 'react';
//redux hook 불러오기
import {useDispatch, useSelector} from 'react-redux';

const Detail = (props) => {
	//스토어에서 상태값 가져오기
	const bucket_list = useSelector((state) => state.bucket.list);
	//url 파라미터에서 인덱스 가져오기
	let bucket_list = parseInt(props.match.params.index);

	return <h1>{bucket_list[bucket_index]}</h1>;
};

export default Detail;

SPA (Single Page Application)

  • 서버에서 주는 html이 1개만 있는 어플리케이션
  • 전통적인 웹사이트 : 페이지를 이동할 때마다 정적자원들(html, css, js)을 내려줌
  • SPA : 딱 한번만 정적자원을 받아옴

라우팅

  • 브라우저 주소에 따라 다른 페이지를 보여주는 것
  • SPA에서 html은 딱 하나만 있지만 브라우저 주소창대로 다른 페이지를 보여줄 수 있음

리액트에서 라우팅 처리하기

  • react-route-dom 패키지 설치하기
yarn add react-router-dom

적용하기

1. index.js에 BrowserRouter 적용하기

import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
	<BrowserRouter>
		<App />
	</BrowserRouter>
	document.getElementById("root")
);
  • BrowserRouter : 웹 브라우저가 가지고 있는 주소 정보를 props로 넘겨줌

 

2. 세부 화면 만들기

  • Home.js
const Home = (props) => {
	return (
		<div>메인 화면</div>
	)
}

export default Home;
  • Cat.js
const Cat = (props) => {
	return (
		<div>고양이</div>
	)
}

export default Cat;
  • Dog.js
const Dog = (props) => {
	return (
		<div>강아지</div>
	)
}

export default Dog;

 

3. App.js에서 Route 적용하기

  • Route 사용 방법 1) 넘겨줄 props가 없을 때
<Route path="주소[ex. /home]" component={[보여줄 컴포넌트]}/>
  • Route 사용 방법 2) 넘겨줄 props가 있을 때
<Route path="주소" render={(props) => (<BucketList list={this.state.list}/>)} />
  • App.js에 적용하기
import {Route} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;
  • "/"가 "/cat"와 "/dog"에 포함되어 있기 때문에 exact 추가

4. URL 파라미터 사용하기

  • 파라미터 : /cat/nabi
  • 쿼리 : /cat?name=nabi
  • App.js
<Route path="/cat/:cat_name" component={Cat}/>
  • Cat.js에서 console.log(props.match); 콘솔로 파라미터 확인 가능

5. 링크 이동시키기

1) <Link/> 사용하기

<Link to="주소">[텍스트]</Link>
  • App.js
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>

				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;

2) history 사용하기

  • App.js
import {withRouter} from 'react-router';
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	componentDidMount(){
		console.log(this.props);
	}

	render() {
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>
	
				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
	
				<button onClick={() => {
					this.props.history.push("/cat");
				}}>
					cat으로 가기
				</button>
				<button onClick={() => {
					this.props.history.goBack();
				}}>뒤로가기</button>
			</div>
		);
	}
}
//내보내는 부분에서 withRouter로 감싸주기
export default withRouter(App);
  • push([이동할 주소]) : 페이지 이동시킴
  • goBack() : 뒤로가기

 

잘못된 주소 처리하기 (Switch 이용)

  • NotFound.js
const NotFound = (props) => {
	return <h1>주소가 올바르지 않습니다</h1>;
);

export default NotFound;
  • App.js
render() {
	return (
		<div className="App">
			...
			<Switch>
				<Route
					path="/"
					exact
					render={(props) => {
						<BucketList
							list={this.state.list}
							history={this.props.history}
						/>
					}}
				/>
				<Route path="/detail" component={Detail}/>
				<Route component={NotFound}/>
			</Switch>
			...
		</div>
	);
}
  • NotFound 컴포넌트를 주소 없이 연결함
  • Switch는 첫번째로 매칭되는 path를 가진 컴포넌트를 렌더링함

1. 로딩 화면(스플래시 화면)에서 firebase DB 데이터 불러오는 문제 해결

- 스레드의 run 메소드 안에 DB 조회 코드가 들어있던 것이 문제... 스레드 밖으로 해당 코드를 뺐더니 해결!

- 하지만 지금은 스레드의 딜레이 시간을 고정으로 준 상태라 그 시간 내에 데이터를 불러오지 못할 수도 있음

- 따라서 확실히 DB 데이터 조회를 완료한 후 액티비티가 이동할 수 있도록 보완할 필요가 있음

 

2. 챗봇 답장 '일기장으로 이동합니다' 출력 시 일기장 액티비티로 이동

- 이 외에도 dialogflow 자체에서 처리할 수 없는 것들은 안드로이드 스튜디오에서 코드 작성해서 처리해야할듯

 

일기장으로 이동합니다 멘트 출력 시 1.5초 뒤 일기장으로 이동

 

3. 진단테스트 안내 문구 추가

- 테스트 진행 전 테스트 목적, 문항 수, 유의사항 등의 안내를 확인할 수 있음

 

진단테스트 안내 화면

 


 

지난 일요일에 회의에서는 테스트 결과에 따라 마음 온도를 어떻게 설정할건지,

마음 온도에 따라 앱 기능의 제한 정도, 게시판 기능의 모호성 등을 논의해보았다.

슬슬 앱의 디자인을 제대로 꾸미기 위해 이모티콘 그림도 각자 그려오기로 했고 (일요일 회의때 대회 진행할 예정 ^_^)

새 기능을 구현하기 위해 각자 역할 분담해서 수행해오기로 했다.

8월 30일까지 한이음 공모전 접수 마감이니까 호다닥 개발해버리자~!😎

+ Recent posts