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단계) #55

Open
wants to merge 32 commits into
base: sumintnals
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1ca1295
docs(README): 기능 목록 추가
sumintnals Jul 1, 2024
da15dde
feat(search_history_item): 저장된 검색어 보여주는 아이템 뷰 UI 구현
sumintnals Jul 1, 2024
3ca3473
feat(place_item): 검색 결과 리스트 뷰의 아이템 뷰 구현
sumintnals Jul 1, 2024
dcb732e
feat(place_item): 검색 결과 리스트 아이템 뷰 구현
sumintnals Jul 1, 2024
c2149fd
feat(activity_main): 메인 뷰 레이아웃 구현
sumintnals Jul 1, 2024
0fe2c16
feat(Place): Place 데이터 클래스 추가
sumintnals Jul 1, 2024
6d8ba38
feat(DBHelper): DBHelper 클래스, PlaceContract 작성
sumintnals Jul 1, 2024
fc3fd89
feat(PlaceRepository): PlaceRepository 생성
sumintnals Jul 1, 2024
89f53cf
feat(DBHelper): insertDummyData() 함수 구현
sumintnals Jul 1, 2024
828ecd4
feat(PlaceRepository): dbClose()함수 추가
sumintnals Jul 4, 2024
86fdd51
feat(MainActivity): 뷰모델 추가
sumintnals Jul 4, 2024
8072b2d
feat(PlaceAdapter): 리스트뷰 위한 PlaceAdapter 추가
sumintnals Jul 4, 2024
89855fe
feat(MainActivity): Place 리스트뷰 연결
sumintnals Jul 4, 2024
3980334
feat(MainActivity): 검색기록 뷰 연결
sumintnals Jul 4, 2024
917463a
docs(README): 기능 사항 2단계로 수정
sumintnals Jul 4, 2024
5013e50
feat(MainActivity): TextWatcher 추가(추가만 해둠)
sumintnals Jul 4, 2024
5740bec
feat(PlaceAdapter): ListAdapter 어댑터에서 RecyclerView 어댑터로 변경
sumintnals Jul 4, 2024
bc9c04c
feat(MainActivity): 메인 액티비티에 리사이클러 뷰 연결
sumintnals Jul 4, 2024
67a59dd
feat(MainActivity): 검색어 기능 구현
sumintnals Jul 4, 2024
c8faa90
chore(README): 기능 사항 수정(1단계 내용 삭제)
sumintnals Jul 4, 2024
b2da1d3
feat(MainActivity): X 눌러 검색어 삭제 기능 구현
sumintnals Jul 4, 2024
1871f63
feat(MainActivity): 검색어 목록 누르면 검색 기록 저장되는 기능 추가
sumintnals Jul 5, 2024
db3e5c7
feat(MainActivity): 검색 기록 클릭 시 해당 검색어로 검색하는 기능 구현
sumintnals Jul 5, 2024
76f177a
feat(MainActivity): 검색 기록 X버튼 누르면 삭제되는 기능 구현
sumintnals Jul 5, 2024
595cef6
refactor(PreferenceManager): setArrayList()를 private로 변경
sumintnals Jul 5, 2024
5fc7d6e
refactor(MainActivity): 로그 삭제...
sumintnals Jul 5, 2024
0d8ab18
design(Map): 아이콘 추가, 앱 색상 변경
sumintnals Jul 5, 2024
b7d2069
refactor(MainActivity): MVVM 구현하려고 노력...
sumintnals Jul 5, 2024
3fdaa81
design(activity_main): 리사이클러 뷰 아래 보이도록 수정
sumintnals Jul 5, 2024
2722e8d
refactor(MainActivity): lateinit -> lazy 변경, 코드 함수로 분리
sumintnals Jul 8, 2024
8642b58
refactor(Adapter): itemClickListener 생성자로 받아 사용하도록 변경
sumintnals Jul 8, 2024
674d762
refactor(MainViewModel): 초기화 블록 이동, 코드 리포맷팅
sumintnals 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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# android-map-keyword
# android-map-keyword (2단계)

