-
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주차_과제 #33
Open
ichanguk
wants to merge
13
commits into
kakao-tech-campus-2nd-step2:ichanguk
Choose a base branch
from
ichanguk:step2
base: ichanguk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
693dc64
docs: update README.md
ichanguk 4fcc386
design: add Base Search Layout
ichanguk e93e703
feat: update Insert Data Feature
ichanguk 0691879
docs: update README.md
ichanguk 2ca8020
feat: merge PlaceDatabaseHelper into PlaceRepository
ichanguk c70a41f
feat: update Search Feature
ichanguk 9339061
feat: update Save Search Word Feature
ichanguk 9913f13
feat: update Delete Saved Search Word Feature
ichanguk beb45f7
docs: update README.md
ichanguk b88e836
feat: update Delete And Insert Existing Search Word Feature
ichanguk 5cf713b
feat: update Store And Delete Search Word Using PlaceId
ichanguk 13e9f79
refactor: improve Code Readability And Remove Duplicate Code
ichanguk 0a87ac8
refactor: refactor Suggested Improvements from code review
ichanguk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,27 @@ | ||
# android-map-keyword | ||
|
||
- 카카오 테크 캠퍼스 과제(검색어 저장) 수행을 위한 저장소입니다. | ||
|
||
## feature | ||
|
||
### 1단계 - 로컬 데이터 | ||
|
||
1. 검색어 입력 및 검색 결과를 표시할 기본 레이아웃 구현 | ||
|
||
2. 검색에 사용될 데이터를 로컬 베이스에 생성 | ||
- SQLite를 사용 | ||
|
||
### 2단계 - 검색 | ||
|
||
1. 검색어를 입력하면 검색 결과 목록이 표시된다. | ||
- 리사이클러뷰 사용(세로 스크롤) | ||
|
||
2. 입력한 검색어는 X를 눌러서 삭제할 수 있다. | ||
|
||
3. 검색 결과 목록에서 하나의 항목을 선택할 수 있다. | ||
- 선택된 항목은 검색어 저장 목록에 추가된다. | ||
- 리사이클러뷰 사용(가로 스크롤) | ||
|
||
4. 저장된 검색어는 X를 눌러서 삭제할 수 있다. | ||
|
||
5. 이미 검색어 저장 목록에 있는 검색어를 검색 결과 목록에서 선택한 경우 기존 검색어는 삭제하고 다시 추가한다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,279 @@ | ||
package campus.tech.kakao.map | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.widget.addTextChangedListener | ||
import androidx.databinding.DataBindingUtil | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.recyclerview.widget.DiffUtil | ||
import androidx.recyclerview.widget.LinearLayoutManager | ||
import androidx.recyclerview.widget.ListAdapter | ||
import androidx.recyclerview.widget.RecyclerView | ||
import campus.tech.kakao.map.databinding.ActivityMainBinding | ||
import campus.tech.kakao.map.databinding.ItemPlaceBinding | ||
import campus.tech.kakao.map.databinding.ItemSavedSearchWordBinding | ||
import campus.tech.kakao.map.model.Place | ||
import campus.tech.kakao.map.model.SavedSearchWord | ||
import campus.tech.kakao.map.viewmodel.PlaceViewModel | ||
import campus.tech.kakao.map.viewmodel.SavedSearchWordViewModel | ||
|
||
class MainActivity : AppCompatActivity() { | ||
private lateinit var binding: ActivityMainBinding | ||
private val placeViewModel by lazy { ViewModelProvider(this)[PlaceViewModel::class.java] } | ||
private val savedSearchWordViewModel by lazy { ViewModelProvider(this)[SavedSearchWordViewModel::class.java] } | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
setupBinding() | ||
setupDummyData() | ||
setupViews() | ||
observeViewModels() | ||
} | ||
|
||
private fun setupBinding() { | ||
binding = DataBindingUtil.setContentView(this, R.layout.activity_main) | ||
binding.apply { | ||
this.placeViewModel = [email protected] | ||
this.savedSearchWordViewModel = [email protected] | ||
this.lifecycleOwner = this@MainActivity | ||
} | ||
} | ||
|
||
/** | ||
* RecyclerView들을 설정하는 함수. | ||
*/ | ||
private fun setupRecyclerViews() { | ||
setSearchResultRecyclerView() | ||
setSavedSearchWordRecyclerView() | ||
} | ||
|
||
/** | ||
* 테스트용 더미 데이터를 place db에 삽입하기 위한 함수. | ||
*/ | ||
private fun setupDummyData() { | ||
placeViewModel.clearAllPlaces() | ||
testDataInsert() | ||
} | ||
|
||
/** | ||
* 테스트용 더미 데이터(카페, 약국)를 삽입하는 함수 | ||
*/ | ||
private fun testDataInsert() { | ||
insertPlaces("서울 성동구 성수동", "카페") | ||
insertPlaces("서울 강남구 대치동", "약국") | ||
insertPlaces("서울 강남구 수서동", "약국") | ||
} | ||
|
||
/** | ||
* place 데이터를 db에 삽입하는 함수. | ||
* | ||
* @param address 저장할 주소값 | ||
* @param category 저장할 카테고리값 | ||
*/ | ||
private fun insertPlaces( | ||
address: String, | ||
category: String, | ||
) { | ||
repeat(20) { idx -> | ||
placeViewModel.insertPlace( | ||
Place( | ||
name = "$category ${idx + 1}", | ||
address = "$address ${idx + 1}", | ||
category = category, | ||
), | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* view들에 필요한 작업을 처리하는 함수. | ||
*/ | ||
private fun setupViews() { | ||
setClearImageViewClickListener() | ||
setSearchEditText() | ||
setupRecyclerViews() | ||
} | ||
|
||
/** | ||
* 검색 EditText가 변경되면 placeViewModel을 통해 검색을 수행하도록 하는 함수. | ||
*/ | ||
private fun setSearchEditText() { | ||
binding.searchEditText.addTextChangedListener { editable -> | ||
val searchText = editable.toString().trim() | ||
placeViewModel.searchPlacesByCategory(searchText) | ||
} | ||
} | ||
|
||
/** | ||
* clearImageView의 클릭 리스너를 설정하는 함수. | ||
* | ||
* searchEditText의 text를 null로 변경. | ||
*/ | ||
private fun setClearImageViewClickListener() { | ||
binding.searchClearImageView.setOnClickListener { | ||
binding.searchEditText.text = null | ||
} | ||
} | ||
|
||
interface OnPlaceItemClickListener { | ||
fun onPlaceItemClicked(place: Place) | ||
} | ||
|
||
/** | ||
* 검색 결과를 표시하는 RecyclerView를 설정하는 함수. | ||
* | ||
* - `placeItemClickListener` : placeItem을 누르면 검색어가 저장되도록 하는 클릭 리스너 interface 구현 객체 | ||
*/ | ||
private fun setSearchResultRecyclerView() { | ||
val placeItemClickListener = | ||
object : OnPlaceItemClickListener { | ||
override fun onPlaceItemClicked(place: Place) { | ||
savedSearchWordViewModel.insertSearchWord( | ||
SavedSearchWord( | ||
name = place.name, | ||
placeId = place.id, | ||
), | ||
) | ||
} | ||
} | ||
binding.searchResultRecyclerView.adapter = ResultRecyclerViewAdapter(placeItemClickListener) | ||
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. Memory Leak, Garbage Collection |
||
binding.searchResultRecyclerView.layoutManager = LinearLayoutManager(this) | ||
} | ||
|
||
class ResultRecyclerViewAdapter(private val clickListener: OnPlaceItemClickListener) : | ||
ListAdapter<Place, ResultRecyclerViewAdapter.PlaceViewHolder>(PlaceDiffCallback()) { | ||
override fun onCreateViewHolder( | ||
parent: ViewGroup, | ||
viewType: Int, | ||
): PlaceViewHolder { | ||
val binding = ItemPlaceBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
return PlaceViewHolder(binding) | ||
} | ||
|
||
override fun onBindViewHolder( | ||
holder: PlaceViewHolder, | ||
position: Int, | ||
) { | ||
val place = getItem(position) | ||
|
||
holder.binding.place = place | ||
holder.itemView.setOnClickListener { | ||
clickListener.onPlaceItemClicked(place) | ||
} | ||
} | ||
|
||
class PlaceViewHolder(val binding: ItemPlaceBinding) : RecyclerView.ViewHolder(binding.root) | ||
|
||
private class PlaceDiffCallback : DiffUtil.ItemCallback<Place>() { | ||
override fun areItemsTheSame( | ||
oldItem: Place, | ||
newItem: Place, | ||
): Boolean { | ||
return oldItem.id == newItem.id | ||
} | ||
|
||
override fun areContentsTheSame( | ||
oldItem: Place, | ||
newItem: Place, | ||
): Boolean { | ||
return oldItem == newItem | ||
} | ||
} | ||
} | ||
|
||
interface OnSavedSearchWordClearImageViewClickListener { | ||
fun onSavedSearchWordClearImageViewClicked(savedSearchWord: SavedSearchWord) | ||
} | ||
|
||
/** | ||
* SavedSearchWordRecyclerView를 설정하는 함수. | ||
* | ||
* - `savedSearchWordClearImageViewClickListener` : clear 버튼을 누르면 해당 저장된 검색어가 사라지도록 하는 클릭리스너 interface 구현 객체 | ||
*/ | ||
private fun setSavedSearchWordRecyclerView() { | ||
val savedSearchWordClearImageViewClickListener = | ||
object : OnSavedSearchWordClearImageViewClickListener { | ||
override fun onSavedSearchWordClearImageViewClicked(savedSearchWord: SavedSearchWord) { | ||
savedSearchWordViewModel.deleteSearchWordById(savedSearchWord) | ||
} | ||
} | ||
binding.savedSearchWordRecyclerView.adapter = | ||
SavedSearchWordRecyclerViewAdapter(savedSearchWordClearImageViewClickListener) | ||
binding.savedSearchWordRecyclerView.layoutManager = | ||
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) | ||
} | ||
|
||
class SavedSearchWordRecyclerViewAdapter(private val clickListener: OnSavedSearchWordClearImageViewClickListener) : | ||
ListAdapter<SavedSearchWord, SavedSearchWordRecyclerViewAdapter.SavedSearchWordViewHolder>(SavedSearchWordDiffCallback()) { | ||
override fun onCreateViewHolder( | ||
parent: ViewGroup, | ||
viewType: Int, | ||
): SavedSearchWordViewHolder { | ||
val binding = ItemSavedSearchWordBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
return SavedSearchWordViewHolder(binding) | ||
} | ||
|
||
override fun onBindViewHolder( | ||
holder: SavedSearchWordViewHolder, | ||
position: Int, | ||
) { | ||
val savedSearchWord = getItem(position) | ||
holder.binding.savedSearchWord = savedSearchWord | ||
holder.itemView.setOnClickListener { | ||
clickListener.onSavedSearchWordClearImageViewClicked(savedSearchWord) | ||
} | ||
} | ||
|
||
class SavedSearchWordViewHolder( | ||
val binding: ItemSavedSearchWordBinding, | ||
) : RecyclerView.ViewHolder(binding.root) | ||
|
||
private class SavedSearchWordDiffCallback : DiffUtil.ItemCallback<SavedSearchWord>() { | ||
override fun areItemsTheSame( | ||
oldItem: SavedSearchWord, | ||
newItem: SavedSearchWord, | ||
): Boolean { | ||
return oldItem.id == newItem.id | ||
} | ||
|
||
override fun areContentsTheSame( | ||
oldItem: SavedSearchWord, | ||
newItem: SavedSearchWord, | ||
): Boolean { | ||
return oldItem == newItem | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* viewModel을 관찰하도록 하는 함수. | ||
*/ | ||
private fun observeViewModels() { | ||
observeSearchResults() | ||
observeSavedSearchWords() | ||
} | ||
|
||
/** | ||
* 검색 결과를 관찰하고, RecyclerView에 결과를 반영하는 함수. | ||
*/ | ||
private fun observeSearchResults() { | ||
placeViewModel.searchResults.observe( | ||
this, | ||
) { places -> | ||
(binding.searchResultRecyclerView.adapter as? ResultRecyclerViewAdapter)?.submitList(places) | ||
} | ||
} | ||
|
||
/** | ||
* 저장된 검색어를 관찰하고, RecyclerView에 결과를 반영하는 함수. | ||
*/ | ||
private fun observeSavedSearchWords() { | ||
savedSearchWordViewModel.savedSearchWords.observe( | ||
this, | ||
) { savedSearchWords -> | ||
(binding.savedSearchWordRecyclerView.adapter as? SavedSearchWordRecyclerViewAdapter)?.submitList(savedSearchWords) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
by viewModels()
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.
activity-ktx
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.
멘토링 때에도 말씀드렸다시피 전략적인 선택을 하면 됩니다. 다만 지금 현재 화면의 요구사항이 굉장히 단순하기 때문에 저는 fragment 로 따로 분리하지는 않을 것 같아요. 확장요구에 대한 낌새가 보일 때 fragment 로 분리하는 작업을 진행할 것 같습니다.
또한 같은 Fragment 혹은 Activity 내에서 ViewModel 을 여러개 사용하는 것은 관점에 따라 좋을 수도 나쁠 수도 있습니다. 위에도 언급했다시피 이 화면 자체의 요구사항이 매우 간단하기 떄문에 ViewModel 을 나누기 보다 저는 하나의 ViewModel 로 진행할 것 같습니다 :)