---
# System prepended metadata

title: 기술 공유 - Travelog

---

# 기술 공유 - 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())
    }
}
```