### 기능 사항
- 검색어 입력 레이아웃 구현한다.0
- 검색에 사용될 데이터를 로컬 데이터베이스에 생성한다.0
- 검색어를 입력하면 검색 결과 목록이 표시된다.0
- 검색 결과 목록은 세로 스크롤이 된다. RecyclerView를 사용한다.0
- 입력한 검색어는 X를 눌러서 삭제할 수 있다. 0
- 검색 결과 목록에서 하나의 항목을 선택할 수 있다.0
- 선택된 항목은 검색어 저장 목록에 추가된다. 0
- 저장된 검색어 목록은 가로 스크롤이 된다. 0
- 저장된 검색어는 X를 눌러서 삭제할 수 있다.
- 저장된 검색어는 앱을 재실행하여도 유지된다.0

### 프로그래밍 요구 사항
- 가능한 MVVM 아키텍처 패턴을 적용하도록 한다.

6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
}

android {
Expand Down Expand Up @@ -40,6 +41,11 @@ android {
}

dependencies {
kapt("com.android.databinding:compiler:3.1.4")
implementation("androidx.fragment:fragment-ktx:1.8.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3")
implementation("com.google.code.gson:gson:2.11.0")

implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<application
android:name="campus.tech.kakao.map.MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
Binary file added app/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package campus.tech.kakao.map

object Constants {
const val SEARCH_HISTORY_KEY = "searchHistory"
const val PREFERENCE_NAME = "preference_name"
}
54 changes: 54 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/DBHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package campus.tech.kakao.map

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class DBHelper (context: Context): SQLiteOpenHelper(context, "place.db", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(
"CREATE TABLE IF NOT EXISTS ${PlaceContract.TABLE_NAME} ("+
"${PlaceContract.TABLE_COLUMN_NAME} VARCHAR(30) NOT NULL,"+
"${PlaceContract.TABLE_COLUMN_ADDRESS} VARCHAR(50) NOT NULL,"+
"${PlaceContract.TABLE_COLUMN_CATEGORY} VARCHAR(30) NOT NULL"+
");"
)
insertDummyData(db)
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("DROP TABLE IF EXISTS ${PlaceContract.TABLE_NAME}")
onCreate(db)
}

fun insert(db: SQLiteDatabase, place: Place) {
val sql = " INSERT INTO " +
"${PlaceContract.TABLE_NAME}("+
"${PlaceContract.TABLE_COLUMN_NAME}, ${PlaceContract.TABLE_COLUMN_ADDRESS}, ${PlaceContract.TABLE_COLUMN_CATEGORY})"+
" VALUES(${place.name}, ${place.address}, ${place.category});"

db.execSQL(sql)
}

fun select(db: SQLiteDatabase, name:String, address: String, category: String) : String? {
return null

Choose a reason for hiding this comment

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

코드가 아직 구현되지 않은 것 같은데 맞을까요?

Copy link
Author

Choose a reason for hiding this comment

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

앗 네!! 현재 PlaceRepository의 getSearchResults()함수를 사용해서 검색 결과를 가져오고 있습니다! 이 함수의 위치를 옮겨야 할까요?

    fun getSearchResults(searchText: String): List<Place> {
        val rDb = dbHelper.readableDatabase
        val places = mutableListOf<Place>()
        val query = "SELECT * FROM ${PlaceContract.TABLE_NAME} WHERE ${PlaceContract.TABLE_COLUMN_NAME} LIKE ?"
        val cursor = rDb.rawQuery(query, arrayOf("%$searchText%"))

        if (cursor != null) {
            if (cursor.moveToFirst()) {
                do {
                    val name = cursor.getString(cursor.getColumnIndexOrThrow(PlaceContract.TABLE_COLUMN_NAME))
                    val address = cursor.getString(cursor.getColumnIndexOrThrow(PlaceContract.TABLE_COLUMN_ADDRESS))
                    val category = cursor.getString(cursor.getColumnIndexOrThrow(PlaceContract.TABLE_COLUMN_CATEGORY))
                    val place = Place(name, address, category)
                    places.add(place)
                } while (cursor.moveToNext())
            }
            cursor.close()
        }
        return places
    }

Copy link
Author

Choose a reason for hiding this comment

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

그리고 Repository를 삭제하고 뷰모델에 기존 Repository에 있던 함수를 옮기면 될까요??

}

private fun insertDummyData(db: SQLiteDatabase?) {
db?.let {
val categories = listOf("카페", "약국", "식당")
val baseAddress = "서울 성동구 성수동"

categories.forEach { category ->
for (i in 1..15) {
val name = "$category $i"
val address = "$baseAddress $i"
val sql = "INSERT INTO ${PlaceContract.TABLE_NAME} (" +
"${PlaceContract.TABLE_COLUMN_NAME}, ${PlaceContract.TABLE_COLUMN_ADDRESS}, ${PlaceContract.TABLE_COLUMN_CATEGORY}) " +
"VALUES ('$name', '$address', '$category');"
it.execSQL(sql)
}
}
}
}
}
53 changes: 53 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,53 @@
package campus.tech.kakao.map

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import campus.tech.kakao.map.databinding.SearchHistoryItemBinding

class HistoryAdapter(var items: List<SearchHistory>, val inflater: LayoutInflater) : RecyclerView.Adapter<HistoryAdapter.HistoryViewHolder>() {

interface OnItemClickListener {
fun onItemClick(position: Int)
fun onXMarkClick(position: Int)
}

var itemClickListener: OnItemClickListener? = null
sumintnals marked this conversation as resolved.
Show resolved Hide resolved

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder {
val binding = SearchHistoryItemBinding.inflate(inflater, parent, false)
return HistoryViewHolder(binding)
}

override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
holder.bind(items[position])
}

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

fun setData(searchHistory: List<SearchHistory>) {
items = searchHistory
notifyDataSetChanged()
}

inner class HistoryViewHolder(private val binding: SearchHistoryItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(searchHistory: SearchHistory) {
binding.history.text = searchHistory.searchHistory
}

init {
sumintnals marked this conversation as resolved.
Show resolved Hide resolved
itemView.setOnClickListener {
itemClickListener?.onItemClick(absoluteAdapterPosition)
}

binding.xmark.setOnClickListener {
itemClickListener?.onXMarkClick(absoluteAdapterPosition)
}
}
}
}
101 changes: 100 additions & 1 deletion app/src/main/java/campus/tech/kakao/map/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,110 @@
package campus.tech.kakao.map

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import campus.tech.kakao.map.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels {
ViewModelFactory(applicationContext)
}

