-
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 #32
base: normenghub
Are you sure you want to change the base?
Changes from all commits
439780c
502739c
9079ac3
670e00a
c01c1b6
60382eb
210a741
4d764ca
9ab3b8f
f29075b
74b8c69
5a27745
ce63ed1
bed845b
f6e49e6
000cab5
63f5349
c13d732
7150c22
232c882
5df541b
b3a9562
84a23be
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,25 @@ | ||
# android-map-keyword | ||
|
||
# 2단계 - 검색 | ||
|
||
****** | ||
|
||
|
||
## 기능 요구 사항 | ||
|
||
검색어를 입력하면 검색 결과 목록이 표시된다. | ||
|
||
검색 결과 목록은 세로 스크롤이 된다. | ||
|
||
입력한 검색어는 X를 눌러서 삭제할 수 있. | ||
|
||
검색 결과 목록에서 하나의 항목을 선택할 수 있다. | ||
|
||
선택된 항목은 검색어 저장 목록에 추가된다. | ||
|
||
저장된 검색어 목록은 가로 스크롤이 된다. | ||
|
||
저장된 검색어는 X를 눌러서 삭제할 수 있다. | ||
|
||
저장된 검색어는 앱을 재실행하여도 유지된다. | ||
|
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 | ||
} | ||
} | ||
} |
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) | ||
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
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. p3; 변수를 선언할때는 어떤 의미를 담고있는지 최대한 나타내주는것이 좋습니다. 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. 확인 했습니다! |
||
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
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. 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)
} 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. table1이 카페/약국 정보를 저장하기 위한 테이블이고, table2가 검색어 기록을 위한 테이블이네요. 데이터베이스에 미리 설정된 값을 넣어둔다면 table도 기록을 위한목적 하나만 사용가능해보이네요. 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. 확인 했습니다! |
||
|
||
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
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. p2;
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. 확인 했습니다! |
||
|
||
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) | ||
} | ||
} | ||
} |
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 | ||
) |
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
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. p3; PlacesAdapter를 생성할때 class PlacesAdapter (...) {
private val places = arrayListOf<Place>()
fun updateData(newPlaces: List<Place>) {
places.addAll(newPlaces)
notifyDataSetChanged()
}
} 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. 확인 했습니다! |
||
|
||
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) | ||
} | ||
} |
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()) | ||
) | ||
} | ||
|
||
} |
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.
p4; 지연초기화로 필요할때 클래스를 대입해주는것도 괜찮지만, 클래스가 생성되는 시점에 만들어도 되는 클래스까지
lateinit var
을 사용할 필요까지는 없습니다. lateinit도 결국 mutable variable이기때문에 변경가능하다는 취약점이 있고, 바뀌지 않는다면 클래스 멤버변수들이 초기화 될때 인스턴스가 생성되어도 괜찮을것 같습니다. 가령 SQLiteDb 나 PlacesAdapter, HistoryAdapter 같은 클래스들이 예시가 되겠네요.지연초기화 방법에는
by lazy
라는것도 있으니, 차이점을 비교하면서 사용해보시면 더 좋을것 같습니다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.
XML에
app:layoutManager="..."
속성을 사용했다면, Activity에서 LayoutManager를 설정해주지 않더라도 inflate될때 자동으로 설정되게 됩니다. 따라서, recyclerView, historyRecyclerView에 layoutManager를 설정해주는건 중복코드가 될것 같네요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.
확인 했습니다!