안드로이드 2019 Dev Summit에서 발표된 Testing Coroutines 자료를 정리합니다.
Room history
Async Queries
Room에서는 Rx에 대한 지원을 제공합니다.
2.1부터 coroutines에 대한 suspend function을 제공했고, 2.2에서 observable한 Flow를 제공하면서 완벽한 coroutine을 지원합니다.
Flow
Flow는 kotlin에서 새로이 제공하는 coroutine builder입니다. (1.2.0에는 없었는데..1.3.2를 보니 갑자기 생겨 있더군요..)
비동기로 동작하며 Cold stream이기 때문에 lazy하게 동작시킬 수 있습니다.
또한 intermediate operator와 terminal operator가 존재합니다.
collection 또는 sequence와 유사한 operator들을 제공 합니다.
애완견을 얻는 예제로 flow를 확인해 보겠습니다.
랜덤하게 개를 얻는 코드로 flow를 생성하여 반환합니다.
이때 emit 함수로 값을 방출하며, 이는 suspend function이므로 동시에 수신쪽에서도 결과를 전달 받습니다.
collect() 함수를 호출하면, emitter에서 emit()을 호출할때 마다 하나씩 값을 받아와 출력합니다.
room에서도 return값으로 flow를 사용할 수 있습니다.
dogs table에서 정보를 읽어 반환할때, 모든 정보를 받아서 한번에 일회성 list로 반환하는게 아니라, flow로 전달 받습니다.
만약 table의 dogs 정보가 바뀐다면 이전 데이터를 포함하여 다시 list를 전달받을 수 있습니다
따라서 이런 coroutine의 장점은 lifecycle 함수와 맞물려 사용하면 그 효율성이 극대화 되겠죠?
Lifecycle-ktx library를 사용하면 lifecycle에 의하여 동작하는 flow를 dao를 통해서 전달 받을 수 있습니다.
위 예제에서 보듯이 Lifecycle-ktx에서 제공하는 Fragment를 사용할 경우 lifecycleScope을 이용하여 flow를 등록하여 데이터가 변화될때 마다 adpater의 데이터 set을 바꿔줄 수 있습니다.
lifcycleScope의 자세한 내용은 아래 링크를 참고하면 됩니다.
https://developer.android.com/topic/libraries/architecture/coroutines
flow의 대한 자세한 사용법은 아래 링크에서 참고하면 됩니다.
https://tourspace.tistory.com/258
Pre-packaged databases
Room database를 생성할때 기존에 포함된 db나 다운로드 받은 파일로 db를 생성할 수 있습니다.
createFromFile() -> (다운로드 받은)파일로 Room DB를 생성할때
createFromAsset() -> asset에 탑재하여 배포한 db를 이용하여 Room DB를 생성할때
저장하거나, 다운로드 받은 파일을 직접 오픈하는게 아니라 해당 파일들을 app의 internal database로 복사하여 open하기 때문에 각 파일들은 읽기 권한이 있어야 합니다.
Relations
그리고 강아지와 사람의 테이블을 아래와 같이 정의 합니다.
Pet 테이블에서는 petOwnerId를 통해서 Owner의 key를 가지고 있습니다.
위와 같이 Entity class로 table 정보를 지정합니다.
그리고 Pet Entity를 생성할때 foreignKey를 넣어줄 수 있습니다.
기본적으로 모든 사람과 강아지 정보를읽기 위해서는
1. Owner를 select함
2. Owner 결과만큼 for문을 돌면서 Pet 테이블을 select함.
으로 진행해야 합니다. (조인을 안하고..뺑뺑이 돌려서 뽑는 가장 무식한 방법 입니다.)
하지만 PetAndOwner 테이블을 하나 만들어 Pet 객체를 선언하고 @Relation annotation을 지정하여 테이블간의 정보를 지정해 줍니다.
(이게 foreign key로 지정되었기 때문에 정상 동작 되는지 foreign key가 없어도 되는지는....직접 해봐야 알것 같습니다.ㅠ.ㅠ)
@Relation 기능은 원래 있던 annotation 이지만 2.2에서부터는 collection이 아닌 object에도 사용할 수 있습니다.
이제 사람 한명이 여러마리의 강아지를 가질 수 있다고 가정합니다.
1:M 관계 입니다.
모든 사람이 같는 강아지를 뽑아내려면 위와 같이 쿼리해야 합니다.
똑같이 Owner를 select하고 결과를 for문으로 돌면서 Pet을 계속 select해야 합니다.
이를 room으로 해결 하려면 아래와 같이 OwnerWithPets table을 만들고 내부에 pets collection 변수를 만들어 relation을 선언합니다.
위와 같이 선언한다면 아래와 같이 Owner table을 select하도록 할때 원하는 결과를 얻을 수 있습니다.
M:M 관계입니다.
따라서 이는 Pet 테이블에 단순히 Owner의 key 컬럼을 포함하는 형태로는 정의할 수 없습니다.
당연히 두개의 관계를 가지는 Junction table인 PetOwner이라는 테이블을 만듭니다.
강아지와 사람의 정보를 담는 두개의 테이블과 이 두 테이블의 관계를 담는 테이블을 이용 해서 모든 정보를 뽑으려고 합니다.
일단 Owner 기준으로 뽑기 위해서 Owner를 select 합니다.
그리고 반환되는 결과를 for문으로 돌면서 pet table과 PetOwner을 조인한 query로 Pet의 정보를 뽑습니다.
이때 pets의 객체에 @Relation annotation을 설정하고 associateBy 값으로 Jonction table을 알려줍니다.
그리고 Owner 테이블 기준으로 Query하여 원하는 정보를 뽑아 옵니다.
이로써 @Relation 으로 1:1, 1:M, M:M을 모두 지원 할 수 있음을 알 수 있습니다.
Schema Default value
따라서 @ColumnInfo를 이용하여 기본값을 설정 할 수 있으며 실제 CURRENT_TIMESTAMP라는 값은 SQLite에서 현재시간을 표현하는 값 입니다.
(즉 SQLite에서 사용하는 표현(Keyword)을 그대로 사용할 수 있습니다)
Incremental option
하지만 아직 Experimental 이라고 합니다.
추후에는 기본으로 켜 놓을거라고 하네요.
Extension Projections
기본 Entity 이외에 실제로 일부 데이터를 반환하기 위해서 아래와 같이 Pup 클래스를 만들 수 있습니다.
하지만 아래와 같이 SQL을 작성할 경우 warning이 발생합니다.
실제로 query에 의해서 반환되는 column이 지정된 data class보다 많기 때문입니다.
이는 projection 값으로 * 을 사용했기 때문인데 이런 경우 아래와 같이 원하는 projection만을 명시해 줄 수 있습니다.
하지만 이는 아직 experimental 기능이라고 하네요...
그리고 이 expand projections을 사용하려면 빌드 config에 위와같이 추가해야 합니다.
마지막으로 테이블 join시 동일한 양쪽 테이블에 동일한 컬럼이 존재할 수 있습니다.
이때 어떤 컬럼을 어느 class에 담아야 할지 애매할때 prefix를 이용하여 지정해 줄 수 있습니다.
위와같이 join되는 컬럼의 특정 이름을 선언해 주고, data class 내부에서 prefix로 어떤 class에 해당 값이 담겨야 하는지를 명시해 줍니다.
'개발이야기 > Android' 카테고리의 다른 글
Android Room & Coroutines (0) | 2019.11.25 |
---|---|
Android Dev Summit 2019 - Debugging Tips n' Tricks (0) | 2019.11.20 |
Android Dev Summit 2019 - Testing Coroutines on Android (0) | 2019.11.15 |
Easy Coroutines in Android: viewModelScope (0) | 2019.11.14 |
Android JobScheduler API #2 (0) | 2017.10.27 |