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_노신 2주차 과제 Step2 #32

Open
wants to merge 23 commits into
base: normenghub
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
439780c
docs : updated README.md
Normenghub Jul 1, 2024
502739c
feat : Add SearchView
Normenghub Jul 1, 2024
9079ac3
feat : Created SQLiteOpenHelper.kt
Normenghub Jul 1, 2024
670e00a
feat : Created SQLiteDb.kt
Normenghub Jul 1, 2024
c01c1b6
feat : Add SQLiteHelper.kt
Normenghub Jul 1, 2024
60382eb
feat : Add SQLiteDb.kt
Normenghub Jul 1, 2024
210a741
feat : Add logAllData for checking on log
Normenghub Jul 1, 2024
4d764ca
feat : Add checkRun fun for Add Data for only one
Normenghub Jul 1, 2024
9ab3b8f
Refactor : function moved in the class
Normenghub Jul 2, 2024
f29075b
docs : Updated README.md
Normenghub Jul 3, 2024
74b8c69
feat : add recycleView searching
Normenghub Jul 3, 2024
5a27745
feat : Add class Item with changed file name
Normenghub Jul 3, 2024
ce63ed1
feat : Add DATA ID with AUTOINCREMENT
Normenghub Jul 3, 2024
bed845b
docs : created HistoryAdapter.kt
Normenghub Jul 3, 2024
f6e49e6
feat : RecyclerView for HistoryView
Normenghub Jul 3, 2024
000cab5
docs : created item_history.xml
Normenghub Jul 3, 2024
63f5349
feat : Send Data to SelectedData Table when clicked list
Normenghub Jul 3, 2024
c13d732
feat : get searchingHistory from SelectedData Table
Normenghub Jul 3, 2024
7150c22
feat : get searchingHistory from SelectedData Table
Normenghub Jul 3, 2024
232c882
feat : delete each item_history when click image Button
Normenghub Jul 3, 2024
5df541b
fix : did not delete specific DB COL, it fixed
Normenghub Jul 4, 2024
b3a9562
fix : del app:layoutManager="..."
Normenghub Jul 7, 2024
84a23be
fix : using String.isNullOrEmpty()
Normenghub Jul 7, 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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# android-map-keyword

# 2단계 - 검색

******


## 기능 요구 사항

검색어를 입력하면 검색 결과 목록이 표시된다.

검색 결과 목록은 세로 스크롤이 된다.

입력한 검색어는 X를 눌러서 삭제할 수 있.

검색 결과 목록에서 하나의 항목을 선택할 수 있다.

선택된 항목은 검색어 저장 목록에 추가된다.

저장된 검색어 목록은 가로 스크롤이 된다.

저장된 검색어는 X를 눌러서 삭제할 수 있다.

저장된 검색어는 앱을 재실행하여도 유지된다.

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

import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class HistoryAdapter(private var historyList: MutableList<Pair<Int, String>>, private val itemClickListener: (Int) -> Unit) :
RecyclerView.Adapter<HistoryAdapter.HistoryViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_history, parent, false)
return HistoryViewHolder(view)
}

override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
val (id, historyItem) = historyList[position]
holder.bind(historyItem)

holder.delButton.setOnClickListener {
itemClickListener(id)
}
}

override fun getItemCount(): Int {
return historyList.size
}

fun updateData(newHistoryList: List<Pair<Int, String>>) {
historyList.clear()
historyList.addAll(newHistoryList)
notifyDataSetChanged()
}

fun removeItemById(id: Int) {
val index = historyList.indexOfFirst { it.first == id }
if (index != -1) {
historyList.removeAt(index)
notifyItemRemoved(index)
}
}

inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val historyTextView: TextView = itemView.findViewById(R.id.historyTextView)
internal val delButton: ImageButton = itemView.findViewById(R.id.delButton)

