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주차 과제 2단계 #65

Open
wants to merge 31 commits into
base: joominchul
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3b598dd
Docs[README.md]: 생성 리드미
joominchul Jul 5, 2024
2c41560
Design: 생성 검색 화면 UI
joominchul Jul 5, 2024
4f9066a
Feat: 생성 데이터베이스
joominchul Jul 5, 2024
96926be
Feat: 생성 addPlace
joominchul Jul 5, 2024
6080cee
Feat[PlaceDbHelper.kt]: 생성 existPlace
joominchul Jul 5, 2024
9a75a01
Feat[PlaceDbHelper.kt]: 변경 addPlace
joominchul Jul 5, 2024
8ab4b19
Feat[PlaceDbHelper.kt]: 추가 searchPlaceName
joominchul Jul 5, 2024
a2f41fa
Feat[PlaceDbHelper.kt]: 추가 existData
joominchul Jul 5, 2024
175ca91
Feat[]: 추가 검색에 사용될 데이터
joominchul Jul 5, 2024
c3c7e22
Docs[README.md]: 추가 2단계
joominchul Jul 5, 2024
76521a0
Docs[README.md]: 추가 이미지 출처
joominchul Jul 5, 2024
c4505c0
Feat[]: 추가 검색 결과 목록 출력
joominchul Jul 6, 2024
217b4d8
Feat[MainActivity.kt]: 추가 X를 눌러 입력 검색어 삭제
joominchul Jul 6, 2024
119090f
Fix[PlaceDbHelper.kt]: 변경 existPlace
joominchul Jul 6, 2024
5aaa892
Refactor[]: Place DB 이름을 상수로 변경
joominchul Jul 6, 2024
790679a
Rename[PlaceAdapter]: 변경 이름
joominchul Jul 6, 2024
c2baa69
Refactor[PlaceDbHelper]: 변경 existData
joominchul Jul 6, 2024
78eacb1
Feat[]: 추가 검색 결과 목록 기능
joominchul Jul 6, 2024
568b0f9
Feat[SearchWordDbHelper]: 추가 검색어 x 눌러서 삭제 기능
joominchul Jul 6, 2024
204ffcb
Feat[]: 추가 검색어 재실행 유지
joominchul Jul 6, 2024
08a7a27
Refactor[]: 변경 검색어 검색 인식 방법
joominchul Jul 7, 2024
0890b9e
Refactor[]: 변경 UI세팅 함수명
joominchul Jul 7, 2024
2bfa781
Comment[MainActivity]: 삭제 테스트용 로그
joominchul Jul 7, 2024
b868cb3
Refactor[MainActivity]: 변경 warning 부분
joominchul Jul 7, 2024
326f711
Refactor[]: 변경 place와 word 라이브데이터 타입
joominchul Jul 7, 2024
3dcef65
Refactor[]: 삭제 필요 없는 import, 변경 WordfromPlace 함수 이름
joominchul Jul 7, 2024
ac38146
Refactor[]: 변경 _place, _searchWords 타입
joominchul Jul 7, 2024
8d5bdd7
Refactor[]: 추가 DB Version 상수
joominchul Jul 7, 2024
083eeb6
Refactor[]: 추가 createTable
joominchul Jul 7, 2024
de4f9f0
Refactor[]: 변경 어댑터 클릭리스너
joominchul Jul 7, 2024
c0f5b5b
Refactor[]: 변경 어댑터에 ListAdapter 사용
joominchul Jul 8, 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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,33 @@
# android-map-keyword
# 1단계

## 기능 요구 사항
- 카카오맵 클론 코딩을 위한 시작입니다.
- 검색어 입력 및 검색 결과를 표시할 기본 레이아웃을 구현한다.
- 검색에 사용될 데이터를 로컬 데이터베이스에 생성한다.

## 프로그래밍 요구 사항
- 검색 데이터는 저장은 SQLite를 사용한다.
- 가능한 MVVM 아키텍처 패턴을 적용하도록 한다.
- 코드 컨벤션을 준수하며 프로그래밍한다.

# 2단계

## 기능 요구 사항
- 검색어를 입력하면 검색 결과 목록이 표시된다.
- 검색 결과 목록은 세로 스크롤이 된다.
- 입력한 검색어는 X를 눌러서 삭제할 수 있다.
- 검색 결과 목록에서 하나의 항목을 선택할 수 있다.
- 선택된 항목은 검색어 저장 목록에 추가된다.
- 목록에 있는 검색 결과를 다시 선택하면 기존 저장 목록에서 지워지고 가장 끝에 다시 추가된다.
- 저장된 검색어 목록은 가로 스크롤이 된다.
- 저장된 검색어는 X를 눌러서 삭제할 수 있다.
- 저장된 검색어는 앱을 재실행하여도 유지된다.

