Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

강원대_Android_이아림_3주차과제_STEP2 #74

Open
wants to merge 26 commits into
base: arieum
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b7f54a9
Resolved merge conflicts
arieum Jul 8, 2024
6547942
Create: Add KaKaoResponse_data classes
arieum Jul 9, 2024
295192b
Create: Add KakaoApiService interface
arieum Jul 9, 2024
db935b3
feat: Integrate KakaoAPI_call to fetch and display place
arieum Jul 9, 2024
976ce29
feat: Integrate KakaoAPI_call to fetch and display hospital_place
arieum Jul 9, 2024
87000ad
style: Adjust place_card.xml layout dimensions
arieum Jul 9, 2024
42c7748
chore: Remove comments
arieum Jul 9, 2024
bcc1055
fix: Correct typos in code
arieum Jul 9, 2024
3ad088a
feat: Initialize KaKaoMapSDK
arieum Jul 9, 2024
6e42d7c
Create: Add Mapview.XML
arieum Jul 9, 2024
8a6181d
feat: Add MapView lifecycle management
arieum Jul 9, 2024
d186bec
fix: modified code continuously to KakaoMap_API error
arieum Jul 9, 2024
ec3c6d6
feat: Complete map_view.xml
arieum Jul 9, 2024
68d0342
feat: Add navigation to search_view when searchTextView is clicked
arieum Jul 9, 2024
666b16f
Add files via upload
arieum Jul 9, 2024
382779c
Update README.md
arieum Jul 10, 2024
6e2ac40
refactor: Extract searchTextView_onClickListener to a seperate method…
arieum Jul 10, 2024
feda049
Merge branch 'arieum_step2' of https://github.com/arieum/android-map-…
arieum Jul 10, 2024
24720d3
refactor: Change Result_RecyclerView to inherit from ListAdapter
arieum Jul 10, 2024
7de4cdb
refactor: Refactored activity_main.xml -> Replaced 'left''right' attr…
arieum Jul 12, 2024
31698c0
refactor: Refactore cursor_handling to use getIntOrNull for null-safe…
arieum Jul 12, 2024
484e952
Fix : Fixed Type_mismatch -> TextWatcher was expected by removing unn…
arieum Jul 12, 2024
cb6a5cf
refactor: Refacotred data_class_KakaoResponse to use @SerializedName …
arieum Jul 12, 2024
fbfdbce
refactor: Add ID_field to Place_class and configured db to autoIncrem…
arieum Jul 12, 2024
f616899
refactor: Define RetrofitObject_object as singleton
arieum Jul 12, 2024
7a803eb
refator: Use 'use'_function for automatic management in database
arieum Jul 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added 3주차과제실행영상.webm
Binary file not shown.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# android-map-search
## 🙋‍♀️ 개요
이 App은 사용자가 검색어를 입력하여 검색 결과를 확인하고, 선택된 항목을 저장할 수 있는 기능을 제공한다. 저장된 검색어 목록은 앱을 재실행해도 유지된다. 검색 데이터는 카카오로컬 API를 사용하고, 지도는 카카오지도 SDK를 사용한다.

## ✨ 주요기능
>**STEP1_검색화면**
- 검색어를 입력하면 검색 결과가 15개 이상 표시된다
- 검색 결과_데이터는 <ins>카카오로컬 API</ins>를 사용한다

>**STEP2_지도화면**
- 앱 처음 실행시 검색화면 X, 지도화면이 표시되도록 한다
- 지도화면은 <ins>카카오지도 SDK</ins>를 사용한다
- `검색창 클릭`시 검색화면이으로 이동하도록 한다
- 검색화면에서 `단말기_뒤로가기`를 하면 지도화면으로 돌아온다


## 📱 실행화면
1. 앱 처음 실행시, 지도화면 표시