fun bind(item: String) {
historyTextView.text = item
}
}
}
126 changes: 118 additions & 8 deletions app/src/main/java/campus/tech/kakao/map/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,121 @@
package campus.tech.kakao.map
package campus.tech.kakao.map

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
class MainActivity : AppCompatActivity() {
private lateinit var searchView: SearchView
private lateinit var recyclerView: RecyclerView
private lateinit var noResultTextView: TextView
private lateinit var adapter: PlacesAdapter
private lateinit var databaseHelper: SQLiteDb
private lateinit var historyRecyclerView: RecyclerView
private lateinit var historyAdapter: HistoryAdapter

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

searchView = findViewById(R.id.searchView)
recyclerView = findViewById(R.id.recyclerView)
noResultTextView = findViewById(R.id.noResultTextView)
historyRecyclerView = findViewById(R.id.historyRecyclerView)
databaseHelper = SQLiteDb(this)
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = PlacesAdapter(listOf()) { name ->
databaseHelper.insertIntoSelectedData(name)
updateHistory()
}
recyclerView.adapter = adapter

historyRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
Comment on lines +29 to +37

Choose a reason for hiding this comment

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

p4; 지연초기화로 필요할때 클래스를 대입해주는것도 괜찮지만, 클래스가 생성되는 시점에 만들어도 되는 클래스까지 lateinit var 을 사용할 필요까지는 없습니다. lateinit도 결국 mutable variable이기때문에 변경가능하다는 취약점이 있고, 바뀌지 않는다면 클래스 멤버변수들이 초기화 될때 인스턴스가 생성되어도 괜찮을것 같습니다. 가령 SQLiteDb 나 PlacesAdapter, HistoryAdapter 같은 클래스들이 예시가 되겠네요.

지연초기화 방법에는 by lazy 라는것도 있으니, 차이점을 비교하면서 사용해보시면 더 좋을것 같습니다

Choose a reason for hiding this comment

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

XML에 app:layoutManager="..." 속성을 사용했다면, Activity에서 LayoutManager를 설정해주지 않더라도 inflate될때 자동으로 설정되게 됩니다. 따라서, recyclerView, historyRecyclerView에 layoutManager를 설정해주는건 중복코드가 될것 같네요

Copy link
Author

Choose a reason for hiding this comment

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

확인 했습니다!

val historyList = databaseHelper.getAllSelectedData()
historyAdapter = HistoryAdapter(historyList.toMutableList()) { id ->
val deletedRows = databaseHelper.deleteFromSelectedData(id)
if (deletedRows > 0) {
historyAdapter.removeItemById(id)
} else { }
}
Comment on lines +39 to +44

Choose a reason for hiding this comment

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

p3; 변수를 선언할때는 어떤 의미를 담고있는지 최대한 나타내주는것이 좋습니다.
가령 isDeleteSelectData 처럼 "데이터베이스에서 데이터를 제거하는데에 성공했다" 라는 의미가 잘 전달되도록 deletedRows > 0 을 서술적으로 풀어주는것이죠.

Copy link
Author

Choose a reason for hiding this comment

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

확인 했습니다!

historyRecyclerView.adapter = historyAdapter

setupSearchView()
checkRun()
}

private fun setupSearchView() {
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}

override fun onQueryTextChange(newText: String?): Boolean {
if (newText.isNullOrEmpty()) {
showNoResultMessage()
adapter.updateData(emptyList())
} else {
searchPlaces(newText)
}
return true
}
})
}

private fun checkRun() {
val checkRun = getSharedPreferences("", Context.MODE_PRIVATE)
.getBoolean("First", true)
if (checkRun) {
insertData()
getSharedPreferences("", Context.MODE_PRIVATE).edit().putBoolean("First", false).apply()
databaseHelper.logAllData()
}
}

private fun insertData() {
val dbHelper = SQLiteDb(this)
for (i in 1..10) {
dbHelper.insertData("카페 $i", "서울 성동구 성수동 $i", "카페")
dbHelper.insertData("약국 $i", "서울 강남구 대치동 $i", "약국")
}
}
Comment on lines +79 to +85

