1. 최종 프로젝트 3주차
어제는 드디어 이미지 자동 스크롤과 무한 스크롤을 구현시켰다.
오늘은 좋아요 기능과, 기본적인 데이터 구현에 목표를 두었다.
좋아요 기능
좋아요 기능은 꽤나 양이 많아서 우선은 하나하나 뚫어가기로 했다.
일단 기본적으로, DetailActivity에서 북마크를 클릭해야 시작이 되므로 우선 여기부터 작성하였다.
MountainDetailActivity.kt
private fun initBookMarkButton() {
if (sanDetailUiState.isLiked) binding.ivBookmark.setImageResource(R.drawable.ic_bookmark_on)
binding.ivBookmark.setOnClickListener {
sanDetailUiState.isLiked = !sanDetailUiState.isLiked
binding.ivBookmark.setImageResource(
if (sanDetailUiState.isLiked) R.drawable.ic_bookmark_on else R.drawable.ic_bookmark_off
)
OnBookmarkClickListener.onBookmarkClick(sanDetailUiState)
}
}
만약 isLiked가 true인 상태이면 true 상태의 북마크 색을 보여주고, 클릭하면 isLiked의 값을 반대로 바꾼 다음에 true/false에 따라서 이미지를 변환시킨다.
이 다음에는 북마크를 누른 산을 MyInfoFragment에 보여주는 기능을 추가해야 하는데 우선 이걸 구현하기 전에, SharedPreference부터 구현하기로 했다.
fun savedLiked(bookmark: MutableList<SanDetailUiState>) {
val context = MyApplication.appContext
val prefs = context?.getSharedPreferences(
LikedConstants.LIKED_PREFS_NAME,
Context.MODE_PRIVATE
)
prefs?.edit()?.putString(LikedConstants.LIKED_PREF_KEY, "")?.apply()
}
fun getLiked() : List<SanDetailUiState> {
val context = MyApplication.appContext
val prefs = context?.getSharedPreferences(
LikedConstants.LIKED_PREFS_NAME,
Context.MODE_PRIVATE
)
val json = prefs?.getString(LikedConstants.LIKED_PREF_KEY, null)
return if(json != null) {
val type = object : TypeToken<List<SanDetailUiState>>() {}.type
Gson().fromJson(json, type)
} else {
emptyList()
}
fun isSavedInLikedMountain(mountainName: String): Boolean =
getLiked().find { it.mountain == mountainName } != null
}
그리고 MyInfoFragment에 저장시키기 위한 객체도 하나 만들었다.
object OnBookmarkClickListener {
private val TAG = "OnBookmarkClicked"
fun onBookmarkClick(sanData: SanDetailUiState) {
val likedMountain = LikedUtil.getLiked().toMutableList()
Log.d(TAG, "before_likedMountain.size = ${likedMountain.size}")
if(sanData.isLiked) {
if(!LikedUtil.isSavedInLikedMountain(sanData.mountain)){
likedMountain.add(sanData)
}
} else {
likedMountain.find { it.mountain == sanData.mountain }?.let{
likedMountain.remove(it)
}
}
LikedUtil.savedLiked(likedMountain)
Log.d(TAG, "after_likedVideos.size = ${likedMountain.size}")
}
}
여기까지 만들어두고, 이제 MyInfoFragment에 받아오는 코드를 짜려던 찰나 한 가지 근본적인 문제점에 도달했다.
아직 산 리스트 Fragment가 완성되지 않았다.
그러니까 이걸 완성시켜도 확인을 할 수가 없는 상태.
따라서 이건 잠시 이렇게 두고 당장 급한 것부터 해결하기로 했다.
데이터 관리 - Firebase
다음으로, 데이터를 어떻게 관리할지에 대해서 회의를 진행하였다.
원래라면 산 정보들을 API를 통해서 받아오려고 했으나, 공공데이터에서 주는 API가 썩 만족스럽지 못하기도 하거니와 일부 데이터는 수기로 작성해야 하는 부분이 있어서 일부는 API로 받고 일부는 따로 데이터를 넣을지 아니면 처음부터 한 곳에 데이터를 전부 넣고 관리할지 이야기를 나누다 한 곳에 데이터를 전부 넣기로 결정했고, 그 데이터를 관리할 곳으로 선택한 게 바로 Firebase 되시겠다.
Firebase는 모바일 및 웹 어플리케이션 개발 플랫폼으로, 앱 개발과 관련하여 여러가지 편의를 제공하는데 우리는 여기에 데이터베이스를 저장하고 사용하는 기능을 사용하려 한다.
이런 식으로 큰 틀을 만들고 거기에 산의 종류들을 집어넣은 다음 각 데이터를 하나하나씩 집어넣으면 된다.
참고로, 여기서 적은 필드의 종류가 Kotlin에서 사용하는 객체이다.
여기서 적은 필드의 종류와 데이터타입은
- address (주소) : String
- difficulty (난이도) : Int (각 숫자마다 상, 중, 하를 부여할 예정이다)
- height (해발고도) : Int (나중에 "#,###m"로 변환시켜야 한다)
- name (산 이름) : String
- recommend (추천 이유) : String
- summary (개요) : String
- time_uphill (상행시간) : Int
- time_downhill (하행시간) : Int
이다.
데이터 처리
이제 이걸 가지고 어떻게 Android Studio에 적용시킬지 한 번 알아보자.
일단, Firebase에 있는 데이터들을 받아오기 위해서, Build.gradle에 dependency를 추가해야 한다.
implementation("com.google.firebase:firebase-firestore-ktx:24.10.3")
implementation("com.google.firebase:firebase-firestore:24.10.3")
그리고 우리는 DetailActivity에 받아와야 하므로, 여기에 변수를 추가해두었다.
MountainDetailActivity.kt
// 데이터 firebase로 받아오기
private val firestore = FirebaseFirestore.getInstance()
그러면 이제 곧바로 사용할 수 있느냐? 그게 아니다.
여기서 또 한 번의 공정을 거쳐야 한다.
firestore.collection("sanlist")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
if (document.getString("name") == "한라산") {
val sanlist = SanDetailUiState(
document.getString("name") ?: "none",
document.getString("address") ?: "none",
document.getString("difficulty") ?: "none",
document.getString("height") ?: "none",
document.getString("time_uphill") ?: "none",
document.getString("time_downhill") ?: "none",
document.getString("summary") ?: "none",
document.getString("recommend") ?: "none"
)
firestore.collection()의 괄호 안에 firebase 컬렉션에 등록되어 있는 'sanlist'를 적고, 다음과 같은 형식으로 적는다.
그리고 지금은 확인하기 위해서 저 'document.getString("name") == ' 부분에 임의의 값인 "한라산"을 집어넣었지만 본래는 저기에 산리스트 fragment에서 클릭한 산의 이름을 받아와야 한다.
저 조건을 만족하면, 저 조건에 맞는 산의 데이터들을 전부 SanDetailUiState라는 이름의 data class에 집어넣는다.
그런데 잠깐만.
여기서 뭔가 이상한 점이 있지 않은가?
모르겠다면, 우선 다음으로 넘어가자.
이렇게 받은 데이터들은 바로바로 적용시켜야 하니 틀을 만들었다.
with(binding) {
tvMountain.text = sanlist.mountain
val dec = DecimalFormat("#,###")
val height = sanlist.height.toInt()
tvHeightInfo.text = "${dec.format(height)}m"
tvIntroInfo.text = sanlist.summary
tvRecommendInfo.text = sanlist.recommend
// 숫자에 따라 난이도 부여 & 색상 부여
val difficulty = sanlist.difficulty.toInt()
tvDifficultyInfo.text = when (difficulty) {
1 -> "하"
2 -> "중"
else -> "상"
}
when (tvDifficultyInfo.text) {
"하" -> tvDifficultyInfo.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.offroader_blue
)
)
"중" -> tvDifficultyInfo.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.offroader_orange
)
)
else -> tvDifficultyInfo.setTextColor(
ContextCompat.getColor(
applicationContext,
R.color.offroader_red
)
)
}
//상행시간, 하행시간, 총 등산시간
val uphilltime = sanlist.uphilltime.toInt()
val downhilltime = sanlist.downhilltime.toInt()
tvUptimeInfo.text =
"${uphilltime / 60}시간 ${uphilltime % 60}분"
tvDowntimeInfo.text =
"${downhilltime / 60}시간 ${downhilltime % 60}분"
tvTimeInfo.text =
"${(uphilltime + downhilltime) / 60}시간 ${(uphilltime + downhilltime) % 60}분"
}
이렇게 처리한 데이터들을 화면으로 직접 보면 이렇게 나온다.
잘 받아와진다.
일단 산 개요 저 부분은 수정해야 하는 셈 치더라도 일단 firebase에 저장된 데이터를 잘 받아오는 것만 확인하면 된다.
그런데 여기서 몇 가지 문제점이 있었다.
- 이미지 파일을 받아오려니 용량 초과로 앱이 강제종료 되었다. 분명 어제까지만 해도 잘 받아오던 것이 갑자기 용량 초과 오류가 뜨다니 좀 적잖이 당황스러웠다.
- Number로 적은 부분을 못 받아와서 Number 부분을 전부 String으로 바꿨다.
이게 왜 못 받아지는지 모르겠지만 우선은 String형으로 바꾸고 Number 부분을 toInt()로 변환시켜야 했다- 이 부분은 해결하였다. 알고보니 Int형은 못 받고, Long형과 Double형 두 가지만 받아올 수 있었다. 그래서 Int형으로 적은 부분들을 Long형과 Double형으로 고친 다음, 각각 getLong()과 getDouble()로 바꾸었더니 해결.
이리하여 오늘은 데이터를 받는 것까지 완성했으나 아직 할 일이 산더미이다.
- 좋아요 기능 확인
- 이미지 못 받아오는 문제 확인
- 산 개요 & 추천 이유 서식 수정
- 날씨 API 받아오기
이걸 이번주 안에 다 해결해야 한다니 꽤나 험난한 길이 예상되지만 어쩌겠나. 스스로 불러온 재앙인 것을….
그래도 뭔가 하나씩 완성되고 있는 걸 보니 성취감이 든다.
내일도 이런 기분으로 작업을 했으면 좋겠다.
'[TIL][내일배움캠프]' 카테고리의 다른 글
[내일배움캠프][TIL] 24.03.07 (목) - 최종 프로젝트 3주차 : 개발 8일차 (2) | 2024.03.07 |
---|---|
[내일배움캠프][TIL] 24.03.06 (수) - 최종 프로젝트 3주차 : 개발 7일차 (2) | 2024.03.06 |
[내일배움캠프][TIL] 24.03.04 (월) - 최종 프로젝트 3주차 : 개발 5일차 (0) | 2024.03.04 |
[내일배움캠프][TIL] 24.02.29 (목) - 최종 프로젝트 2주차 : 개발 4일차 (2) | 2024.02.29 |
[내일배움캠프][TIL] 24.02.28 (수) - 최종 프로젝트 2주차 : 개발 3일차 (1) | 2024.02.28 |