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

[강원대 안드로이드 주민철] 4주차 과제 스텝1 #43

Open
wants to merge 30 commits into
base: joominchul
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8ae483b
Rename[]: 이동 3주차 과제 내용
joominchul Jul 15, 2024
7c76c97
Refactor[]: 변경 warning 부분
joominchul Jul 15, 2024
ba17eaf
Docs[README.md]: 추가 요구 사항 정리
joominchul Jul 15, 2024
4c2c111
Feat[]: 추가 선택 항목 위치, 정보를 지도에 표시
joominchul Jul 16, 2024
c69b181
Refactor[]: 변경 warning 요소
joominchul Jul 16, 2024
2613a7d
Design[bottom_sheet]: 변경 패딩
joominchul Jul 16, 2024
daefd35
Feat[]: 추가 저장된 검색어 선택 시 해당 검색어 검색 결과 표시
joominchul Jul 16, 2024
4bc4784
Feat[]: 추가 카카오지도 onMapError() 호출 시 에러 화면 표시
joominchul Jul 17, 2024
c4ad1b7
Merge branch 'joominchul' into feat-joominchul-step1
joominchul Jul 17, 2024
91bb445
Feat[]: 추가 MapPreferences에서 Document를 받아서 저장 기능
joominchul Jul 20, 2024
9a521a2
Refactor[MapActivity]: 변경 뷰 lazy 초기화
joominchul Jul 20, 2024
7e68e17
Refactor[MainViewModel]: 추가 initLatitude, initLongitude
joominchul Jul 20, 2024
668d100
Refactor[MainViewModel]: 추가 getMapInfo 함수
joominchul Jul 20, 2024
b7891d4
Refactor[MapActivity]: 변경 멤버변수
joominchul Jul 20, 2024
f224649
Refactor[MainViewModel]: 제거 initLatitude, initLongitude getter 함수
joominchul Jul 20, 2024
cd1e93e
Refactor[MapActivity]: 변경 Initial Value들 상수로
joominchul Jul 20, 2024
988f859
Refactor[MapActivity]: 변경 ViewModel 맴액티비티를 오너로 설정
joominchul Jul 21, 2024
244c9fd
Refactor[MapActivity]: 변경 KakaoMap 내에 있는 멤버 변수들
joominchul Jul 21, 2024
505088c
Refactor[MyApplication]: 변경 mapPosition 싱글톤 패턴으로
joominchul Jul 21, 2024
66d2aac
Rename[]: 패키지 삭제 후 이동
joominchul Jul 21, 2024
5949ab8
Refactor[]: 변경 mapPosition
joominchul Jul 22, 2024
6049de6
Refactor[]: 변경 mapPosition
joominchul Jul 22, 2024
3ad3f78
Refactor[]: 추가 콜백 메서드 인터페이스
joominchul Jul 22, 2024
556aef4
Refactor[]: 변경 var
joominchul Jul 22, 2024
82b07ff
Refactor[]: 변경 람다 형식
joominchul Jul 22, 2024
829170b
Comment[]: 삭제 주석
joominchul Jul 22, 2024
21f5470
Refactor[SameName]: 변경 selectedRegion 이름
joominchul Jul 22, 2024
17e03c3
Rename[]: 생성 url 패키지
joominchul Jul 22, 2024
1b75544
Refactor[]: 변경 상수
joominchul Jul 22, 2024
da92697
Refactor[DocumentAdapter]: 변경 placeClicked
joominchul Jul 22, 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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# android-map-location