Choose a reason for hiding this comment

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

p4; 데이터베이스에 미리 데이터를 넣어두는 것을 SQLiteDb의 init { } 블록 or SQLiteDbHelper의 onCreate에서 수행하는것도 방법일것 같습니다. sqlite pre populate data 라는 키워드로 검색해보면 관련 내용들을 찾아볼 수 있을듯합니다. 혹시 여유가 되신다면 한번 도전해보세요!

override fun onCreate(db: SQLiteDatabase) {
    // ... 데이터베이스 스키마 설정

    // Insert initial data
    val insertData = ("INSERT INTO $TABLE_NAME ($COLUMN_NAME, $COLUMN_AGE) VALUES "
            + "('John Doe', 30), "
            + "('Jane Doe', 25), "
            + "('Sam Smith', 22)")
    db.execSQL(insertData)
}

Choose a reason for hiding this comment

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

table1이 카페/약국 정보를 저장하기 위한 테이블이고, table2가 검색어 기록을 위한 테이블이네요. 데이터베이스에 미리 설정된 값을 넣어둔다면 table도 기록을 위한목적 하나만 사용가능해보이네요.

Copy link
Author

Choose a reason for hiding this comment

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

확인 했습니다!


private fun searchPlaces(query: String) {
val places = getPlacesFromDatabase(query)
if (places.isEmpty()) {
showNoResultMessage()
recyclerView.visibility = View.GONE
} else {
hideNoResultMessage()
recyclerView.visibility = View.VISIBLE
adapter.updateData(places)
}
}

private fun getPlacesFromDatabase(query: String): List<Place> {
return databaseHelper.getAllData().filter {
it.name.contains(query, ignoreCase = true) ||
it.address.contains(query, ignoreCase = true) ||
it.category.contains(query, ignoreCase = true)
}
}
Comment on lines +99 to +105

Choose a reason for hiding this comment

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

p2;

  • DB에 있는 모든 컬럼을 조회한후에 원하는 데이터를 필터링 하는것 보다 쿼리를 날릴때 WHERE, LIKE 조건을 추가해서 원하는 결과만 리턴받도록 하면 더 좋을것 같습니다. SQL 문법으로 작성한다면,LIKE '%query%' 같은 조건이 추가되겠네요. 커서쿼리 방식으로도 가능한 방법이 있는듯 하네요 (StackOverFlow)
  • SQLiteDb 클래스는 데이터베이스와 관련된 역할수행을 담당하고 있습니다. 그래서, SELECT 쿼리를 날리는 메소드도 SQLiteDb 에서 수행되는게 조금더 적절해보입니다.

Copy link
Author

Choose a reason for hiding this comment

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

확인 했습니다!


private fun showNoResultMessage() {
noResultTextView.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}

private fun hideNoResultMessage() {
noResultTextView.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
}

private fun updateHistory() {
val history = databaseHelper.getAllSelectedData()
historyAdapter.updateData(history)
}
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/Place.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package campus.tech.kakao.map;

data class Place(
val id: Int,
val name: String,
val address: String,
val category: String
)
38 changes: 38 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/PlacesAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package campus.tech.kakao.map

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class PlacesAdapter(private var places: List<Place>, private val onItemClick: (String) -> Unit) : RecyclerView.Adapter<PlacesAdapter.PlaceViewHolder>() {
Comment on lines +8 to +9

Choose a reason for hiding this comment

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

p3; PlacesAdapter를 생성할때 listof() 로 빈 리스트를 전달해주는데요.
클래스 멤버변수로 ArrayList 타입의 places를 변수를 가지고있고, 업데이트가 필요할때는 addAll 메소드로 이미 선언했던 리스트에 데이터를 삽입해주도록 하는게 조금더 좋을것 같습니다.

class PlacesAdapter (...) {
  private val places = arrayListOf<Place>()

  fun updateData(newPlaces: List<Place>) {
    places.addAll(newPlaces)
    notifyDataSetChanged()
  }
}

Copy link
Author

Choose a reason for hiding this comment

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

확인 했습니다!


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaceViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_place, parent, false)
return PlaceViewHolder(view)
}

