오늘은 웹 메신저의 메인 기능인 채팅 조회 및 실시간 업데이트 코드를 작성했다.

Firestore에 채팅 내역을 저장해놓았고, DB에서 불러온 채팅 내역을 상태로 관리하여

onSnapshot 메소드를 이용해서 조회할 때, 채팅이 추가되었을 때 상태를 변경해서 즉시 반영하는 것이다.

 

 

크롬과 엣지를 이용해서 혼자 채팅 테스트를 해보니 채팅 조회와 실시간 반영이 잘 되는 것을 확인했다.

 

그러나... 아주 큰 문제가 있는데 현재 코드는 다음과 같다.

 

chatMessageFB.doc('20210909').collection('userMessage').onSnapshot((docs) => {
    let chatFromFB = [];
    console.log(docs)
    docs.forEach((doc) => {
        chatFromFB.push({
            senderId: doc.data().senderId,
            content: doc.data().content,
            time: doc.data().time
        })
    })
    setChatHistory(chatFromFB);
});

 

document의 id (날짜) 를 지정해서 불러오는 상태인 것이다.

내가 원하는 것은 컬렉션 아래에 있는 모든 document 목록을 forEach로 돌려서

전체 채팅 내역이 뜨게 하는 것인데 나름대로 코드를 작성해보았지만 작동을 하지 않았다.

 

몇 시간을 삽질하고 뭔가 잘못되었음을 느끼고 결국 스파르타 리액트 튜터님께 슬랙에 질문을 남겼는데

Firestore에서는 내가 생각했던 기능을 구현할 수 없다는게 결론이었다...

 

사실 실시간 업데이트 기능때문에 Firestore 말고 Realtime Database를 이용하는 것을 진작에 고려해보았는데

어쩐 일인지 import 'firebase/database';를 작성하면 오류가 발생해서 최대한 Firestore을 이용하는 방향으로 잡았었다.

하지만 이제는 Realtime Database를 사용해야만 하는 상황이 되었기 때문에 어떻게든 그 오류를 해결해보려고 한다.

 

부디 빠른 시일 내에 오류를 해결할 수 있길 바란다🙏

AWS S3 버킷

S3 버킷이란?

  • S3(Simple Storage Service) : 단순 스토리지 서비스
  • 이미지, html, css, js 같은 정적 파일을 저장할 수 있고 정적 웹 호스팅이 가능함

정적 웹 사이트란?

  • 서버 측 스크립트(PHP, JSP, ASP 등) 사용 유무를 기준으로 동적 웹 페이지와 정적 웹 페이지로 나눌 수 있음
  • 정적 웹 사이트는 html, css, js 같은 정적 자원으로만 이루어진 웹 사이트

 

S3 버킷 설정하기

1. 버킷 생성하기 (사이트 도메인과 버킷 이름이 같아야함)

2. 권한 탭에서 ARN([ARN: arn:aws:s3:::[도메인 주소]])를 복사

3. 정책 생성기를 눌러서 정책 생성

  • Step 1) Select Type of Policy : S3 Bucket Policy
  • Step 2) Actions : GetObjcet

4. 생성한 정책을 복사해서 버킷 정책 편집기에 붙여넣기

  • arn 뒤에 반드시 /* 써주기!!

5. S3 버킷에 결과물 올리기

  • 빌드하기
yarn build
  • build 폴더 내의 파일을 버킷에 올리기

6. 정적 웹 사이트 호스팅 설정하기

7. 엔드포인트 주소를 클릭해서 호스팅 확인

 

도메인 연결하기

  • Route 53에서 호스팅 영역 생성
  • 도메인 이름 작성, 유형은 퍼블릭 호스팅 영역 선택
  • 네임서버를 가비아(도메인을 산 곳)에 등록함 (레코드 이름, 값/트래픽 라우팅 대상)
  • 레코드 생성

Firebase로 배포하기

Firebase 호스팅

  • Firebase 대시보드에서 호스팅 신청
  • 프로젝트에 cli 설치
yarn global firebase-tools
  • Firebase에 로그인 후 init 실행
yarn firebase login #웹브라우저에서 내 구글 계정으로 로그인
yarn firebase init
  • Hosting 선택, Use an existing project, Firebase 프로젝트 선택
  • What do you want to use as your public directory? → public

 

Firebase에 결과물 올리기

  • firebase.json 확인
  • 빌드한 결과물 올리기
yarn firebase deploy
  • Firebase 대시보드 → 호스팅 이동, 도메인으로 들어가서 확인

Material UI

공식 문서

https://material-ui.com/

 

Material-UI: A popular React UI framework

React components for faster and easier web development. Build your own design system, or start with Material Design.

material-ui.com

 

설치하기

yarn add @material-ui/core @material-ui/icons

 

사용하기

Detail.js

import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
...

const Detail = (props) => {
	...
	return (
		...
		<ButtonGroup>
			<Button
				variant="outlined"
				onClick={() => { /*생략*/ }}
			>삭제하기</Button>
			<Button
				variant="outlined"
				onClick={() => { /*생략*/ }}
			>완료하기</Button>
		</ButtonGroup>
		...
	);
};