## 1단계 - 카카오맵 API 심화
### 기능 요구 사항
- 저장된 검색어를 선택하면 해당 검색어의 검색 결과가 표시된다.
- 검색 결과 목록 중 하나의 항목을 선택하면 해당 항목의 위치를 지도에 표시한다.
- 앱 종료 시 마지막 위치를 저장하여 다시 앱 실행 시 해당 위치로 포커스 한다.
- 카카오지도 onMapError() 호출 시 에러 화면을 보여준다.
### 프로그래밍 요구 사항
- BottomSheet를 사용한다.
- 카카오 API 사용을 위한 앱 키를 외부에 노출하지 않는다.
- 가능한 MVVM 아키텍처 패턴을 적용하도록 한다.
- 코드 컨벤션을 준수하며 프로그래밍한다.
11 changes: 0 additions & 11 deletions app/src/main/java/campus/tech/kakao/map/MainActivity.kt

This file was deleted.

15 changes: 12 additions & 3 deletions app/src/main/java/campus/tech/kakao/map/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package campus.tech.kakao.map
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import campus.tech.kakao.map.DBHelper.SearchWordDbHelper
import campus.tech.kakao.map.DTO.Document
import campus.tech.kakao.map.DTO.SearchWord
import campus.tech.kakao.map.dbHelper.SearchWordDbHelper
import campus.tech.kakao.map.dto.Document
import campus.tech.kakao.map.dto.MapPositionContract
import campus.tech.kakao.map.dto.SearchWord
import campus.tech.kakao.map.MyApplication.Companion.mapPosition
import campus.tech.kakao.map.RetrofitData.Companion.getInstance

class MainViewModel(application: Application): AndroidViewModel(application) {
Expand Down Expand Up @@ -39,4 +41,11 @@ class MainViewModel(application: Application): AndroidViewModel(application) {
super.onCleared()
wordDbHelper.close()
}

fun getMapInfo(document: Document){
mapPosition.setPreferences(MapPositionContract.PREFERENCE_KEY_LATITUDE, document.latitude)
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
mapPosition.setPreferences(MapPositionContract.PREFERENCE_KEY_LONGITUDE, document.longitude)
mapPosition.setPreferences(MapPositionContract.PREFERENCE_KEY_PLACENAME, document.placeName)
mapPosition.setPreferences(MapPositionContract.PREFERENCE_KEY_ADDRESSNAME, document.addressName)
}
}
100 changes: 99 additions & 1 deletion app/src/main/java/campus/tech/kakao/map/MapActivity.kt
Original file line number Diff line number Diff line change
@@ -1,54 +1,152 @@
package campus.tech.kakao.map

import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import campus.tech.kakao.map.MyApplication.Companion.mapPosition
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.kakao.vectormap.KakaoMap
import com.kakao.vectormap.KakaoMapReadyCallback
import com.kakao.vectormap.LatLng
import com.kakao.vectormap.MapLifeCycleCallback
import com.kakao.vectormap.MapView
import com.kakao.vectormap.camera.CameraUpdate
import com.kakao.vectormap.camera.CameraUpdateFactory
import com.kakao.vectormap.label.Label
import com.kakao.vectormap.label.LabelLayer
import com.kakao.vectormap.label.LabelOptions
import com.kakao.vectormap.label.LabelStyle
import com.kakao.vectormap.label.LabelStyles
import java.lang.Exception

class MapActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private var map: KakaoMap? = null
private lateinit var searchBar: LinearLayout
private var latitude = 37.402005
private var longitude = 127.108621
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
private lateinit var placeName:String
private lateinit var addressName:String
private var styles: LabelStyles? = null
private lateinit var options:LabelOptions
private var layer: LabelLayer? = null
private var label: Label? = null
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
private lateinit var bitmapImage: Bitmap
private lateinit var markerImage: Bitmap
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>
private val bottomSheet by lazy { findViewById<LinearLayout>(R.id.bottom_sheet) }
private val bottomSheetName by lazy { findViewById<TextView>(R.id.name) }
private val bottomSheetAddress by lazy { findViewById<TextView>(R.id.address) }
Copy link

Choose a reason for hiding this comment

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

View를 lazy 초기화 하면 어떤 에러가 발생할 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

