# 기술 공유 - Travelog ## CoordinatorLayout과 NestedScrollView ## 문제점 구글 지도, 네이버 지도 앱처럼, 처음 화면이 출력되면 지도와 간략한 정보만 보이게 하고, 위로 스크롤했을 때 지도의 크기가 감소함과 동시에 정보 화면의 크기가 커지도록 하고 싶었다. # 노원님! ↑해당 부분 이미지 있으면 좋을 것 같아요!! ![](https://i.imgur.com/rPz5dCt.png) ![](https://i.imgur.com/aHkMWPm.png) ## 해결 방법 <!-- 에뮬레이터 내 구글 지도 앱 실행 --> 정보 화면의 크기가 커짐으로써 사용자는 전체적인 정보를 볼 수 있게 된다. 이를 해결하기 위해 CoordinatorLayout과 NestedScrollView를 사용했다. CoordinatorLayout - AppBarLayout - CollapsingToolbarLayout 내에 지도 프래그먼트가 감싸져 있는 형태 각 레이아웃에는 다음과 같은 속성이 명시된다. - CoordinatorLayout, AppBarLayout ```XML android:fitsSystemWindows="true" ``` - CollapsingToolbarLayout ```XML android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed" ``` - LinearLayout - 지도 프래그먼트를 대체하기 위해 사용 ```XML android:fitsSystemWindows="true" app:layout_collapseMode="parallax" android:layout_height="500dp" app:layout_scrollFlags="scroll|enterAlways" ``` - 최대 높이를 지정해준다. - CollapseMode 속성으로 collapse 기능을 사용할 수 있다. AppBarLayout 다음 NestedScrollView를 작성한다. 이 때 다음과 같은 속성을 명시해줘야 한다. ``` android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="fill_vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior" ``` Behavior를 명시해줘야 스크롤에 대응할 수 있다. NestedScrollView 내 RecyclerView를 사용하면 RecyclerView의 ViewHolder가 재활용되지 않는다는 단점이 있다. 어플리케이션 성능 저하 간단한 데이터는 fold를 사용해서 문자열로 만들 수 있음 그래서 초반에는 ScrollView 내 TextView를 여러 개 생성하려고 했으나 복잡한 데이터를 반복적으로 출력할 때 문제가 생겼다. ```JSON "reviews": [ { "author_url": "https://www.google.com/maps/contrib/104262575615709467118/reviews", "language": "ko", "profile_photo_url": "https://lh3.googleusercontent.com/a/AATXAJyb5EcI1J6s_AHAzyZb6dWApU0c6Q6hyCKj9cegeg=s128-c0x00000000-cc-rp-mo-ba5", "rating": 4, "relative_time_description": "4달 전", "text": "쾌적하고, 편안한 분위기의 스타벅스. 하지만 너무 복잡하다.", "time": 1625740572 }, { "author_url": "https://www.google.com/maps/contrib/106700502147240245274/reviews", "language": "ko", "profile_photo_url": "https://lh3.googleusercontent.com/a-/AOh14GjERwFudt17HBDhBQrUXfbZTlyxeaGsEnFHLUCSYA=s128-c0x00000000-cc-rp-mo-ba6", "rating": 4, "relative_time_description": "1주 전", "text": "매장은 크지 않지만 아늑한 느낌이 있다.", "time": 1635756004 } ] ``` 처음에는 NestedScrollView를 없애고 RecyclerView 내 여러가지 뷰홀더를 생성하기로 했으나 피어세션을 통해 동적으로 뷰를 생성해 줄 수 있다는 것을 알게 되었고 어떤 방법을 적용할지 고민하고 있다. ![](https://i.imgur.com/o5QPXtx.png) ![](https://i.imgur.com/SD5vr9R.png) ![](https://i.imgur.com/35wr1LV.png) ## 커스텀 API 정의 ## 문제점 ### 사용하는 관광정보 API는 다음과 같은 기능이 있다. #### - 시군구 데이터 제공해주는 API #### - 각 도시에 대한 데이터를 제공해주는 API ![](https://i.imgur.com/M7CKQj8.png) ![](https://i.imgur.com/fUH0S1J.png) 가이드 화면을 구성하기 위해서는 각 도시에 대해서 도시 정보 API를 호출해야 됐다. 시군구 데이터를 여러 번 조회하여 각 도시의 도시코드를 얻은 각 도시 코드를 매개변수로 넘겨 도시 정보 API를 호출하여 정보를 얻어내야 한다. ### 한 번 가이드 화면을 출력할 때 관광정보 API를 20번 호출해야 한다. 관광정보 API가 일일 최대 호출량이 1천번이라 데이터를 어떻게 더 효율적으로 불러올 수 있을까 생각해봤다. ## 해결방법 ### Serverless Framework를 사용해서 AWS Lambda, AWS API Gateway로 구성된 API를 빠르게 만들 수 있다. 각 4개의 API 주소에 따라서 원하는 데이터를 한 번에 불러올 수 있다. ![](https://i.imgur.com/crCpizA.png) [가이드 화면](https://fk4zc712ck.execute-api.ap-northeast-2.amazonaws.com/dev/guide) [전체 데이터](https://fk4zc712ck.execute-api.ap-northeast-2.amazonaws.com/dev1) [지역 검색](https://fk4zc712ck.execute-api.ap-northeast-2.amazonaws.com/dev/search/%EB%B6%80%EC%82%B0) [경기도 데이터](https://fk4zc712ck.execute-api.ap-northeast-2.amazonaws.com/dev/code/31) ## LiveData, Mediator LiveData ## 문제점 ![](https://i.imgur.com/mhXeWrY.png) ![](https://i.imgur.com/Wp7KTy5.png) ### - (여행일자, 여행장소, 여행이미지, 코멘트) 형태로 DB에 존재 ### - 초기에 DB에서 load해와서 viewModel의 list에 담아 보여주는 형태 ### - 이미지 list / 기타정보 list 따로 존재 ### - 코멘트 수정 시 DB에 해당 내용 update ### - 변경사항 보여주기 위해서 다시 DB에서 load ## :question: 1개 update위해 여러개를 load? -> 과하다 ------------ ## 해결방법 ### - Dao클래스의 반환타입을 LiveData로 ### - Update 사용시 바로 반영되므로 추가적인 load작업 불필요 ### - 이미지와 기타정보를 분리하지말고 같이 다뤄보자 -> Mediator LiveData ### Mediator LiveData란 #### 다른 `LiveData` 객체를`observe`하고 해당 객체의 `OnChanged()` 이벤트를 받는 `LiveData` 하위 클래스이다. `MediatorLiveData`에 변화가 탐지되면, `MediatorLiveData`에 추가한 다른 `LiveData` 객체들을 각각 `observe`할 수 있다. - 안드로이드 공식 문서 예시 ```kotlin= val liveData1<LiveData<>> = ...; val liveData2<LiveData<>> = ...; val liveDataMerger = MediatorLiveData<>(); liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value)); liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value)); ``` - 예시 - 아이디와 비밀번호가 입력되어야 버튼이 활성화되어야 하는 상황 ```kotlin= class TestViewModel : ViewModel() { val userId = MutableLiveData<String>() val userPassword = MutableLiveData<String>() private val _isProcessComplete = MediatorLiveData<Boolean>() val isProcessComplete: LiveData<Boolean> = _isProcessComplete init { with(isProcessComplete) { addSource(userId) { checkProcess(userId, userPassword) } addSource(userPassword) { checkProcess(userId, userPassword) } } } private fun checkProcess(userId: LiveData<String>, userPassword: LiveData<String>) { _isProcessComplete.value = !(userId.value.isNullOrEmpty() || userPassword.value.isNullOrEmpty()) } } ```