export default Detail;

 


페이지 의도적으로 가리기

페이지 가리기가 필요한 이유

  • 현재는 redux에 넣어둔 초깃값 (가짜 데이터) 이 먼저 보인다.
  • Firestore의 데이터만 제대로 보여주기 위해 데이터를 가져오기 전까지는 페이지를 가린다.
  • 수정 또는 추가하기 버튼을 눌렀을 때 API를 여러번 호출하는 현상도 방지할 수 있다.

 

로딩 스피너 만들기

로딩 스피너 컴포넌트 만들기

  • 머터리얼 UI 아이콘 이용
import React from 'react';
import styled from 'styled-components';
import {Eco} from '@material-ui/icons';

const Spinner = (props) => {
	return (
		<Outter>
			<Eco style={{color: '#673ab7', fontSize: '150px'}}/>
		</Outter>
	);
}

const Outter = styled.div`
	position: fixed;
	top: 0;
	left: 0;
	width: 100vw;
	height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: #ede2ff;
`;

export default Spinner;

 

bucket.js

  • initialState에 is_loaded 변수를 추가해서 firestore에서 데이터를 받아오면 갱신하도록 함
const initialState = {
	is_loaded: false,
	list: [
		{text: '영화관 가기', completed: false},
		{text: '매일 책읽기', completed: false},
		{text: '수영 배우기', completed: false},
	],
};

...
	case 'bucket/LOAD': {
		if (action.bucket.length > 0) {
			return {list: action.bucket, is_loaded: true};
		}
		return state;
	}

 

App.js

  • is_loaded 값에 따라 조건부 렌더링을 한다.