아직 초기화 되지 않은 시점에서 사용이 되면 UninitializedPropertyAccessException이 발생할 수 있습니다. 또한 액티비티가 소멸된 후에도 뷰가 참조되어 메모리 누수가 발생할 수도 있습니다.

Copy link

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.

리팩토링 완료 했습니다.

companion object{
var documentClicked = false
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.map_view)
setMapInfo()
mapView.start(object : MapLifeCycleCallback() {
override fun onMapDestroy() {

}

override fun onMapError(p0: Exception?) {
Log.e("MapActivity", "onMapError: ${p0?.message}", p0)
setContentView(R.layout.map_error)
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
val errorText = findViewById<TextView>(R.id.map_error_text)
errorText.text = p0?.message
}

}, object: KakaoMapReadyCallback() {
override fun onMapReady(kakaoMap: KakaoMap) {
map = kakaoMap
}

override fun getPosition(): LatLng {
return LatLng.from(latitude, longitude)
}

override fun getZoomLevel(): Int {
return 17
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
}
})
searchBar = findViewById(R.id.search_bar)
searchBar.setOnClickListener {
val intent = Intent(this@MapActivity, SearchActivity::class.java)
startActivity(intent)
}
initBottomSheet()
}

override fun onResume() {
super.onResume()
setMapInfo()
mapView.resume()
if(documentClicked){
makeMarker()
setBottomSheet()
documentClicked = false
}
else{
layer?.remove(label)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newCenterPosition(LatLng.from(latitude, longitude))
map?.moveCamera(cameraUpdate)
}

override fun onPause() {
super.onPause()
mapView.pause()
}

private fun setMapInfo(){
latitude = mapPosition.getPreferences("latitude","37.406960").toDouble()
longitude = mapPosition.getPreferences("longitude","127.110030").toDouble()
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
placeName = mapPosition.getPreferences("placeName","")
addressName = mapPosition.getPreferences("addressName","")
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
}

private fun makeMarker(){
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
bitmapImage = BitmapFactory.decodeResource(resources, R.drawable.marker)
markerImage = Bitmap.createScaledBitmap(bitmapImage, 100, 100, true)
styles = map?.labelManager?.addLabelStyles(LabelStyles.from(LabelStyle.from(markerImage).setTextStyles(40, Color.BLACK)))
if(styles != null){
options = LabelOptions.from(LatLng.from(latitude, longitude)).setStyles(styles).setTexts(placeName)
layer = map?.labelManager?.layer
if(label != null){
layer?.remove(label)
}
label = layer?.addLabel(options)
}
else{
Log.e("MapActivity", "makeMarker: styles is null")
}
}

private fun initBottomSheet(){
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback(){
override fun onStateChanged(bottomSheet: View, newState: Int) {
}

override fun onSlide(bottomSheet: View, slideOffset: Float) {
}

})
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}

private fun setBottomSheet(){
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
bottomSheetName.text = placeName
bottomSheetAddress.text = addressName
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MyApplication.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package campus.tech.kakao.map

import android.app.Application
import campus.tech.kakao.map.dto.MapPositionPreferences
import com.kakao.vectormap.KakaoMapSdk

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
mapPosition = MapPositionPreferences(this)
KakaoMapSdk.init(this, BuildConfig.KAKAO_API_KEY)
}

companion object{
lateinit var mapPosition : MapPositionPreferences
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
}
}
17 changes: 11 additions & 6 deletions app/src/main/java/campus/tech/kakao/map/RetrofitData.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package campus.tech.kakao.map