private lateinit var placeAdapter: PlaceAdapter
private lateinit var historyAdapter: HistoryAdapter
sumintnals marked this conversation as resolved.
Show resolved Hide resolved
private var placeList: List<Place> = emptyList()

override fun onCreate(savedInstanceState: Bundle?) {
sumintnals marked this conversation as resolved.
Show resolved Hide resolved
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
mainBinding.placeResult.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

if (placeList.isNullOrEmpty()) {
mainBinding.emptyMainText.visibility = View.VISIBLE
} else {
mainBinding.emptyMainText.visibility = View.GONE
}

// 검색어 기록 토대로 UI 업데이트
viewModel.searchHistoryList.observe(this@MainActivity, Observer {
historyAdapter.setData(it)
})
viewModel.getSearchHistoryList()

// 검색 필터링
val searchEditText = mainBinding.search

// 검색 결과 토대로 UI 업데이트
viewModel.placeList.observe(this@MainActivity, Observer {
(mainBinding.placeResult.adapter as? PlaceAdapter)?.setData(it)
if (it.isNullOrEmpty()) {
mainBinding.emptyMainText.visibility = View.VISIBLE
} else {
mainBinding.emptyMainText.visibility = View.GONE
}
})

placeAdapter = PlaceAdapter(placeList, LayoutInflater.from(this@MainActivity))
mainBinding.placeResult.apply {
layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.VERTICAL, false)
adapter = placeAdapter
}