const mapStateToProps = (state) => {
	bucket_list: state.bucket.list,
	is_loaded: state.bucket.is_loaded,
});
...
render() {
	<div className='App'>
		<Container>
			<Title>내 버킷리스트</Title>
			{!this.props.is_loaded ? (
				<Spinner/>
				) : (
					<React.Fragment>
						<Progress />
						<Line />
						<Switch>
							...
						</Switch>
						</React.Fragment>
				)}
			</Container>
		...

redux-thunk란?

  • Firestore에서 데이터를 가져올 때 비동기 통신을 함
  • 리덕스에서 비동기 통신을 할 때 미들웨어가 필요함
  • 일반 액션 생성 함수는 객체를 반환하는데 redux-thunk는 객체 대신 함수를 생성하는 액션 생성함수를 작성할 수 있게 해줌
  • 함수를 생성하면 특정 액션이 발생하기 전에 조건을 주거나 행동을 할 수 있음

미들웨어

  • 리덕스 데이터를 수정할 때 액션 디스패치 → 리듀서에서 처리
  • 미들웨어가 있으면 액션 디스패치 → 미들웨어가 할일 → 리듀서에서 처리

설치하기

yarn add redux-thunk

configStore.js

  • redux-thunk 적용 전 configStore.js
import {createStore, combineReducers} from 'redux';
import bucket from './modules/bucket';
import {createBrowserHistory} from 'history';

export const history = createBrowserHistory();
const rootReducer = combineReducers({bucket});
const store = createStore(rootReducer);

export default store;
  • redux-thunk 적용 후 ⬇
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import bucket from './modules/bucket';
import {createBrowserHistory} from 'history';

export const history = createBrowserHistory();

const middlewares = [thunk];

const enhancer = applyMiddleware(...middlewares);
const rootReducer = combineReducers({bucket});
const store = createStore(rootReducer, enhancer);

export default store;

 

Load하기

bucket.js 수정

  • Firebase랑 통신하는 함수 생성
const bucket_db = firestore.collection('bucket');

export const loadBucketFB = () => {
	return function (dispatch) {
		bucket_db.get().then((docs) => {
			let bucket_data = [];
			docs.forEach((doc) => {
				if(doc.exists){
					bucket_data = [...bucket_data, {id: doc.id, ...doc.data()}];
				}
			});

			dispatch(loadBucket(bucket_data));
		});
	};
};
  • 리듀서 수정
case 'bucket/LOAD': {
	if (action.bucket.length > 0) {
		return {list: action.bucket};
	}

	return state;
}

App.js 수정

const mapDispatchToProps = (dispatch) => ({
	load: () => {
		dispatch(loadBucketFB());
	},
	create: (new_item) => {
		dispatch(createBucket(new_item));
	}
});

 

Create 하기

bucket.js 수정

  • Firebase와 통신하는 함수 생성
export const addBucketFB = (bucket) => {
	return function (dispatch) {
		let bucket_data = {text: bucket, completed: false};
		bucket_db
			.add(bucket_data)
			.then((docRef) => {
				bucket_data = {...bucket_data, id: docRef.id};
				dispatch(createBucket(bucket_data));
			});
			.catch((err) => {
				window.alert('오류 발생');
			});
	};
};
  • 리듀서 수정
case 'bucket/CREATE': {
	const new_bucket_list = [
		...state.list,
		action.bucket,
	];
	return {list: new_bucket_list};
}

App.js

const mapDispatchToProps = (dispatch) => ({
	load: () => {
		dispatch(loadBucketFB());
	},
	create: (new_item) => {
		dispatch(addBucketFB(new_item));
	}
});

 

Update 하기

bucket.js

export const updateBucketFB = (bucket) => {
	return function (dispatch, getState) {
		const _bucket_data = getState().bucket.list[bucket];
		if (!_bucket_data.id) {
			return;
		}

		let bucket_data = {..._bucket_data, completed: true};
		bucket_db
			.doc(bucket_data.id)
			.update(bucket_data)
			.then((res) => {
				dispatch(updateBucket(bucket));
			})
			.catch((err) => {
			});
	};
};

Detail.js

<button onClick={() => {
		dispatch(updateBucketFB(bucket_index));
		props.history.goBack();
	}}>완료하기</button>

 

Delete 하기

bucket.js

export const deleteBucketFB = (bucket) => {
	return function (dispatch, getState) {
		const _bucket_data = getState().bucket.list[bucket];
		if (!_bucket_data.id) {
			return;
		}
		bucket_db
			.doc(_bucket_data.id)
			.delete()
			.then((res) => {
				dispatch(deleteBucket(bucket));
			})
			.catch((err) => {
			});
	};
};

Detail.js

<button onClick={() => {
		dispatch(deleteBucketFB(bucket_index));
		props.history.goBack();
	}}>삭제하기</button>

1. 1:1 채팅방 번호 생성

추후에 자신이 속한 채팅방 목록을 쉽게 조회하기 위해서, 그리고 1:1뿐만 아니라 단체톡방 기능 갖도록 확장할 것을 고려하여

채팅방마다 고유의 번호를 가지고 있는 것이 편리할 것이라는 판단을 했다.

따라서 친구 등록 시간과 자신의 아이디를 조합하여 채팅방 번호를 만들었다.

(ex. 2021년 9월 5일 오전 1시 33분 4초에 친구 등록 -> 20210905013304아이디 번호를 가지는 채팅방 생성)

 

자바스크립트에서 날짜를 다루는 함수를 찾아보았는데 사용자 임의로 포맷을 만들어서

그 형식에 맞게 날짜를 리턴하는 함수는 따로 없는 것으로 보인다.

자바는 format 함수가 있어서 편리한데 자바스크립트에는 없어서 의외다.

 

그래서 처음에는 이렇게 코드를 작성했다가

 

    //현재 날짜와 시간을 이용해서 채팅방 번호 생성
    function makeChatRoomNum() {
        const today = new Date();   //현재 날짜
        const year = today.getFullYear();
        const month = ('0' + (today.getMonth() + 1)).slice(-2);
        const day = ('0' + today.getDate()).slice(-2);
        const hours = ('0' + today.getHours()).slice(-2);
        const minutes = ('0' + today.getMinutes()).slice(-2);
        const seconds = ('0' + today.getSeconds()).slice(-2);
        
        return year + month + day + hours + minutes + seconds + loginUserId;
    }

 

좀 더 찾아보니 moment라는 라이브러리가 자바의 format과 같은 기능을 제공한다고 하여 적용해보았다.

 

import moment from 'moment';

	//현재 날짜와 시간을 이용해서 채팅방 번호 생성
    function makeChatRoomNum() {
		return moment().format('YYYYMMDDHHmmss') + loginUserId;
    }

 

8줄의 코드를 단 한줄로 줄일 수 있었다...!

이 외에도 날짜 더하기, 빼기 등 유용한 함수를 많이 제공하는 것으로 보인다.

라이브러리를 잘 활용하는 것이 개발 생산성 향상에 매우 도움된다는 것을 다시 한번 체감한다.

 

 

2. 채팅방 멤버 DB 저장

친구 추가 시 자동으로 만들어지는 1:1 채팅방에

현재 로그인한 사용자와 친구로 등록된 사용자를 멤버로 등록한다.

 

    //친구 추가
    const addFriend = () => {
        let popup = window.confirm('친구로 추가하시겠습니까?');
        if (popup) {    //'예'를 선택했을 때
            console.log('친구 추가하기');
            const chatRoomNum = makeChatRoomNum();

            //친구 등록 완료
            firestore.collection('users').doc(loginUserEmail).collection('friends').doc(friendEmail).set({id: friendId, name: friendName, chatRoomNum: chatRoomNum})
                .then(() => {
                    alert('친구로 등록되었습니다.')}
                );

            //채팅방 멤버로 등록
            firestore.collection('chatRooms').doc(chatRoomNum).collection('members').doc(loginUserEmail).set({isMember: true});
            firestore.collection('chatRooms').doc(chatRoomNum).collection('members').doc(friendEmail).set({isMember: true});
        }
    }

 

chatRooms (컬렉션)

ㄴ채팅방 번호 (문서)

  ㄴmembers (컬렉션)

    ㄴ로그인한 사용자 이메일 (문서)

       ㄴisMember: true (필드)

    ㄴ친구로 등록된 사용자 이메일 (문서)

       ㄴisMember: true (필드)

 

친구 등록이 완료되면 DB에 위처럼 등록이 된다.

 

 

3. 전송한 채팅 메세지 DB 저장

드디어 채팅 기능 구현을 시작했다! 가장 쉬운 채팅 전송 기능부터 만들어보았다.

채팅방에서 메세지를 입력하고 전송 버튼을 클릭하면 DB에 저장된다.

 

const content = React.useRef(); //채팅 input 박스

    function sendMessage() {
        const value = content.current.value;

        if (value.length == 0) {
            alert('채팅 내용을 입력해주세요');
        } else {
            const date = getDate(); //채팅 내역 document에 사용
            const time = getTime(); //채팅 내역 메세지 field에 사용
            const messageCode = time + loginId;

            firestore.collection('chatRooms').doc(chatRoomNum).collection('chatMessages')
            .doc(date).collection('userMessage').doc(messageCode).set({content: value, time: time.slice(0, -3)}).then(
                //전송 완료 (DB 등록)
            );

            content.current.value = ''; //채팅 input 박스 비우기
        }
    }

 

input 박스 ref에 React.useRef로 만든 ref를 등록해서

전송 버튼 클릭 시 해당 input 안에 입력된 값을 가져오고

내용이 입력되었으면 DB에 저장, 만약 입력되지 않았으면 alert 메세지를 띄운다.

 

chatRooms (컬렉션)

ㄴ채팅방 번호 (문서)

  ㄴchatMessages (컬렉션)

    ㄴ날짜 (문서)

      ㄴuserMessage (컬렉션)

        ㄴ메세지 번호 (문서)

          ㄴcontent: 메세지 내용 (필드)

          ㄴtime: 전송 시간 (필드)

 

현재 DB 구조는 이러한데 추후에 수정될 수도 있다.

 

채팅 메세지 번호를 생성할 때도 역시 moment 라이브러리를 이용해서 간단히 날짜와 시간을 얻을 수 있었다.

 

    function getDate() {
        return moment().format('YYYYMMDD');
    }

    function getTime() {
        return moment().format('HH:mm:ss');
    }

 

 

4. 채팅 메세지 레이아웃 만들기

본격적으로 채팅 내역 조회 기능을 구현하기 전에, 먼저 메세지들의 레이아웃을 만들어보았다.

 

 

일반 메신저와 큰 차이점 없이 이름과 메세지 내용, 전송 시간을 나타내도록 했다.

재사용을 위해 보낸 메세지, 받은 메세지를 각각 컴포넌트로 만들었다.

 

채팅 내역 조회까지 구현하면 주요 기능은 거의 완성되는 건데

구현하면서 얼마나 많은 시행착오를 겪게 될지ㅎㅎ 기대된다.

1. 친구 추가 시 Firestore DB에 저장

친구 찾기 기능을 이용하여 사용자 아이디를 검색하면 해당 사용자가 검색 결과로 나온다.

친구 추가하기 버튼을 클릭하면 친구 추가 진행 확인 alert가 뜨고, 확인 버튼을 클릭했을 때 Firestore DB에 저장된다.

 

 

현재 DB의 구조는 다음과 같이 되어있다.

users (컬렉션)

ㄴ사용자 이메일 (문서)

  ㄴfriends (컬렉션)

   ㄴ친구 이메일 (문서)

    ㄴ친구 아이디 (필드)

    ㄴ친구 이름 (필드)

로그인한 사용자의 문서 아래에 있는 friends 목록에 추가한 친구 정보가 등록되는 것이다.

 

이름은 추후에 수정 기능을 제공할 예정이라 필드에 넣고 싶지 않았는데

사이드바에 친구 목록을 불러오는 것을 구현할 때 중첩으로 DB를 읽어와야해서 비동기 처리가 필요한데

이것을 해결하지 못해서 나중에 다시 시도해보기로 하고, 현재는 이름 필드도 추가하게 해놨다.

 

 

2. 사이드바 친구 목록 표시

친구 추가, 그리고 나중에 구현될 친구 삭제 기능을 이용하여 친구 목록이 수정되면 친구 목록이 바로 업데이트될 수 있도록

첫 렌더링 후에 실행되는 useEffect 안에서 Firestore의 onSnapshot 메소드를 이용했다.

 

    const loginEmail = useSelector(state => state.user.email); //redux에서 관리하는 로그인한 사용자 이메일
    const [friends, setFriends] = React.useState([]);	//친구 목록을 상태로 관리함
    const usersFB = firestore.collection('users');	//Firestore에 있는 user 컬렉션

	useEffect(() => {
        usersFB.doc(loginEmail).collection('friends').onSnapshot((docs) => {
            let listFromFB = [];
            docs.forEach((doc) => {
                listFromFB.push({id: doc.data().id, name: doc.data().name, chatRoomNum: doc.data().chatRoomNum});
            });
            setFriends(listFromFB);
    })}, []);

 

DB에서 읽어온 친구 목록을 listFromFB라는 배열에 담고 이것을 상태로 넘겨주면 리렌더링 되면서 사이드바에 친구 목록이 반영된다.

 

 

친구 등록 후 사이드바에 해당 사용자가 추가된 모습이다.

 

 

3. 친구 이름 클릭 시 1:1 채팅방 팝업 띄우기

사이드바에 있는 친구 목록에서 각 사용자를 클릭하면 해당 사용자와 1:1 채팅을 할 수 있도록 채팅방 팝업을 띄운다.

리액트 라우팅에서 URL 파라미터를 이용하여 채팅 상대에 대한 정보를 전달한다.

 

 

전체적인 레이아웃 영역만 잡은 상태이고 조만간 채팅 메세지 레이아웃을 만들 예정이다.

신입사원 기본역량교육 두번째 날!

이 날은 오전 4시간, 오후 4시간동안 교육이 진행돼서 교육이 끝나고 나서 지친 상태였다.

그래도 얻어가는게 있으니까 교육 시간에 열심히 참여했다!

 

오전에는 문제해결역량 교육이 있었다.

 


문제 정의의 중요성

문제를 해결하기 위해서는 먼저 문제를 정의하는 과정이 필요하다.

그런데 문제를 어떻게 정의하느냐에 따라서 복잡한 해결책이 나올 수도, 간단한 해결책이 나올 수도 있는 것이다.

 

강사님이 언급하신 예시는 "우주에서 사용할 수 있는 필기구"에 대한 문제에 대해

미국은 "무중력 상태에서도 볼펜을 사용할 수 있는 방법",

소련은 "무중력 상태에서 우주 실험을 기록할 수 있는 가장 효과적인 방법"으로 문제 정의를 한 사례가 있었다.

 

 

이미지 자동 분류를 위한 접근 방식 토의 실습

말 그대로 이미지를 자동 분류하기 위해서는 어떤 기술과 방법이 필요한지 토의해보는 시간이었는데

사실 전공 또는 관심 분야가 IT쪽인 사람들이 모였다보니, 다들 딥러닝 또는 머신러닝을 떠올렸다.

우리 팀은 각자의 의견을 모아 "여러 샘플 데이터를 이용하여 모델 학습을 시킨 후 차이가 나는 특징들을 추출함"으로써 이미지 분류를 할 수 있다고 아이디어를 적었다.

 

 

5why 문제 해결 기법

사고역량강화를 위한 기법 중 하나로 5why 문제 해결 기법을 배웠다.

적용 사례 중 하나는 제퍼슨기념관 외벽 손상 사례에 대한 것이었는데

Why1 : 외벽 부식이 강한 이유는? -> 비누 청소를 자주 하기 때문

Why2 : 비누 청소를 자주 하는 이유는? -> 비둘기 배설물이 많기 때문

Why3 : 비둘기 배설물이 많은 이유는? -> 거미가 많기 때문

Why4 : 거미가 많은 이유는? -> 불나방이 많기 때문

Why5 : 불나방이 많은 이유는? -> 직원들이 일찍 퇴근하기 위해 전등을 일찍 키기 때문

결국 5번의 Why로 늦게 전등을 키는 해결 방법을 얻을 수 있었다고 한다.

 

나는 왜 이 일을 하고 싶은가? 브레인스토밍

사고 역량에는 창의적 사고, 논리적 사고, 비판적 사고가 있는데 창의적 사고 기법 중 하나가 브레인스토밍이다.

브레인스토밍은 자유분방하게 아이디어를 제시하는 것인데

나는 왜 이 일(희망 진로)을 하고 싶은가? 라는 주제로 브레인스토밍 실습을 했다.

 

줌의 주석 기능을 이용하여 강의를 듣는 사람들이 각자 자신의 생각을 적어보았는데

 

금전적인 수단, 내가 가장 잘 할 수 있는 일, 틀에 박히지 않은 일, 보다 편리한 세상을 만들 수 있어서 등의 의견이 있었다.

 

나는 FE개발자가 목표라, "작성한 코드에 따라 시각적 결과물이 나오는게 흥미로워서" 라는 의견을 작성했다.

나의 희망 진로를 꿈꾸는 이유를 떠올리며 공부를 하다보면 꾸준히, 포기하지 않고 열심히 달릴 수 있을 것 같다.

 

 

해적 금화 나누기 문제 실습

논리적 사고 실습 시간으로 "해적 금화 나누기" 문제를 팀별로 풀어보았다. (해당 문제는 인터넷에 검색하면 쉽게 찾을 수 있으므로 설명은 생략한다.)

각 해적에게 분배할 금화의 액수를 정해야 하는데 그 값을 정하기 위해 팀원들과 다양한 의견을 주고받았다.

여러 의견을 듣고 각 의견을 종합하여 가장 합리적이라고 생각되는 답을 찾아내서 실습 자료에 적었다.

나는 평소에도 어떤 주제에 대해 내 생각을 전달하며 생각의 근거를 대는 논리적인 대화를 좋아하기 때문에

이번 실습이 굉장히 재밌었다. 다른 사람들과 각자의 의견을 펼칠 수 있는 장이 열린 느낌ㅋㅋㅋ

 

 

문제 해결 실습

강의에서 배운 내용들을 종합하여 해결하고자 하는 문제 선정, 문제 원인 분석, 해결안 도출 시간을 팀별로 가졌다.

 

우리 조가 선정한 문제는 유튜브에서 한국어 댓글을 찾기 어렵다는 점이었다.

유튜브를 이용하는 사람들은 한번쯤은 공감했을 것이다.

외국인 시청자가 많은 영상일수록 한국 영상임에도 불구하고 한국인이 작성한 댓글을 찾기가 굉장히 어렵다.

 

이것의 원인은 한국어 댓글이 댓글 순서 알고리즘 상 낮은 우선순위를 차지하고 있기 때문이라고 분석했다.

따라서 각 언어별 댓글 분류 기능을 이용하여 사용자가 접속한 나라의 언어, 또는 사용자가 선택한 언어의 댓글을 우선순위로 보여주는 기능을 제공하면 해결할 수 있을 것이라 작성했다.

 

 

이번 강의에서 배운 내용들을 바탕으로 실생활 또는 조직에서 발생하는 문제들을 정확히 파악하고, 적합한 해결책을 도출할 수 있도록 문제해결능력을 키워야겠다.

여름방학 기간에 집중적으로 마음이 앱 개발을 진행했다!

앱 개발 기획 시 생각했던 주요 기능은 AI 상담 채팅, 진단테스트, 일기장, 게시판이 있었고

7월에 해당 기능들을 모두 구현했다.

 

또한 한이음 중간점검 보고서 제출 마감일이 7월 19일까지라서 중간보고서와 제작설계서를 작성했었다.

 


 

게시판

6월에 게시판 레이아웃 틀을 잡고 게시글 등록 기능을 구현했었고

7월에는 다음과 같은 기능들을 구현하여 게시판 개발을 마무리했다.

  • 게시글 수정/삭제 기능
  • 댓글 등록/삭제 기능
  • 공감 기능
  • 댓글 수, 공감 수 출력

 

현재 게시판은 자유게시판과 익명게시판으로 나뉜다.

추후에 진단테스트 결과에 따라 마음 온도가 낮게 나온 사용자에 대해 게시판 기능 제한을 둘 예정인데

게시판의 분류가 애매하다는 생각이 들어서 어떻게 변경할지 고민할 필요가 있었다.

 

게시글 목록과 글 내용 페이지

 

진단테스트

진단테스트 문항은 JSON 파일에 저장해놓고, parsing하여 화면에 출력하도록 했다.

게시판과 마찬가지로 RecyclerView를 이용해서 구현하였는데

 

처음 구현하고 나서 테스트를 해보니 사용자가 선택한 응답의 체크 표시가 사라지고,

응답하지 않은 문항에 체크가 되어있는 이상한 현상이 있었다.

 

다행히도 이러한 현상에 대해 다룬 포스팅을 찾을 수 있었다.

https://kawaiineko.tistory.com/19

 

[android] Listview Checkbox 스크롤 후 체크 이상현상 해결법

안드로이드 개발은 쉬우면서도 어렵다. 이게 무슨 개소리냐면 머리로 구상한대로만 진행된다면 정말 그 어떤 코딩보다도 쉬운데 구상한대로 진행하다보면 꼭 생각지도 못한 문제가 발생한다.

kawaiineko.tistory.com

 

RecyclerView의 재사용 처리로 인해 이러한 현상이 발생하는 것이라고 한다.

체크한 응답이 코드상에서 저장은 되는데, 화면에 출력될 때 이상하게 나오는거라

화면에 선택했던 응답에 체크 표시를 하도록 코드를 추가함으로써 해결할 수 있었다.

 

테스트 진행 화면과 결과 화면

 

AI 상담 채팅

앱의 핵심 기능이라고 할 수 있는 AI 상담 채팅 기능!

학교폭력 관련 앱이므로 앱 이용자와 전문가가 직접 상담을 할 수 있다면 좋겠지만 지금 우리가 구현하긴 힘들다고 생각되어

이것의 대체 방안으로 AI 학교폭력 상담 채팅 기능을 제공하기로 한 것이다.

 

Google Dialogflow 챗봇 API를 이용하여 기능 구현을 했는데

구글 서비스 계정에서 문제가 여러 차례 발생하여 Android Studio와 연동하기까지 많은 어려움이 있었다.

지금은 다행히 해당 문제를 해결하여 연동이 완료되었고

앱에서 Dialogflow에 채팅을 전송하고, 응답을 받는 코드 구현 방법을 유튜브에서 찾을 수 있어서 쉽게 구현했다.

 

다만, 원래 Dialogflow는 Custom payload를 이용하여 카드 형식으로 Dialogflow에 보낼 채팅을 선택할 수 있었는데

앱으로 구현할 땐 그러한 응답 형식을 구현할 수 없었다.

사용자가 메세지를 1개 보내면 그에 대한 챗봇 응답 1개만 텍스트 형식으로 받을 수 있는 로직이었기 때문이다.

따라서 웰컴 메세지 (채팅을 시작할 때 첫 메세지) 를 추가하여 사용자에게 예상 질문 목록을 보여주는 것으로 보완했다.

 

채팅 내역은 Firebase DB에 저장하고 불러오도록 코드를 작성하였으며

채팅 내역 전체 삭제 기능을 추가하여 편하게 웰컴 메세지를 다시 확인할 수 있도록 하였다.

 

채팅 화면, 웰컴 메세지, 채팅 내역 전체 삭제 기능

 

기타 개발

  • 사이드바 추가 (Navigation Drawer 이용)
  • 로그아웃 기능 추가 (Firebase Authentication 이용)
  • 앱 디자인 개선

 

8월에는 진단테스트 결과에 따른 기능 제한과 마음 채우기 (영상 시청) 기능 구현을 주 목표로 할 것이고

그 외 앱의 디자인 개선과 각 기능의 완성도를 높이기 위해 다양한 방법을 생각해보아야겠다.

 


 

7월 개발 일지 목록

  • 7/1 (게시글 수정/삭제, 댓글 등록/조회 기능 구현)
 

2021.07.01 개발 일지 (게시글 수정/삭제, 댓글 등록/조회)

1. 게시글 내용 페이지 - 게시글 목록 불러오는 것까지 구현했었는데 이제 각 게시물 카드를 터치했을 때 게시글 세부 내용을 확인하는 페이지로 이동하도록 추가함 2. 게시글 수정 및 삭제 - 작

askges20.tistory.com

 

  • 7/2 (댓글 목록 레이아웃 수정, 댓글 삭제 기능 추가)
 

2021.07.02 개발 일지 (데이터 중복 출력 오류 해결, 댓글 삭제 기능)

1. 댓글 목록 ListView height 조절 - ScrollView 안에 모든 요소를 넣고 그 중에서 ListView가 있는 구조인데 ListView만 스크롤이 되고 전체 ScrollView는 스크롤이 되지 않는 상태였음 - ListView의 각 item들..

askges20.tistory.com

 

  • 7/4 (Android Studio - Firebase 다시 연결)
 

2021.07.04 개발 일지 (Android Studio - Firebase DB 다시 연결)

새로운 Firebase 프로젝트 DB와 안드로이드 스튜디오 연결하기 오늘은 회의가 있는 날이었다. 일기장, 게시판 주요 기능 구현은 거의 다 마친 상태에서 앞으로 구현해야할 기능들에 대해 얘기를 나

askges20.tistory.com

 

  • 7/10 (상담 채팅 레이아웃 구성, 로그인 유지 구현, 마이페이지 사용자 정보 출력)
 

2021.07.10 개발 일지 (1:1 상담 레이아웃 구성, 로그인 유지, 마이페이지 사용자 정보 출력)

1. 1:1 상담 채팅(dialogflow) - 앱 내의 레이아웃 구현 - https://www.youtube.com/watch?v=zVxDBBCdpfY 이 영상에 나온 방법대로 구현 - 메세지 전송 시 화면에 해당 메세지가 출력되는 상태 - 현재 dialo..

askges20.tistory.com

 

  • 7/11 ~ 7/12 (게시판 사용자 아이디 출력, 앱 테마 및 디자인 개선, 진단테스트 기능 구현)
 

2021.7.11 - 7.12 개발 일지 (게시판에 사용자 아이디 반영, 앱 테마 및 디자인 개선, 진단테스트 기능

새벽 시간에 완전 집중해서 개발했다!! 덕분에 많은 진전이 있었다. 슬슬 앱의 디자인적인 요소를 신경써야할거 같아서 폰트나 테마를 수정해봤고 주요 기능인 진단테스트 구현을 하루만에 거

askges20.tistory.com

 

  • 7/13 (Android Studio - Dialogflow 연결 완료, 채팅 내역 Firebase DB에 저장, 불러오기)
 

2021.07.13 개발 일지 (Android Studio - Dialogflow 연결 완료)

1. 게시글 작성 페이지 레이아웃 수정 2. Dialogflow - Android Studio 연결 완료 3. 1:1 상담 채팅 내역 Firebase DB 저장 4. 1:1 상담 채팅 내역 Firebase DB에서 읽어오고 화면에 띄우기 어제는 정말 감격스러..

askges20.tistory.com

 

  • 7/14 ~ 7/15 (Firebase Authentication 로그아웃 기능 구현)
 

2021.07.14 - 7.15 개발 일지 (로그아웃 기능 구현)

1. 로그아웃 기능 추가 - 마이페이지에 로그아웃 버튼 추가 - 파이어베이스 Authentication에서 로그아웃 필요 FirebaseAuth.getInstance().signOut(); - 로그아웃을 진행하면 기존에 실행되던 액티비티를 모두

askges20.tistory.com

 

  • 7/19 (게시판 공감 기능 추가, 사이드바 추가, 채팅 날짜 출력)
 

2021.07.19 개발 일지 (게시판 공감 기능 추가, 사이드바 구현, 채팅 날짜 표시)

일주일 전에 정보처리기사 실기 시험 보고나서 한동안 아무 의욕 없이 지내다가 오늘에서야 다시 의욕이 되돌아온듯 하다. 그동안 많이 쉰만큼 오늘은 12시간동안 빡세게 개발했지!⭐🤸‍♀️

askges20.tistory.com

 

  • 7/24 (게시판 댓글 개수 출력, 익명게시판 댓글 영역 삭제)
 

2021.07.24 개발 일지 (댓글 개수 표시, 익명게시판 댓글 영역 제거)

1. 자유게시판 댓글 개수 표시 - 공감 개수 표시는 지난번에 구현 완료했었고 이번에 댓글 개수 표시까지 완료함 2. 익명게시판 목록에서 댓글 개수 표시X, 글 내용 페이지에서 댓글 영역 제거 3.

askges20.tistory.com

 

  • 7/29 (진단테스트 결과 내용 추가, 채팅 전체 삭제 기능 구현, 채팅 웰컴 메세지 출력)
 

2021.07.29 개발 일지 (진단테스트 결과 상세 내용, 채팅 전체 삭제, 채팅 웰컴 메세지 추가)

1. 진단테스트 결과 출력 - 테스트 점수에 따라 피해, 가해 정도를 [아주 약함 / 약함 / 보통 / 심함 / 아주 심함]으로 분류하여 상세 진단 내용 출력 - 프로그레스바로 최대 점수 중 몇 점을 차지했

askges20.tistory.com

 

+ Recent posts