![지도화면](https://github.com/arieum/android-map-search/blob/arieum_step2/%EC%B2%AB%ED%99%94%EB%A9%B4_%EC%A7%80%EB%8F%84%ED%99%94%EB%A9%B4.png)

2. 카카오로컬 API로부터 검색결과 불러오기


![검색결과화면](https://github.com/arieum/android-map-search/blob/arieum_step2/%EA%B2%B0%EA%B3%BC%ED%99%94%EB%A9%B4fromapi.png)

3. 전체적인 앱 실행영상 + 검색화면에서 뒤로가기 누르면 다시 지도화면으로 이동하기
![3주차과제실행영상](https://github.com/arieum/android-map-search/blob/arieum_step2/3%EC%A3%BC%EC%B0%A8%EA%B3%BC%EC%A0%9C%EC%8B%A4%ED%96%89%EC%98%81%EC%83%81.webm)

## ⚙️ 사용된 API 및 SDK
- 카카오로컬 API_Document ☞ <https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-category>
- 카카오지도 SDK_Document ☞ <https://apis.map.kakao.com/android_v2/docs/getting-started/quickstart/>

![PrayingCatGIF](https://github.com/arieum/android-map-search/assets/143606293/a0bc3779-a19a-4e15-aae0-d7a475662aea)
33 changes: 32 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.Properties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
Expand All @@ -15,6 +17,19 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

val kakaoApiKey = getApiKey("KAKAO_API_KEY")
val kakaoRestApiKey = getApiKey("KAKAO_REST_API_KEY")

buildConfigField("String", "KAKAO_API_KEY", kakaoApiKey)
buildConfigField("String", "KAKAO_REST_API_KEY", kakaoRestApiKey)

ndk {
abiFilters.add("arm64-v8a")
abiFilters.add("armeabi-v7a")
abiFilters.add("x86")
abiFilters.add("x86_64")
}
}

buildTypes {
Expand All @@ -26,10 +41,16 @@ android {
)
}
}

buildFeatures {
buildConfig = true
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
Expand All @@ -49,9 +70,19 @@ dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("com.kakao.maps.open:android:2.9.5")
implementation("com.kakao.sdk:v2-all:2.20.3")
implementation("com.kakao.maps.open:android:2.9.7")
implementation("androidx.activity:activity:1.8.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

fun getApiKey(key: String): String {
val properties = Properties()
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
properties.load(localPropertiesFile.inputStream())
}
return properties.getProperty(key, "")
}
6 changes: 5 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -16,6 +17,9 @@
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="false" />
<activity
android:name=".MapViewActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -25,4 +29,4 @@
</activity>
</application>

</manifest>
</manifest>
14 changes: 14 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/KakaoApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package campus.tech.kakao.map

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query

interface KakaoApiService {
@GET("v2/local/search/category")
fun getPlace(
@Header("Authorization") apiKey: String,
@Query("category_group_code") categoryGroupCode: String,
): Call<KakaoResponse>
}
30 changes: 30 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/KakaoResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package campus.tech.kakao.map

import com.google.gson.annotations.SerializedName

data class KakaoResponse(
val meta: Meta,
val documents: List<Document>
)

data class Meta(
@SerializedName("same_name") val sameName: Any?,
@SerializedName("pageable_count") val pageableCount: Int,
@SerializedName("total_count") val totalCount: Int,
@SerializedName("is_end") val isEnd: Boolean
)

data class Document(
@SerializedName("place_name") val placeName: String,
val distance: String?,
@SerializedName("place_url") val placeUrl: String,
@SerializedName("category_name") val categoryName: String,
@SerializedName("address_name") val addressName: String,
@SerializedName("road_address_name") val roadAddressName: String,
val id: String,
val phone: String?,
@SerializedName("category_group_code") val categoryGroupCode: String,
@SerializedName("category_group_name") val categoryGroupName: String,
val x: String,
val y: String
)
113 changes: 113 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,124 @@
package campus.tech.kakao.map

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

private lateinit var input: EditText
private lateinit var researchCloseButton: ImageView
private lateinit var tabRecyclerView: RecyclerView
private lateinit var noResultTextView: TextView
private lateinit var recyclerView: RecyclerView
private lateinit var placeRepository: PlaceRepository
private var placeList = mutableListOf<Place>()
private var researchList = mutableListOf<Place>()
private lateinit var resultAdapter: RecyclerViewAdapter
private lateinit var tapAdapter: TapViewAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

input = findViewById(R.id.input)
researchCloseButton = findViewById(R.id.close_button)
noResultTextView = findViewById(R.id.no_result_textview)
recyclerView = findViewById(R.id.recyclerView)
tabRecyclerView = findViewById(R.id.tab_recyclerview)

placeRepository = PlaceRepository(this)
placeRepository.reset()
placeList = placeRepository.insertInitialData()
Comment on lines +38 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 Repository들을 View에서 직접 세팅하고 사용하고 계신데 ViewModel로 옮기셔야 합니다


resultAdapter = RecyclerViewAdapter {
placeRepository.insertLog(it)
addResearchList(it)
}
recyclerView.adapter = resultAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
resultAdapter.submitList(placeList)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ViewModel을 적용하기가 조금 어렵다고 하셨는데 이번주까지만 MVC로 진행하시고 이번에 Step3 작업 하신 이후에는 조금씩이나마 ViewModel을 사용해서 데이터 흐름이 LocalDB -> View로 오는 흐름을 만들어보세요
실무에서는 다 이런 방식으로 설계하기 때문에 결국 부딪혀야 합니다. 강사님과 멘토 믿고 부족해도 괜찮으니 시도해보세요!


researchList = placeRepository.getResearchEntries().toMutableList()
tapAdapter = TapViewAdapter(researchList) {
placeRepository.deleteResearchEntry(it)
removeResearchList(it)
}
tabRecyclerView.adapter = tapAdapter
tabRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)

updateTabRecyclerViewVisibility()

input.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// 미사용
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// 미사용
}

override fun afterTextChanged(s: Editable?) {
filterList(s.toString())
}
})

