코딩 보카 2.0을 마치고…
프로젝트 소개
코딩 보카는 개발을 시작한 지 얼마 안된 초보자들을 위한 앱으로 다양한 개발 용어들과 개발관련 영단어들을 사용자의 수준에 맞게 레벨 별로 학습할 수 있게 도와주는 앱 프로젝트로 이번 코딩 보카 2.0은 이러한 코딩 보카를 유저들의 피드백을 반영하여 기존의 코딩 보카를 고도화 시킨 프로젝트이다.
- 플랫폼 : 안드로이드/모바일
- 사용 언어 : Kotlin
- 진행 기간: 2023.04.27 ~ 2023.06.15, 2023.07.13, 2023.08.01, 2023.08.09, 2023.11.15(53일)
- 진행 인원: 개인 프로젝트 (1인)
- 깃허브 리포지토리
- 플레이 스토어
개발 배경
내가 처음으로 진행한 어느 정도 규모 있는 프로젝인 코딩 보카는 개발 당시에는 디자인도 어느 정도 마음에 들고 기능들도 괜찮다고 생각이 들어 딱히 수정할 필요성 자체를 못 느꼈으나 앱에 넣어놓은 개발자에게 문의하기 기능과 버그 제보를 통해 지속해서 수정할 부분들이 제보되었고
그동안은 바빠서 계속 나중에 나중에 하며 미루던 중 그동안 하던 일들이 끝나고 여유가 생겨 피드백을 하나 씩 반영하려다가 천천히 피드백 내용을 살펴보니 디자인, 버그, 레벨 시스템으로 이러한 문제는 코딩보카 앱이 가진 코어적 문제였기에 일일이 수정이 번거롭고 어렵다고 느껴져서 이럴 바에 차라리 앱을 아예 갈아엎어서 처음부터 다시 만들자는 생각이 들어 디자인과 시스템 모두를 처음부터 만들게 되는 코딩 보카 2.0 프로젝트를 시작하게 되었다
사용 예제
구현 기능
- 홈
- 유저가 당일 학습한 단어 수를 보여주는 기능
- 유저가 학습 중인 코스들과 진행률을 보여주는 기능
- 단어장
- 유저가 선택한 코스의 단어장을 보여주는 기능
- 학습
- 플래시 카드 (단어 학습 여부 단어장을 왼쪽, 오른쪽으로 넘길 수 있음)
- 깜빡이 (자동으로 넘어가는 단어장, TTS 기능 선택)
- OX 퀴즈 (퀴즈에 OX로 정답을 맞춰가면서 학습을 할 수 있음)
- 초성 퀴즈 (주어진 초성으로 어떤 단어인지 응답 가능)
- 코스
- 찜 기능
- 코스 검색 기능
- 코스 진행 기능
- 마이페이지
- 이름 변경
- 이미지 변경
- 회원 탈퇴 및 로그아웃
- 문의하기
이슈
데이터베이스 선택
기존의 코딩 보카에선 데이터베이스를 개발 관련 지식 부족 문제로 앱에 정보를 저장하기 위해서 온라인 데이베이스를 통해서만 가능하다고 생각하여 로컬 데이터베이스를 전혀 사용하지 않고 Firebase에 Realtime Database를 사용해 개발하였는데
이에 대한 문제점으로 Firebase에 Realtime Database와 앱 간 통신을 하며 딜레이가 있어 유저에게 정보가 바로 바로 보이지 않았다.
해당 문제를 해결하기 위해 찾아보던 중 로컬에서도 데이터를 저장할 수 있다는 것을 알게 되었으며 기기 내부에 데이터를 저장하면 앱에선 온라인 데이터베이스 방식보다 빠르게 데이터를 가져올 수 있어 사용자가 보는 화면 속 정보 갱신에 딜레이를 줄일 수 있다는 것을 알게 되어 Android에서 사용할 수 있는 로컬 데이터베이스들에 대해 알아보게 되었다.
그래서 찾게 된 것이 SharedPreferences와 Sqlite였는데 이 중 처음에 시도한 것이 바로 SharedPreferences였다. 그 까닭은 Sqlite보다 확연히 사용하기 쉬웠기 때문이었는데 문제는 처음에는 간단한 정보들만 저장하려고 했으나 나중에 앱이 확장되어 유저의 여러 정보를 저장하게 되자 간단하게 String, Int, Long, Float, Boolean 등과 같은 것만 저장하는 SharedPreferences로는 무리를 느꼈다.
하지만 나는 SharedPreferences가 String을 저장할 수 있다는 것에 착안하여 List나 Data Class 등을 Json으로 변환시켜 SharedPreferences로 저장시키고 이를 또 반대로 하여 데이터를 사용하는 방식을 사용하였는데
1
2
3
4
5
6
7
8
9
10
11
fun saveListToSharedPreferences(sharedPrefs: SharedPreferences, key: String, list: List<Any>) {
val gson = Gson()
val json = gson.toJson(list)
sharedPrefs.edit().putString(key, json).apply()
}
fun <T> getListFromSharedPreferences(sharedPrefs: SharedPreferences, key: String, typeToken: TypeToken<List<T>>): List<T>? {
val gson = Gson()
val json = sharedPrefs.getString(key, null) ?: return null
return gson.fromJson<List<T>>(json, typeToken.type)
}
처음엔 해당 방법을 떠올린 것에 대해 정말 창의적이고 잘 만들었다고 생각하였으나 문득 다른 개발자들이 이 방법을 쓰지 않는 이유가 있지 않을까?와 함께 애초에 SharedPreferences는 초기 설정 값이나 자동 로그인 여부 등 간단한 정보를 저장하려고 만들어진 것인데 이는 올바른 방법이 아니라는 생각이 들어 이를 통해 발생할 수 있는 문제에 대해 찾아보니
성능 저하, 메모리 사용 증가, 동시성 문제, 데이터 손실 위험, 보안 취약점과 같은 많은 문제가 생길 수 있다고 하여 기존에 개발하던 SharedPreferences 방식을 또 다시 갈아엎고 Sqlite로 전환하였다.
하지만 여기서 멈추지 않고 Android Sqlite에 대해 계속 찾아보니 Android Jetpack 라이브러리 중 Room이 존재하였는데 Room은 Sqlite보다 쉬운 학습 난이도와 컴파일 도중 SQL에 대한 유효성 검사와 같은 여러 장점을 가지고 있었기에 이를 사용했다.
그러면서도 앱에 데이터를 백업을 위해서 Firebase Realtime Database를 같이 사용하였다.
비 업데이트 코스 데이터 추가
코딩 보카 2.0 에선 기본적으론 로컬 데이터베이스에 모든 데이터를 저장하고 Firebase Realtime Database는 유저가 언제, 어디서, 어느 기기를 사용하던 지 똑같은 경험을 하길 원하기에 백업 용도로 사용하고 있었는데
로컬에 저장된 코스 데이터를 앱을 업데이트를 하지 않아도 데이터가 업데이트 되고 싶었다. 그래서 해당 방법을 구현하기 위해 많은 고민을 했었는데 그래서 떠오른 아이디어가 SplashActivity에서 온라인 데이터베이스에 있는 코스 데이터를 다운 받게 하면 될 거라 생각했다.
하지만 코스 데이터가 최신이어도 계속해서 SplashActivity에서 중복된 코스 데이터를 온라인 데이터베이스에서 가져와 덮어쓰는 건 비효율적이라 생각이 들었고 이를 방지하기 위해 다음과 같은 로직을 추가로 세웠다.
- 일주일에 한번 씩만 코스 버전 데이터를 확인
- 코스 버전 데이터 확인 시 로컬과 온라인 간 버전이 다르다면 코스 다운로드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
val lastUpdate = if(PreferenceSingleton.prefs.getString("last update") == "0") {
getTodayDate()
} else {
PreferenceSingleton.prefs.getString("last update")
}
val isGreaterThanSevenDays = isDateDifferenceGreaterThanSevenDays(lastUpdate)
if (isGreaterThanSevenDays) {
database = Firebase.database.reference
val myRef = database.child("version")
myRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val value = snapshot.value
val version = value.toString().toInt()
val myVersion = PreferenceSingleton.prefs.getString("version").toInt()
if (version != myVersion) {
PreferenceSingleton.prefs.setString("version", version.toString())
PreferenceSingleton.prefs.setString("last update", getTodayDate())
updateCourseItem()
} else {
PreferenceSingleton.prefs.setString("last update", getTodayDate())
}
}
override fun onCancelled(error: DatabaseError) {
Log.d("오류", error.toString())
}
})
}
이제와서 생각해보면 이럴 바엔 그냥 코스 부분은 온라인 데이터베이스에서 실시간으로 정보를 받아와도 되지 않나? 싶기도 하지만 당시 프로젝트 목적이 앱의 본질에 충실하게 오프라인 상태에서도 사용 가능하고 딜레이 없는 것을 목적으로 프로젝트를 진행했기에 어쩔 수 없었던 거 같다.
단어장 및 학습하기 데이터 추가
이번에 2.0 프로젝트를 하면서 가장 걱정했던 점이 데이터를 구하는 문제로 코딩 관련 단어, 코딩 관련 영 단어에 대한 단어 및 설명은 데이터를 구하기가 어려워 1.0 버전에서는 직접 컴퓨터시스템 일반 교과서를 통해 단어를 찾고 이에 대해 찾아봐서 설명을 입력하는 식으로 하여 데이터를 입력하는데 매우 오래 걸렸다.
하지만 2.0에선 ChatGPT를 이용하여 코스 별 단어와 설명을 쉽게 구할 수 있었으며 이를 정해진 Json 파일 형식으로 뽑아달라고 하여 Firebase Realtime Database에 집어넣어 비교적 손 쉽게 단어장 및 학습하기 데이터를 추가할 수 있었다.
느낀점
이번 프로젝트는 내가 진행한 최초의 업데이트 프로젝트로 내게는 굉장히 뜻깊은 앱인 코딩 보카를 아예 처음부터 새로 만드는 프로젝트였다.
코딩 보카는 1학년 때 만든 미숙한 앱이다보니 만큼 버그가 많았고 해결된 버그도 있지만 출시 당시 미처 발견하지 못했거나 해결하지 못한 버그들이 아직 남아있는 점이 맘에 걸렸던 점들을 이번 프로젝트를 통해 해소할 수 있어서 좋았으며 실 사용한 유저들의 피드백을 받아드려 기존 프로젝트를 수정하는 뜻 깊은 경험을 할 수 있었다.
하지만 아쉬운 점으로는 해당 프로젝트를 진행하는 동안 갑자기 추가로 진행해야 하는 일들이 자꾸만 생겼어서 프로젝트 기간이 53일로 다소 많이 늘어난 것이 아쉽다. 그래서 이번 경험을 바탕으로 다음에 프로젝트를 할 때에는 해당 프로젝트에만 집중할 수 있도록 일정들을 잘 조율할 필요성을 깨닫게 되었다.