# 기술 공유 - Travelog
## CoordinatorLayout과 NestedScrollView
## 문제점
구글 지도, 네이버 지도 앱처럼, 처음 화면이 출력되면 지도와 간략한 정보만 보이게 하고, 위로 스크롤했을 때 지도의 크기가 감소함과 동시에 정보 화면의 크기가 커지도록 하고 싶었다.
# 노원님! ↑해당 부분 이미지 있으면 좋을 것 같아요!!
 
## 해결 방법
<!-- 에뮬레이터 내 구글 지도 앱 실행 -->
정보 화면의 크기가 커짐으로써 사용자는 전체적인 정보를 볼 수 있게 된다.
이를 해결하기 위해 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 내 여러가지 뷰홀더를 생성하기로 했으나 피어세션을 통해 동적으로 뷰를 생성해 줄 수 있다는 것을 알게 되었고 어떤 방법을 적용할지 고민하고 있다.
  
## 커스텀 API 정의
## 문제점
### 사용하는 관광정보 API는 다음과 같은 기능이 있다.
#### - 시군구 데이터 제공해주는 API
#### - 각 도시에 대한 데이터를 제공해주는 API


가이드 화면을 구성하기 위해서는 각 도시에 대해서 도시 정보 API를 호출해야 됐다.
시군구 데이터를 여러 번 조회하여 각 도시의 도시코드를 얻은 각 도시 코드를 매개변수로 넘겨 도시 정보 API를 호출하여 정보를 얻어내야 한다.
### 한 번 가이드 화면을 출력할 때 관광정보 API를 20번 호출해야 한다.
관광정보 API가 일일 최대 호출량이 1천번이라 데이터를 어떻게 더 효율적으로 불러올 수 있을까 생각해봤다.
## 해결방법
### Serverless Framework를 사용해서 AWS Lambda, AWS API Gateway로 구성된 API를 빠르게 만들 수 있다.
각 4개의 API 주소에 따라서 원하는 데이터를 한 번에 불러올 수 있다.

[가이드 화면](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
## 문제점
 
### - (여행일자, 여행장소, 여행이미지, 코멘트) 형태로 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())
}
}
```