오늘은 기존에 구현했던 kakaoAPI를 이용해서 이미지와 동영상을 가져오는 부분에 MVVM패턴을 적용시켜보았다.
기존에 ImageSearchFragment에서 사용하던 코드이다.
@SuppressLint("NotifyDataSetChanged")
private fun communicateNetWork(param: HashMap<String, String>) = lifecycleScope.launch() {
val authKey = "KakaoAK ${Contract.API_KEY}"
//기존 검색기록은 삭제
items.clear()
val imageResponseData = NetWorkClient.combinedNetWork.getImageResults(authKey, param)
val videoResponseData = NetWorkClient.combinedNetWork.getVideoResults(authKey, param)
imageResponseData.searchDocument?.let {
items.addAll(it.map { CombinedSearchItem(it, SearchItemType.IMAGE) })
}
videoResponseData.searchDocument?.let {
items.addAll(it.map { CombinedSearchItem(it, SearchItemType.VIDEO) })
}
items.shuffle()
items.sortedByDescending {
when (it.itemType) {
SearchItemType.IMAGE -> (it.searchItem as SearchDocument).datetime
SearchItemType.VIDEO -> (it.searchItem as SearchDocumentVideo).datetime
}
}
searchAdapter = SearchAdapter(items)
binding.searchRecyclerView.adapter = searchAdapter
searchAdapter.notifyDataSetChanged()
searchAdapter.itemClick = object : SearchAdapter.ItemClick {
override fun onClick(item: CombinedSearchItem) {
val selectedThumbnail: String
val selectedSiteName: String
val selectedTime: String
when (item.itemType) {
SearchItemType.IMAGE -> {
val result = item.searchItem as SearchDocument
selectedThumbnail = result.thumbnail_url
selectedSiteName = result.display_sitename
selectedTime = result.datetime
result.isLike = !result.isLike
}
SearchItemType.VIDEO -> {
val result = item.searchItem as SearchDocumentVideo
selectedThumbnail = result.thumbnail
selectedSiteName = result.title
selectedTime = result.datetime
result.isLike = !result.isLike
}
}
val selectedItems = SelectedItem(selectedThumbnail, selectedSiteName, selectedTime)
if (SelectedItem.myLikeList.contains(selectedItems)) {
SelectedItem.myLikeList.remove(selectedItems)
} else {
SelectedItem.myLikeList.add(selectedItems)
}
}
}
}
...
private fun setUpImageParameter(input: String, page:Int): HashMap<String, String> {
val authKey = "KakaoAK ${Contract.API_KEY}"
return hashMapOf(
"Authorization" to authKey,
"query" to input,
"sort" to "recency",
"page" to page.toString(),
"size" to "20"
)
}
imageSearchFragment에 대한 ViewModel을 만들어준다. 이름은 SearchViewModel로 만들어줬다.
class SearchViewModel : ViewModel() {
private val _searchParam: MutableLiveData<HashMap<String, String>> = MutableLiveData()
val searchParam: LiveData<HashMap<String, String>> get() = _searchParam
private val _searchResult: MutableLiveData<List<CombinedSearchItem>> = MutableLiveData()
val searchResult: LiveData<List<CombinedSearchItem>> get() = _searchResult
fun setUpParameter(input: String, page: Int) {
val authKey = "KakaoAK ${Contract.API_KEY}"
_searchParam.value = hashMapOf(
"Authorization" to authKey,
"query" to input,
"sort" to "recency",
"page" to page.toString(),
"size" to "20"
)
}
fun dataFromNetwork(param: HashMap<String, String>) {
viewModelScope.launch {
val authKey = "KakaoAK ${Contract.API_KEY}"
val items = mutableListOf<CombinedSearchItem>()
val imageResponseData = NetWorkClient.combinedNetWork.getImageResults(authKey, param)
val videoResponseData = NetWorkClient.combinedNetWork.getVideoResults(authKey, param)
imageResponseData.searchDocument?.let {
items.addAll(it.map { CombinedSearchItem(it, SearchItemType.IMAGE) })
}
videoResponseData.searchDocument?.let {
items.addAll(it.map { CombinedSearchItem(it, SearchItemType.VIDEO) })
}
items.shuffle()
items.sortedByDescending {
when (it.itemType) {
SearchItemType.IMAGE -> (it.searchItem as SearchDocument).datetime
SearchItemType.VIDEO -> (it.searchItem as SearchDocumentVideo).datetime
}
}
_searchResult.value = items
}
}
ViewModel()을 상속해서 만들어준다. 사용했던 함수를 대부분 그대로 가져와서 사용해주고 return값을 넣어주는 대신 _value값에 리턴할 값을 넣어준다. value는 _value값을 받아올거고, 뷰모델이아닌 뷰에서 observe를 이용해서 상태변화를 감지하고 값을 받아와줄거다.
searchParam 결과값을 받아와서 communication에 해당값을 넣어서 결과값을 가져와야한다.
@SuppressLint("NotifyDataSetChanged")
private fun initViewModel() =with(viewModel){
searchParam.observe(viewLifecycleOwner){
communicateNetWork(it)
}
searchResult.observe(viewLifecycleOwner){
items.clear()
items.addAll(it)
searchAdapter.notifyDataSetChanged()
}
}
onViewCreated에서 initViewModel를 실행시켜주고, 여기서 searchParam의 값을 옵저빙해서 검색됐을때의 값을 가져와준다.(it) 이 값을 communicateNetWork에 넣어서 결과를 받아와준다.
@SuppressLint("NotifyDataSetChanged")
private fun communicateNetWork(param: HashMap<String, String>) {
viewModel.dataFromNetwork(param)
}
그리고 여기서 나온 값 역시 옵저빙을 해서 값이 변화하면 감지해서 initViewModel()에서 searchResult를 설정해준다.
기존 아이템을 지우고, 받아온 사진,동영상들을 아이템에 다 추가해주고, 리사이클러뷰를 업데이트시켜줬다.
이렇게해서 기존에 MVC형태로 만들어졌던 코드를 기능은 viewModel에서, 뷰는 View에서 관리를 시켜줬다.
build.gradle.kts파일에서 키값을 숨기는법이다.
키값을 숨기지않고 그대로 깃허브에 올리면 보안상의 문제가 있기때문에 해당부분은 올리지말아야한다.
kakao_api_key = "숨길 키값"
이 파일 안에서 숨길 키값을 적어준다. 나는 kakao Rest키값을 숨겼다.
안에서 함수를 만들어준다.
fun getApiKey(propertyKey: String): String{
return gradleLocalProperties(rootDir).getProperty(propertyKey)
}
그리고 아래 위치에 추가해준다.
android{
defaultConfig{
buildConfigField("String", "kakao_api_key", getApiKey("kakao_api_key"))
}
buildFeatures {
buildConfig = true
}
}
이렇게 추가하고 sync를 한 후에 상단바에 Build -> cleanProject, RebuildProject를 해준다.
const val API_KEY = BuildConfig.kakao_api_key
이렇게 사용해주면된다.
mvvm
MVVM
디자인패턴
github에key값숨기기
깃허브에키값숨기기