## 프로그래밍 요구 사항
- 검색 결과 목록은 리사이클러뷰를 사용한다.
- 가능한 MVVM 아키텍처 패턴을 적용하도록 한다.

## 이미지 출처
- 위치 마커 아이콘 제작자: bsd - Flaticon
- 문자 x 아이콘 제작자: Freepik - Flaticon
4 changes: 3 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ android {
}

dependencies {

val lifecycle_version = "2.8.3"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
Expand Down
78 changes: 78 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,89 @@
package campus.tech.kakao.map

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

private lateinit var model: MainViewModel
private lateinit var search:EditText
private lateinit var clear: TextView
private lateinit var noResult: TextView
private lateinit var searchResult: RecyclerView
private lateinit var searchWordResult: RecyclerView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
UISetting()
searchResult.layoutManager = LinearLayoutManager(this)
searchWordResult.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
search.addTextChangedListener(object : TextWatcher{
joominchul marked this conversation as resolved.
Show resolved Hide resolved
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val query = s.toString()
if (query.isNullOrEmpty()){
noResult.visibility = View.VISIBLE
searchResult.visibility = View.GONE
}else{
noResult.visibility = View.GONE
searchResult.visibility = View.VISIBLE
model.search(query)
}
}

override fun afterTextChanged(s: Editable?) {
}

})
model = ViewModelProvider(this).get(MainViewModel::class.java)
model.placeList.observe(this, Observer {
if (it.isNullOrEmpty()){
noResult.visibility = View.VISIBLE
searchResult.visibility = View.GONE
}else{
noResult.visibility = View.GONE
searchResult.visibility = View.VISIBLE
searchResult.adapter = PlaceAdapter(it, model)
}
})
model.loadWord()
model.wordList.observe(this, Observer {
if (it.isNullOrEmpty()){
searchWordResult.visibility = View.GONE
Log.d("testt", "null")
joominchul marked this conversation as resolved.
Show resolved Hide resolved
}
else{
searchWordResult.visibility = View.VISIBLE
searchWordResult.adapter = WordAdapter(it, model)
Log.d("testt", it[0].name)
}
})

}

fun UISetting(){
joominchul marked this conversation as resolved.
Show resolved Hide resolved
search = findViewById(R.id.search)
clear = findViewById(R.id.search_clear)
noResult = findViewById(R.id.no_search_result)
searchResult = findViewById(R.id.search_result_recycler_view)
searchWordResult = findViewById(R.id.search_word_recycler_view)
clear.setOnClickListener {
search.setText("")
}
}

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

import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel(application: Application): AndroidViewModel(application) {
private val placeDbHelper = PlaceDbHelper(application)
val placeList: LiveData<MutableList<Place>> get() = placeDbHelper._place
joominchul marked this conversation as resolved.
Show resolved Hide resolved

private val wordDbHelper = SearchWordDbHelper(application)
val wordList: LiveData<MutableList<SearchWord>> get() = wordDbHelper._searchWords

fun insertInitData(){
if (!placeDbHelper.existData()){
for(i in 1..10){
placeDbHelper.addPlace(Place("카페 $i", "남양주 $i", "카페"))
placeDbHelper.addPlace(Place("약국 $i", "남양주 $i", "약국"))
}
}

}

fun search(query: String) {
placeDbHelper.searchPlaceName(query)
}

fun addWord(place: Place){
wordDbHelper.addWord(WordfromPlace(place))
}

fun WordfromPlace(place: Place):SearchWord{
joominchul marked this conversation as resolved.
Show resolved Hide resolved
return SearchWord(place.name, place.address, place.type)
}
fun deleteWord(word: SearchWord){
wordDbHelper.deleteWord(word)
}

fun loadWord(){
wordDbHelper.updateSearchWords()
}
}
3 changes: 3 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,3 @@
package campus.tech.kakao.map

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

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

class PlaceAdapter(
val placeList: MutableList<Place>,
val viewModel: MainViewModel
): RecyclerView.Adapter<PlaceAdapter.ViewHolder>() {
inner class ViewHolder(
itemView: View
): RecyclerView.ViewHolder(itemView) {
val name:TextView = itemView.findViewById(R.id.name)
val address:TextView = itemView.findViewById(R.id.address)
val type:TextView = itemView.findViewById(R.id.type)
init {
itemView.setOnClickListener {
val position:Int = bindingAdapterPosition
val place:Place = placeList[position]
viewModel.addWord(place)
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.place_item, parent, false))
}

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

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.name.text = placeList[position].name
holder.address.text = placeList[position].address
holder.type.text = placeList[position].type
}
}


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