override fun onBindViewHolder(holder: PlaceViewHolder, position: Int) {
val place = places[position]
holder.nameTextView.text = place.name
holder.addressTextView.text = place.address
holder.categoryTextView.text = place.category
holder.itemView.setOnClickListener {onItemClick(place.name)}
}

override fun getItemCount(): Int {
return places.size
}

fun updateData(newPlaces: List<Place>) {
places = newPlaces
notifyDataSetChanged()
}

class PlaceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val nameTextView: TextView = itemView.findViewById(R.id.nameTextView)
val addressTextView: TextView = itemView.findViewById(R.id.addressTextView)
val categoryTextView: TextView = itemView.findViewById(R.id.categoryTextView)
}
}
111 changes: 111 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/SQLiteDb.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package campus.tech.kakao.map

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import campus.tech.kakao.map.SQLiteHelper.Companion.COL_ID_2

class SQLiteDb(context: Context) {
private val dbHelper: SQLiteHelper = SQLiteHelper.getInstance(context)
private val database: SQLiteDatabase = dbHelper.writableDatabase

fun insertData(name: String, address: String, category: String): Long {
if (!isDataExists(name)) {
val values = ContentValues().apply {
put(SQLiteHelper.COL_NAME, name)
put(SQLiteHelper.COL_ADDRESS, address)
put(SQLiteHelper.COL_CATEGORY, category)
}
return database.insert(SQLiteHelper.TABLE_NAME, null, values)
}
return -1
}

fun isDataExists(name: String): Boolean {
val cursor = database.query(
SQLiteHelper.TABLE_NAME,
arrayOf(SQLiteHelper.COL_NAME),
"${SQLiteHelper.COL_NAME} = ?",
arrayOf(name),
null,
null,
null
)
val exists = cursor.count > 0
cursor.close()
return exists
}

fun getAllData(): List<Place> {
val places = mutableListOf<Place>()
val cursor = database.query(
SQLiteHelper.TABLE_NAME,
arrayOf(SQLiteHelper.COL_ID, SQLiteHelper.COL_NAME, SQLiteHelper.COL_ADDRESS, SQLiteHelper.COL_CATEGORY),
null,
null,
null,
null,
null
)

if (cursor.moveToFirst()) {
do {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_NAME))
val address = cursor.getString(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_ADDRESS))
val category = cursor.getString(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_CATEGORY))
places.add(Place(id, name, address, category))
} while (cursor.moveToNext())
}
cursor.close()

return places
}

fun logAllData() {
val cursor = getAllData()
for (place in cursor) {
println("ID: ${place.id}, Name: ${place.name}, Address: ${place.address}, Category: ${place.category}")
}
}

fun insertIntoSelectedData(name: String): Long {
val values = ContentValues().apply {
put(SQLiteHelper.COL_NAME_2, name)
}
return database.insert(SQLiteHelper.TABLE_NAME_2, null, values)
}

fun getAllSelectedData(): List<Pair<Int, String>> {
val selectedData = mutableListOf<Pair<Int, String>>()
val cursor = database.query(
SQLiteHelper.TABLE_NAME_2,
arrayOf(SQLiteHelper.COL_ID_2, SQLiteHelper.COL_NAME_2),
null,
null,
null,
null,
null
)

if (cursor.moveToFirst()) {
do {
val id = cursor.getInt(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_ID_2))
val name = cursor.getString(cursor.getColumnIndexOrThrow(SQLiteHelper.COL_NAME_2))
selectedData.add(id to name)
} while (cursor.moveToNext())
}
cursor.close()

return selectedData.reversed()
}

fun deleteFromSelectedData(id: Int): Int {
return database.delete(
SQLiteHelper.TABLE_NAME_2,
"$COL_ID_2 = ?",
arrayOf(id.toString())
)
}

}
Loading