researchCloseButton.setOnClickListener {
input.setText("")
}
}

private fun filterList(query: String) {
val filteredList = if (query.isEmpty()) {
emptyList<Place>()
} else {
placeList.filter {
it.category.category.contains(query, ignoreCase = true) || it.name.contains(query, ignoreCase = true)
}
}

if (filteredList.isEmpty()) {
noResultTextView.isVisible = true
recyclerView.isGone = true
} else {
noResultTextView.isGone = true
recyclerView.isVisible = true
resultAdapter.submitList(filteredList.toMutableList())
}
}

private fun addResearchList(place: Place) {
if (!researchList.contains(place)){
researchList.add(place)
tapAdapter.notifyItemInserted(researchList.size - 1)
updateTabRecyclerViewVisibility()
}
}

private fun removeResearchList(it: Place) {
val position = researchList.indexOf(it)
if (position != -1) {
researchList.removeAt(position)
tapAdapter.notifyItemRemoved(position)
updateTabRecyclerViewVisibility()
}
}

private fun updateTabRecyclerViewVisibility() {
tabRecyclerView.isVisible = placeRepository.hasResearchEntries()
}

override fun onDestroy() {
super.onDestroy()
input.removeTextChangedListener(null)
}
}

60 changes: 60 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MapViewActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package campus.tech.kakao.map

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.kakao.vectormap.KakaoMap
import com.kakao.vectormap.KakaoMapReadyCallback
import com.kakao.vectormap.MapLifeCycleCallback
import com.kakao.vectormap.MapView

class MapViewActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var searchTextview: TextView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map_view)

mapView = findViewById(R.id.map)
searchTextview = findViewById(R.id.search)

try {
mapView.start(object : MapLifeCycleCallback() {
override fun onMapDestroy() {
Log.d("KakaoMap", "카카오맵 정상종료")
}

override fun onMapError(exception: Exception?) {
Log.e("KakaoMap", "카카오맵 인증실패", exception)
}
}, object : KakaoMapReadyCallback() {
override fun onMapReady(map: KakaoMap) {
Log.d("KakaoMap", "카카오맵 정상실행")
}
})
Log.d("MapViewActivity", "mapView start called")
} catch (e: Exception) {
Log.e("MapViewActivity", "Exception during mapView.start", e)
}

searchTextview.setOnClickListener { onSearchTextViewClick() }
}

private fun onSearchTextViewClick() {
startActivity(Intent(this@MapViewActivity, MainActivity::class.java))
}
override fun onResume() {
super.onResume()
Log.d("MapViewActivity", "onResume called")
mapView.resume()
}

override fun onPause() {
super.onPause()
Log.d("MapViewActivity", "onPause called")
mapView.pause()
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MyApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package campus.tech.kakao.map

import android.app.Application
import com.kakao.vectormap.KakaoMapSdk

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val appKey = BuildConfig.KAKAO_API_KEY
KakaoMapSdk.init(this, appKey)
}
}
22 changes: 22 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MyPlaceContract.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package campus.tech.kakao.map

import android.provider.BaseColumns

object MyPlaceContract {
object Place : BaseColumns {
const val TABLE_NAME = "place"
const val COLUMN_NAME = "name"
const val COLUMN_IMG = "img"
const val COLUMN_LOCATION = "location"
const val COLUMN_CATEGORY = "category"
}

object Research : BaseColumns {
const val TABLE_NAME = "research"
const val COLUMN_NAME = "name"
const val COLUMN_IMG = "img"
const val COLUMN_LOCATION = "location"
const val COLUMN_CATEGORY = "category"
}

}
Loading