import androidx.lifecycle.MutableLiveData
import campus.tech.kakao.map.DTO.Document
import campus.tech.kakao.map.DTO.PlaceResponse
import campus.tech.kakao.map.DTO.UrlContract
import campus.tech.kakao.map.DTO.UrlContract.AUTHORIZATION
import campus.tech.kakao.map.dto.Document
import campus.tech.kakao.map.dto.PlaceResponse
import campus.tech.kakao.map.dto.UrlContract
import campus.tech.kakao.map.dto.UrlContract.AUTHORIZATION
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
Expand All @@ -28,8 +28,13 @@ class RetrofitData private constructor() {
if (response.isSuccessful) {
val documentList = mutableListOf<Document>()
val body = response.body()
body?.documents?.forEach {
documentList.add(Document(it.placeName, it.categoryGroupName, it.addressName))
body?.documents?.forEach {document ->
documentList.add(Document(
document.placeName,
document.categoryGroupName,
document.addressName,
document.longitude,
document.latitude))
}
_documents.value = documentList
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/campus/tech/kakao/map/RetrofitService.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package campus.tech.kakao.map

import campus.tech.kakao.map.DTO.PlaceResponse
import campus.tech.kakao.map.dto.PlaceResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
Expand Down
27 changes: 17 additions & 10 deletions app/src/main/java/campus/tech/kakao/map/SearchActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import campus.tech.kakao.map.Adapter.DocumentAdapter
import campus.tech.kakao.map.Adapter.WordAdapter
import campus.tech.kakao.map.adapter.DocumentAdapter
import campus.tech.kakao.map.adapter.WordAdapter
mkSpace marked this conversation as resolved.
Show resolved Hide resolved

class SearchActivity : AppCompatActivity() {

Expand All @@ -29,13 +29,20 @@ class SearchActivity : AppCompatActivity() {
setupUI()
searchResult.layoutManager = LinearLayoutManager(this)
searchWordResult.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
documentAdapter = DocumentAdapter(){ Document ->
model.addWord(Document)
}
wordAdapter = WordAdapter() { SearchWord ->
model.deleteWord(SearchWord)
}
search.doOnTextChanged { text, start, before, count ->
documentAdapter = DocumentAdapter({ document ->
model.addWord(document)
},{document ->
model.getMapInfo(document)
finish()
})
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
wordAdapter = WordAdapter(
{ searchWord ->
model.deleteWord(searchWord)
},{ searchWord ->
model.searchLocalAPI(searchWord.name)
}
)
search.doOnTextChanged { text, _, _, _ ->
val query = text.toString()
if (query.isEmpty()){
noResult.visibility = View.VISIBLE
Expand Down Expand Up @@ -71,7 +78,7 @@ class SearchActivity : AppCompatActivity() {
})
}

fun setupUI(){
private fun setupUI(){
search = findViewById(R.id.search)
clear = findViewById(R.id.search_clear)
noResult = findViewById(R.id.no_search_result)
Expand Down
60 changes: 60 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/adapter/DocumentAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package campus.tech.kakao.map.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import campus.tech.kakao.map.dto.Document
import campus.tech.kakao.map.MapActivity.Companion.documentClicked
import campus.tech.kakao.map.R

class DocumentAdapter(
val addWord: (Document) -> Unit,
val sendDocumentInfo: (Document) -> Unit
): ListAdapter<Document, DocumentAdapter.ViewHolder>(
object : DiffUtil.ItemCallback<Document>(){
Copy link

Choose a reason for hiding this comment

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

현재 작성하신 Diff Callback이 어떤 역할을 하는지 설명 부탁드립니다.

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

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.

areItemsTheSame와 areContentsTheSame 함수로 판단할 수 있는데, areItemsTheSame 함수는 아이템이 같은 주소에 할당이 되어 있는지를 판단하고, areContentsTheSame 함수는 그 내용이 같은지를 판단합니다.

Copy link

Choose a reason for hiding this comment

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

areItemsTheSame은 true인데 areContentsTheSame이 false인 경우도 있을까요?

override fun areItemsTheSame(oldItem: Document, newItem: Document): Boolean {
return oldItem === newItem
}

override fun areContentsTheSame(oldItem: Document, newItem: Document): Boolean {
return oldItem == newItem
}

}
) {
private var placeClicked = { position:Int ->
mkSpace marked this conversation as resolved.
Show resolved Hide resolved
val document: Document = getItem(position)
addWord(document)
sendDocumentInfo(document)
documentClicked = true
Copy link

Choose a reason for hiding this comment

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

콜백을 묶지 마세요! 콜백 메서드가 여러개가 함께 실행되어야 한다 -> 비즈니스 로직입니다.
판단하는 로직을 View에 두지 마세요
documentCliced 라고 하는 인스턴스 변수를 계속 변경하고 계시는데 객체간 통신은 이런 방식으로 하는게 아닙니다 ㅜㅜ

Copy link
Author

Choose a reason for hiding this comment

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

잘 이해가 가질 않습니다...ㅠ
다시 setOnClickListener에서 콜백을 호출하게 해야 할까요?

Copy link

Choose a reason for hiding this comment

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

Adapter 내에서 place를 클릭 하면 addWord 하고, sendDocumentInfo 하고 외부 객체의 인스턴스 변수인 documentClicked에 접근해 값을 설정해주는 이 일련의 동작들이 모두 비즈니스 로직이라는 말씀을 드리려고 했던거였어요.
비즈니스 로직이라 생각되는 부분들은 모두 ViewModel로 옮기셔야 합니다.

Copy link
Author

Choose a reason for hiding this comment

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

DocumentAdapter 내에 있던 placeClicked 함수를 리팩토링하여 뷰모델 객체를 만들고, 뷰모델로 관련 로직을 옮겼습니다.
뷰모델 내에서는 callback의 onWordAdded와 onDocumentInfoSet 함수를 호출하게 하였으며, 기존 documentClicked 변수는 뷰모델 내 라이브 데이터로 변경하였습니다.
그로 인해 MapActivity에 새로이 라이브데이터 observe를 만들어 검색 결과가 클릭이 되면 라벨(마커)를 생성하고, 그렇지 않으면 제거하게끔 하였습니다.
DocumentAdapter에서 뷰모델 객체를 생성할 때 val viewModel = MainViewModel(MyApplication()) 이렇게 하는 게 맞는지, 또한 뷰모델 내 placeClicked 함수에서 callback 함수들을 호출하는 것이 맞는지 검토 부탁드립니다.

Copy link

Choose a reason for hiding this comment

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

네 사실 documentClicked는 UI 상태와 관련된 데이터잖아요. 그래서 View를 구성하기 위한 데이터를 관장하는 ViewModel로 그 로직을 옮기신 부분 잘 구현하셨습니다. 다만 아직 부족한 부분이 있는데요. 사용자와 상호작용 하는 부분과 UI State를 어떻게 View와 바인딩 해야 하는지 순서에 대해 이해하실 필요가 있습니다. 아래 설명드리는 순서대로 설계해보세요!
사용자가 RecyclerView 내에서 아이템을 클릭하는 상호작용을 했다고 가정했을 때

  1. View(Activity)는 상호작용을 인지하고 ViewModel에 관련 로직을 실행하게끔 알려줍니다!
  2. ViewModel은 비즈니스 로직을 실행하면서 UI에 맞게 데이터를 변경합니다
  3. 변경된 데이터를 View는 표시합니다.

근데 구현하신걸 보면 클릭 이벤트를 Adapter에 추가해서 Adapter에서 ViewModel을 생성 후 ViewModel의 onPlaceClicked 호출 하고 ViewModel에서는 또 Activity에 구현된 Callback을 통해 View에 구현된 로직 실행하고 또 그 구현 로직들이 ViewModel을 호출하고 있는데 이러면 일원화가 안되는 문제가 있죠.

서면으로 답변 드리기 어려운 부분이 있으니 멘토링 신청해주시면 코드 같이 보면서 설명드리겠습니다

}
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 {
placeClicked(bindingAdapterPosition)
}
}
}

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

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val document: Document = getItem(position)
holder.name.text = document.placeName
holder.address.text = document.addressName
holder.type.text = document.categoryGroupName
}
}


Loading