import android.provider.BaseColumns

object PlaceContract:BaseColumns {
const val DB_NAME = "place.db"
const val TABLE_NAME = "place"
const val COLUMN_NAME_NAME = "name"
const val COLUMN_NAME_ADDRESS = "address"
const val COLUMN_NAME_TYPE = "type"
}
100 changes: 100 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/PlaceDbHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package campus.tech.kakao.map

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.lifecycle.MutableLiveData

class PlaceDbHelper(context: Context):SQLiteOpenHelper(
context, PlaceContract.DB_NAME, null, 2) {
joominchul marked this conversation as resolved.
Show resolved Hide resolved
val _place = MutableLiveData<MutableList<Place>>()
joominchul marked this conversation as resolved.
Show resolved Hide resolved
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL("CREATE TABLE ${PlaceContract.TABLE_NAME} " +
"(${PlaceContract.COLUMN_NAME_NAME} TEXT, " +
"${PlaceContract.COLUMN_NAME_ADDRESS} TEXT, " +
"${PlaceContract.COLUMN_NAME_TYPE} TEXT)")
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("DROP TABLE IF EXISTS ${PlaceContract.TABLE_NAME}")
onCreate(db)
joominchul marked this conversation as resolved.
Show resolved Hide resolved
}

fun addPlace(place: Place) {
val db = writableDatabase
if (!existPlace(place, db)){
val values = ContentValues()
values.put(PlaceContract.COLUMN_NAME_NAME, place.name)
values.put(PlaceContract.COLUMN_NAME_ADDRESS, place.address)
values.put(PlaceContract.COLUMN_NAME_TYPE, place.type)
db.insert(PlaceContract.TABLE_NAME, null, values)
db.close()
}
}

fun existData(): Boolean{
val db = readableDatabase
val cursor = db.query(
PlaceContract.TABLE_NAME,
arrayOf(PlaceContract.COLUMN_NAME_NAME),
null,
null,
null,
null,
null
)
val result = cursor.moveToFirst()
cursor.close()
return result
}

fun existPlace(place: Place, db: SQLiteDatabase): Boolean{
val selection = "${PlaceContract.COLUMN_NAME_NAME} = ? AND " +
"${PlaceContract.COLUMN_NAME_ADDRESS} = ? AND " +
"${PlaceContract.COLUMN_NAME_TYPE} = ?"
val cursor = db.query(
PlaceContract.TABLE_NAME,
arrayOf(PlaceContract.COLUMN_NAME_NAME),
selection,
arrayOf(place.name, place.address, place.type),
null,
null,
"${PlaceContract.COLUMN_NAME_NAME} DESC"
)

val result = cursor.moveToFirst()
cursor.close()
return if (result) true else false
}

fun searchPlaceName(name: String){
val resultList = mutableListOf<Place>()
var searchResult = "%${name}%"
val cursor = readableDatabase.query(
PlaceContract.TABLE_NAME,
arrayOf(PlaceContract.COLUMN_NAME_NAME,
PlaceContract.COLUMN_NAME_ADDRESS,
PlaceContract.COLUMN_NAME_TYPE),
"${PlaceContract.COLUMN_NAME_NAME} like ?",
arrayOf(searchResult),
null,
null,
"${PlaceContract.COLUMN_NAME_NAME} ASC"
)

while (cursor.moveToNext()) {
val name = cursor.getString(
cursor.getColumnIndexOrThrow(PlaceContract.COLUMN_NAME_NAME)
)
val address = cursor.getString(
cursor.getColumnIndexOrThrow(PlaceContract.COLUMN_NAME_ADDRESS)
)
val type = cursor.getString(
cursor.getColumnIndexOrThrow(PlaceContract.COLUMN_NAME_TYPE))
resultList.add(Place(name, address, type))
}
cursor.close()
_place.value = resultList
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/SearchWord.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package campus.tech.kakao.map

data class SearchWord(val name: String, val address: String, val type: String)
10 changes: 10 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/SearchWordContract.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package campus.tech.kakao.map

object SearchWordContract {
const val DB_NAME = "search_word.db"
const val TABLE_NAME = "SearchWord"
const val COLUMN_NAME_NAME = "name"
const val COLUMN_NAME_ADDRESS = "address"
const val COLUMN_NAME_TYPE = "type"

}
Loading