searchEditText.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?) {
val searchText = searchEditText.text.toString()
viewModel.getSearchResult(searchText)
}
}
)

// X 누르면 초기화
mainBinding.xmark.setOnClickListener {
searchEditText.setText("")
}

historyAdapter = HistoryAdapter(
viewModel.searchHistoryList.value ?: emptyList()
, LayoutInflater.from(this@MainActivity))
mainBinding.searchHistory.apply {
layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
adapter = historyAdapter
}

// PlaceAdapter OnclickListener
placeAdapter.itemClickListener = object : PlaceAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val item = placeAdapter.getItem(position)
val searchHistory = SearchHistory(item.name)
viewModel.saveSearchHistory(searchHistory)
}
}

// HistoryAdapter OnclickListener
historyAdapter.itemClickListener = object : HistoryAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val item = viewModel.searchHistoryList.value?.get(position)
if (item != null) {
searchEditText.setText(item.searchHistory)
}
}

override fun onXMarkClick(position: Int) {
viewModel.deleteSearchHistory(position)
}
}
}
}
58 changes: 58 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,58 @@
package campus.tech.kakao.map

import android.util.Log
import android.widget.EditText
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel(private val placeRepository: PlaceRepository) : ViewModel() {
private val searchRepository: SearchRepository = SearchRepository()

private var _placeList = MutableLiveData<List<Place>>()
private val _searchHistoryList = MutableLiveData<List<SearchHistory>>()

val searchHistoryList: LiveData<List<SearchHistory>>
get() = _searchHistoryList
init {

Choose a reason for hiding this comment

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

nit; 위의 코멘트를 확인해주세요. + 코드 포맷팅도 함께 적용해주세요. IDE에서 제공하는 기본 기능을 활용하시면 될 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

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

넵!

Copy link
Author

Choose a reason for hiding this comment

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

초기화 블록을 위로 올리고, Code->Reformat Code를 하면 될까요??

_searchHistoryList.value = searchRepository.getSearchHistory()
}
val placeList: LiveData<List<Place>>
get() = _placeList

fun insertPlace(place: Place) {
placeRepository.insertPlace(place)
}

fun getPlace(): List<Place>? {
return placeRepository.getPlace()
}

override fun onCleared() {
super.onCleared()
placeRepository.dbClose()
}

fun getSearchResult(searchText: String) {
if (searchText.isEmpty()) {
_placeList.postValue(emptyList())
} else {
val results = placeRepository.getSearchResults(searchText)
_placeList.postValue(results)
}
}

fun saveSearchHistory(searchHistory: SearchHistory) {
searchRepository.saveSearchHistory(searchHistory)
_searchHistoryList.value = searchRepository.getSearchHistory()
}

fun deleteSearchHistory(position: Int) {
searchRepository.deleteSearchHistory(position)
_searchHistoryList.value = searchRepository.getSearchHistory()
}

fun getSearchHistoryList() {
_searchHistoryList.value = searchRepository.getSearchHistory()
}
}
15 changes: 15 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,15 @@
package campus.tech.kakao.map

import android.app.Application
import android.content.SharedPreferences

class MyApplication: Application() {
companion object {
lateinit var prefs: PreferenceManager
}

override fun onCreate() {
prefs = PreferenceManager(applicationContext)
super.onCreate()
}
}
9 changes: 9 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,9 @@
package campus.tech.kakao.map

import androidx.annotation.DrawableRes

data class Place (
val name: String,
val address: String,
val category: String,
)
Loading