-
Notifications
You must be signed in to change notification settings - Fork 33
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_정수현_2주차 과제_Step2 #43
base: jsh00325
Are you sure you want to change the base?
Changes from all commits
381b1ee
e53a1be
cde4321
5f158fb
e1238e3
d90c483
3c3b587
23c102a
43533ef
0c2bd06
788e357
77958d0
14ca317
c1e84b5
00a9cb3
457e5b7
48e9578
f097dce
94f989b
7e5040b
007b136
76ca1eb
426fcb6
866fcfa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,41 @@ | ||
# android-map-keyword | ||
|
||
## 📄 프로그램 설명 | ||
|
||
카카오맵의 클론 코딩을 위해, 검색 레이아웃을 제작하였습니다. | ||
|
||
이때 내부 저장소인 SQLite를 통해서 데이터를 검색하고, 기록을 저장하는 기능을 구현합니다. | ||
|
||
## 🎯 1단계(로컬 데이터) 구현할 기능 | ||
|
||
- [X] 검색어를 입력 받고 보여주는 레이아웃 제작 | ||
|
||
- [X] 검색어를 입력 받는 EditText 제작 | ||
|
||
- [X] X 버튼을 누르면 검색어를 초기화할 수 있도록 ImageButton 제작 | ||
|
||
- [X] 검색 결과를 보여주는 레이아웃 제작 | ||
|
||
- [X] 이전의 검색 기록을 확인할 수 있는 레이아웃 제작 | ||
|
||
- [X] 검색에 사용될 데이터를 생성하여 SQLite에 저장 | ||
|
||
## 🎯 2단계(검색) 구현할 기능 | ||
|
||
- [X] X버튼 클릭 시, 입력된 검색어를 지우기 | ||
|
||
- [X] 검색어를 입력하면 검색 결과를 띄워 주기 | ||
|
||
- [X] 업종(`COLUMN_CATEGORY`)과 일치한 결과를 DB에서 리스트로 가져오기 | ||
|
||
- [X] 가져온 리스트를 RecyclerView에 적용하여 보여주기 | ||
|
||
- [X] 검색 결과 중 하나를 클릭하는 경우, 해당 장소를 검색 기록에 추가하기 | ||
|
||
- [X] 검색 기록을 보여주는 item의 레이아웃 제작하기 | ||
|
||
- [X] 이미 DB에 존재한다면, 해당 값을 지우고 새로 저장해서 맨 뒤로 가도록 구현하기 | ||
|
||
- [X] DB에 존재하지 않는다면, 새롭게 추가하고 맨 뒤에서 불러오기 | ||
|
||
- [X] 검색 기록의 X버튼 클릭 시, 해당 검색 기록을 삭제하기 |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
import android.provider.BaseColumns | ||
|
||
object HistoryContract : BaseColumns { | ||
const val TABLE_NAME = "HISTORY" | ||
const val COLUMN_NAME = "name" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
import android.content.Context | ||
import android.database.sqlite.SQLiteDatabase | ||
import android.database.sqlite.SQLiteOpenHelper | ||
|
||
class HistoryDbHelper(context: Context) : | ||
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { | ||
|
||
override fun onCreate(db: SQLiteDatabase?) { | ||
db?.let { | ||
val query = "CREATE TABLE ${HistoryContract.TABLE_NAME} (" + | ||
"${HistoryContract.COLUMN_NAME} VARCHAR(50))" | ||
it.execSQL(query) | ||
} | ||
} | ||
|
||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { | ||
db?.execSQL("DROP TABLE IF EXISTS ${HistoryContract.TABLE_NAME}") | ||
onCreate(db) | ||
} | ||
|
||
companion object { | ||
const val DATABASE_NAME = "history.db" | ||
const val DATABASE_VERSION = 1 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
data class Location( | ||
val name: String, | ||
val address: String, | ||
val category: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
import android.provider.BaseColumns | ||
|
||
object LocationContract : BaseColumns { | ||
const val TABLE_NAME = "LOCATION" | ||
const val COLUMN_NAME = "name" | ||
const val COLUMN_ADDRESS = "address" | ||
const val COLUMN_CATEGORY = "category" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
import android.content.ContentValues | ||
import android.content.Context | ||
import android.database.sqlite.SQLiteDatabase | ||
import android.database.sqlite.SQLiteOpenHelper | ||
|
||
class LocationDbHelper(context: Context) : | ||
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { | ||
override fun onCreate(db: SQLiteDatabase?) { | ||
db?.let { | ||
val query = "CREATE TABLE ${LocationContract.TABLE_NAME} (" + | ||
"${LocationContract.COLUMN_NAME} VARCHAR(50)," + | ||
"${LocationContract.COLUMN_ADDRESS} VARCHAR(50)," + | ||
"${LocationContract.COLUMN_CATEGORY} VARCHAR(20))" | ||
it.execSQL(query) | ||
|
||
createLocationData(it) | ||
} | ||
} | ||
|
||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { | ||
db?.execSQL("DROP TABLE IF EXISTS ${LocationContract.TABLE_NAME}") | ||
onCreate(db) | ||
} | ||
|
||
private fun createLocationData(db: SQLiteDatabase) { | ||
// 임의의 카페 데이터 15개 생성 | ||
for (i in 1..15) { | ||
val values = ContentValues() | ||
values.put(LocationContract.COLUMN_NAME, "카페$i") | ||
values.put(LocationContract.COLUMN_ADDRESS, "서울 성동구 성수동 $i") | ||
values.put(LocationContract.COLUMN_CATEGORY, "카페") | ||
db.insert(LocationContract.TABLE_NAME, null, values) | ||
} | ||
|
||
// 임의의 약국 데이터 15개 생성 | ||
for (i in 1..15) { | ||
val values = ContentValues() | ||
values.put(LocationContract.COLUMN_NAME, "약국$i") | ||
values.put(LocationContract.COLUMN_ADDRESS, "서울 강남구 대치동 $i") | ||
values.put(LocationContract.COLUMN_CATEGORY, "약국") | ||
db.insert(LocationContract.TABLE_NAME, null, values) | ||
} | ||
} | ||
|
||
companion object { | ||
const val DATABASE_NAME = "location.db" | ||
const val DATABASE_VERSION = 1 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package campus.tech.kakao.map.model | ||
|
||
import android.content.ContentValues | ||
import android.content.Context | ||
|
||
class SearchLocationRepository(context: Context) { | ||
private val locationDbHelper: LocationDbHelper = LocationDbHelper(context) | ||
private val historyDbHelper: HistoryDbHelper = HistoryDbHelper(context) | ||
|
||
fun searchLocation(category: String): List<Location> { | ||
val db = locationDbHelper.readableDatabase | ||
val searchQuery = "SELECT * FROM ${LocationContract.TABLE_NAME} " + | ||
"WHERE ${LocationContract.COLUMN_CATEGORY} = '$category'" | ||
val cursor = db.rawQuery(searchQuery, null) | ||
|
||
val result = mutableListOf<Location>() | ||
while (cursor.moveToNext()) { | ||
result.add( | ||
Location( | ||
name = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_NAME)), | ||
address = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_ADDRESS)), | ||
category = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_CATEGORY)) | ||
) | ||
) | ||
} | ||
cursor.close() | ||
db.close() | ||
|
||
return result.toList() | ||
} | ||
|
||
fun addHistory(locationName: String) { | ||
if (isExistHistory(locationName)) { | ||
removeHistory(locationName) | ||
} | ||
|
||
val db = historyDbHelper.writableDatabase | ||
val historyValues = ContentValues() | ||
historyValues.put(HistoryContract.COLUMN_NAME, locationName) | ||
db.insert(HistoryContract.TABLE_NAME, null, historyValues) | ||
|
||
db.close() | ||
} | ||
|
||
fun getHistory(): List<String> { | ||
val db = historyDbHelper.readableDatabase | ||
val searchQuery = "SELECT * FROM ${HistoryContract.TABLE_NAME}" | ||
val cursor = db.rawQuery(searchQuery, null) | ||
|
||
val result = mutableListOf<String>() | ||
while (cursor.moveToNext()) { | ||
result.add(cursor.getString(cursor.getColumnIndexOrThrow(HistoryContract.COLUMN_NAME))) | ||
} | ||
|
||
cursor.close() | ||
db.close() | ||
return result.toList() | ||
} | ||
|
||
private fun isExistHistory(locationName: String): Boolean { | ||
val db = historyDbHelper.readableDatabase | ||
val searchQuery = "SELECT * FROM ${HistoryContract.TABLE_NAME} " + | ||
"WHERE ${HistoryContract.COLUMN_NAME} = '$locationName'" | ||
val cursor = db.rawQuery(searchQuery, null) | ||
|
||
val result = cursor.count > 0 | ||
cursor.close() | ||
db.close() | ||
|
||
return result | ||
} | ||
|
||
fun removeHistory(locationName: String) { | ||
val db = historyDbHelper.writableDatabase | ||
val deleteQuery = "DELETE FROM ${HistoryContract.TABLE_NAME} " + | ||
"WHERE ${HistoryContract.COLUMN_NAME} = '$locationName'" | ||
db.execSQL(deleteQuery) | ||
db.close() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package campus.tech.kakao.map.view | ||
|
||
import android.content.Context | ||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.DiffUtil | ||
import androidx.recyclerview.widget.RecyclerView | ||
import campus.tech.kakao.map.databinding.ItemHistoryBinding | ||
import campus.tech.kakao.map.viewmodel.SearchLocationViewModel | ||
|
||
class HistoryAdapter( | ||
private var dataList: List<String>, | ||
private val context: Context, | ||
private val viewModel: SearchLocationViewModel | ||
) : RecyclerView.Adapter<HistoryAdapter.MyViewHolder>() { | ||
|
||
inner class MyViewHolder(private val binding: ItemHistoryBinding) : | ||
RecyclerView.ViewHolder(binding.root) { | ||
init { | ||
binding.removeLocationHistoryButton.setOnClickListener { | ||
viewModel.removeHistory(dataList[bindingAdapterPosition]) | ||
} | ||
} | ||
fun binding(historyData: String) { | ||
binding.locationHistoryNameTextView.text = historyData | ||
} | ||
} | ||
|
||
fun updateDataList(newDataList: List<String>) { | ||
val diffUtil = HistoryDiffUtilCallback(dataList, newDataList) | ||
val diffResult = DiffUtil.calculateDiff(diffUtil) | ||
|
||
dataList = newDataList | ||
diffResult.dispatchUpdatesTo(this) | ||
} | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { | ||
return MyViewHolder( | ||
ItemHistoryBinding.inflate( | ||
LayoutInflater.from(context), | ||
parent, | ||
false | ||
) | ||
) | ||
} | ||
|
||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { | ||
holder.binding(dataList[position]) | ||
} | ||
|
||
override fun getItemCount(): Int = dataList.size | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package campus.tech.kakao.map.view | ||
|
||
import androidx.recyclerview.widget.DiffUtil | ||
|
||
class HistoryDiffUtilCallback( | ||
private val oldList: List<String>, | ||
private val newList: List<String> | ||
) : DiffUtil.Callback() { | ||
override fun getOldListSize(): Int = oldList.size | ||
|
||
override fun getNewListSize(): Int = newList.size | ||
|
||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = | ||
oldList[oldItemPosition] == newList[newItemPosition] | ||
|
||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = | ||
oldList[oldItemPosition] == newList[newItemPosition] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DiffUtil을 사용 해보셨군요 🎉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이 부분을 최적화하기 위해 여러 블로그 글을 봤는데, 대부분의 예제들이 Data Class를 설정한 후 id값을 통해 override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] === newList[newItemPosition]
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
정확히 잘 이해하셨습니다 👏 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
db 버전이 업그레이드될때 실행되는 마이그레이션 작업인데
지금은 모든 데이터를 지우고 새로 설정 해주시고 계시네요 ㅎㅎ
지금은 실 서비스가 아니니까 상관 없는데
실제 서비스를 할땐 이런 작업을 유의해야합니다.
앱 업데이트를 했는데 내가 저장해둔 데이터가 날아가면... 무수히 많은 문의를 받을수도 있거든요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"안드로이드 DB(데이터베이스) onUpgrade", tistory / "SQLiteOpenHelper", Android Developers
관련해서 여러 문서들과 블로그들을 찾아보면서, 실제로 onUpgrade()가 호출되는 상황을 이해할 수 있었습니다.
앞으로 업그레이드가 필요한 상황에서는, 기존의 데이터를 새로운 버전으로 마이그레이션을 할 수 있도록 코드를 작성하겠습니다!