ListAdapter, DiffUtil

개발/안드로이드 2021. 8. 20. 09:24 Posted by 아는 개발자

ListAdapter는 RecyclerView.Adapter의 확장기능으로 리스트내에 노출할 아이템의 변경 여부를 백그라운드 쓰레드에서 판단할 수 있는 기능을 제공한다. 생성자에는 DiffUtil.ItemCallback의 구체 클래스를 넘겨주는데 여기서 구현하는 두 함수를 이용해 아이템 변경 유무를 판단한다

 

class BookAdapter() : ListAdapter<Book, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<Book>() {
    override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
        return oldItem.isbn13 == newItem.isbn13
    }

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

 

areItemsTheSame 은 두 아이템이 같은 것인지 확인한다. 아이템마다 고유의 id 가 있다면 두 아이템은 같은 것으로 볼 수 있다. 예제처럼 책을 리스트로 노출한다면 isbn이 동일할 때 같은 책인 것으로 볼 수 있다.

 

areContentsTheSame 은 같다고 알려진 두 아이템의 변경 유무를 확인 한다. return 값이 true면 동일한 것이고 false면 변경이 일어난 경우다. 같은 id 를 가졌더라도 클래스 내부의 값이 달라졌다면 변경이 된 것이기 때문에 다른 것으로 본다. 코드상에서처럼 두 객체를 비교해도 되고 성능을 올리려면 변경유무를 판단할 때 사용하는 속성값만 넣어도 된다. 책의 경우에는 책 제목이나 가격 속성을 이용해도 좋을 것 같다.

 

최종적으로 areContentsTheSame 이 false 인 item에 대해서만 onBindVieholder 함수가 호출 된다. Bind 함수 내에 많은 작업을 처리하는 경우 성능 개선에 도움이 될 것 같다.

 

주의사항

 

https://stackoverflow.com/a/50031492

 

ListAdapter not updating item in RecyclerView

I'm using the new support library ListAdapter. Here's my code for the adapter class ArtistsAdapter : ListAdapter<artist, artistsadapter.viewholder="">(ArtistsDiff()) { override fun</artist,>

stackoverflow.com

 

submitList로 리스트를 업데이트 할 때는 리스트 주소값도 바뀌어야 한다. ListAdapter에서 DiffUtil로 사용하는 AsyncListDiffer 클래스는 새로운 리스트가 기존 리스트와 같은 객체인 경우에는 값을 업데이트하지 않는다. 이는 곧 내부에 데이터를 아무리 바꿔도 리스트의 주소값이 동일하면 변경된 값을 화면에 노출시키지 않는다는 의미다. 이럴때는 깊은 복사(list deep copy)를 사용해 완전히 새로운 객체를 만드는 수밖에 없는데, 리스트 내 객체를 빼거나 추가하는 경우가 아니면 리스트 내부 객체까지 깊은 복사를 해야 리스트의 깊은 복사가 된다. 

 

public class AsyncListDiffer<T> {
    private final ListUpdateCallback mUpdateCallback;
    ...
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    public void submitList(@Nullable final List<T> newList,    
            @Nullable final Runnable commitCallback) {
        // incrementing generation means any currently-running diffs are discarded when they finish
        final int runGeneration = ++mMaxScheduledGeneration;

        if (newList == mList) {
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }

 

성능을 위한 선택이었겠지만 내부 데이터 변경도 볼 수 없는 어댑터는 사용성이 크게 떨어지는 것 같다.

728x90

'개발 > 안드로이드' 카테고리의 다른 글

RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04

오브젝트 리뷰 - 6

기술/아키텍처 2021. 8. 9. 20:00 Posted by 아는 개발자

유연한 설계

 

개방-폐쇄 원칙 

 

개방-폐쇄 원칙은 런타임 의존성과 컴파일타임 의존성에 관한 이야기다. 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있다. 개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것이다. 올바른 추상화를 설계하고 추상화에 대해서만 의존하도록 관계를 제한함으로써 설계를 유연하게 확장할 수 있다.

 

생성과 사용의 분리

 

유연하고 재사용 가능한 설계를 원한다면 객체와 관련된 두 가지 책임을 서로 다른 객체로 분리해야 한다. 하나는 객체를 생성하는 것이고, 다른 하나는 객체를 사용하는 것이다. 한 마디로 말해서 객체에 대한 생성과 사용을 분리(seprating use from creation)해야 한다.

 

경우에 따라 객체 생성과 관련된 책임만 전담하는 별도의 객체를 추가하고 Client는 이 객체를 사용하도록 만들 수 있다. 이처럼 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 FACTORY라고 부른다.

 

의존성 주입 

 

객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고 부른다. 의존성 주입은 의존성을 해결하기 위해 의존성을 객체의 퍼블릭 인터페이스에 명시적으로 드러내서 외부에서 필요한 런타임 의존성을 전달할 수 있도록 만드는 방법을 포괄하는 명칭이다. 

 

의존성 주입은 필요한 의존성을 클래스 퍼블릭 인터페이스에 명시적으로 드래너기 때문에 의존성을 이해하기 위해 코드 내부를 읽을 필요가 없어 객체의 캡슐을 단단하게 보호한다. 가급적 의존성을 객체의 퍼블릭 인터페이스에 노출하라. 의존성을 구현 내부에 숨기면 숨길수록 코드를 이해하기도, 수정하기도 어려워진다.

 

상속과 코드 재사용 

 

상속과 중복 코드 

 

중복코드가 가지는 가장 큰 문제는 코드를 수정하는 데 필요한 노력을 몇 배로 증가시킨다는 것이다. 중복 여부를 판단하는 기준은 변경이다. 요구사항이 변경됐을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복이다. 

 

상속은 객체지향 프로그래밍에서 중복을 제거하기 위한 대표적인 방법이다. 상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야한다. 이것은 자식 클래스의 작성자가 부모 클래스의 구현 방법에 대한 정확한 지식을 가져야 한다는 것을 의미한다. 따라서 상속은 결합도를 높인다. 그리고 부모 클래스와 자식 클래스 사이의 강한 결합이 코드를 수정하기 어렵게 만든다.

 

취약한 기반 클래스 문제 

 

부모클래스의 변경에 의해 자식 클래스가 영향을 받는 현상을 취약한 기반 클래스 문제라고 부른다. 취약한 기반 클래스 문제는 캡슐화를 약화시키고 결합도를 높인다. 상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시킨다. 객체지향의 기반은 캡슐화를 통한 변경의 통제인데 상속은 코드 재사용을 위해 캡슐화의 장점을 희석시키고 구현에 대한 결합도를 높임으로써 객체지향이 가진 강력함을 반감시킨다.

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

오브젝트 리뷰 - 5

기술/아키텍처 2021. 7. 27. 20:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

의존성 관리하기 

 

의존성 이해하기 

 

어떤 객체가 예정된 작업을 정상적으로 수행하기 위해 다른 객체를 필요로 하는 경우 두 객체 사이에 의존성이 존재한다고 말한다. 의존성은 방향성을 가지며 항상 단방향이다 (p254)

 

의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성(direct dependency)간접 의존성(indirect dependency)로 나누기도 한다. 직접 의존성이란 한 요소가 다른 요소에 직접 의존하는 경우를 가리킨다. 간접 의존성이란 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우를 뜻한다. (p257)

 

런타임 의존성은 실행 시점에 객체 간의 의존성을 뜻한다. 컴파일 타임은 코드 상에서 클래스 간의 의존성을 뜻한다. 런타임 의존성과 컴파일타임 의존성이 다를 수 있다. 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 한다. 런타임 의존성과 컴파일 타임 의존성 사이의 거리가 멀면 멀수록 설계가 유연해진다. (p258)

 

클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다. 이를 컨텍스트 독립성이라고 부른다. 설계가 유연해지기 위해서는 가능한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야한다. (p260)

 

유연한 설계 

 

바람직한 의존성은 재사용성과 관련이 있다. 어떤 의존성이 다양한 환경에서 클래스를 재사용할 수 없도록 제한한다면 그 의존성은 바람직하지 못한 것이다. 컨텍스트에 독립된 의존성은 다양한 환경에서 재사용 될 수 있는 가능성을 열어 놓는다(p265, 266)

 

바람직한 의존성을 가질 때 두 객체는 느슨한 결합도(loose coupling) 또는 약한 결합도를(weak coupling) 가진다고 말한다. 반대로 바람직하지 못한 의존성을 가질 때 두 객체는 단단한 결합도(tight coupling) 또는 강한 결합도(strong coupling)을 가진다고 말한다. (p266)

 

결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다. 한 요소가 다른 요소에 대해 더 많은 정보를 알고 있을 수록 두 요소는 강하게 결합된다. 반대로 더 적은 정보를 알고 있을 수록 두 요소는 약하게 결합된다. 더 많이 알수록 더 많이 결합된다. 더 많이 알고 있다는 것은 더 적은 컨텍스트에서 재사용 가능하다는 것을 의미한다. 결합도를 느슨하게 만들기 위해선 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다(p267)

 

추상화란 어떤 양상, 세부사항, 구조를 좀더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다. 구체 클래스에 비해 추상 클래스는 클라이언트가 알아야하는 지식의 양이 더 적기 때문에 구체 클래스보다 추상 클래스에 의존하는 것이 결합도가 더 낮다. 인터페이스에 의존하면 추상 클래스의 상속 계층을 모르더라도 협력이 가능해진다. 인터페이스 의존성은 협력하는 객체가 어떤 메시지를 수신할 수 있는지에 대한 지식만을 남기기 때문에 추상 클래스보다 결합도가 낮다. 의존하는 대상이 추상적일수록 결합도는 더 낮아진다는 것이 핵심이다. (p268)

 

의존성이 퍼블릭 인터페이스에 노출되는 경우 명시적 의존성이라고 한다. 반대로 퍼블릭 인터페이스에 노출되지 않는 경우 숨겨진 의존성이라고 한다. 의존성이 명시적이지 않으면 클래스를 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 할 수 있다 (p270, 271)

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

오브젝트 리뷰 - 4

기술/아키텍처 2021. 7. 25. 15:56 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

메시지와 인터페이스 

 

협력과 메시지

 

메시지는 객체 사이의 협력을 가능하게 하는 매개체다. 객체가 다른 객체에게 접근할 수 있는 유일한 방법은 메시지를 전송하는 것뿐이다. 전통적인 방법은 클라이언트-서버 모델이다. 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라 부르며 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다. (p176)

 

객체가 독립적으로 수행할 수 있는 것보다 더 큰 책임을 수행하기 위해서는 다른 객체와 협력해야 한다. 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지다. (p177)

 

메시지는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다. 한 객체가 다른 객체에게 도움을 요청하는 것을 메시지 전송 또는 메시지 패싱이라고 부른다. 이때 메시지를 전송하는 객체를 메시지 전송자라고 부르고 메시지를 수신하는 객체를 메시지 수신자라고 부른다. 메시지는 오퍼레이션명과 인자로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다. (p177)

 

메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라 부른다. 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다. 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다. 이런 메시지와 메서드의 구분은 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다. (p178)

 

객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다. 프로그래밍 언어 관점에서 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다. 그에 비해 메시지를 수신했을 때 실제로 실행되는 코드는 메서드라고 부른다. (p179)

 

오페레이션의 이름과 파라미터 목록을 합쳐 시그니처라고 부른다. 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이고 메서드는 이 시그니처에 구현을 더한 것이다. (p180)

 

인터페이스와 설계 품질 

 

좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야한다 (p181)

 

디미터 법칙은 객체 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한한다. 오직 하나의 도트만 사용하라는 말로 요약되기도 한다. 디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다. 디미터 법칙을 따르면 부끄럼타는 코드를 작성할 수 있다. 부끄럼타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며 다른 객체의 구현에 의존하지 않는 코드를 의미한다. (p183, 184, 185)

 

디미터 법칙을 만족하지 않은 경우 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메세지를 전송하는 경우가 생기는데 이런 경우를 기차 충돌(train wreck)이라 한다. 기차 충돌은 클래스의 내부 구현이 외부로 노출됐을 때 나타나는 전형적인 형태다. (p186)

 

훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야한다. 묻지 말고 시켜라는 이런 스타일의 메시지 작성을 장려하는 원칙이다. 객체의 정보를 이용하는 행동을 객체의 외부가 아닌 내부에 위치시키기 때문에 자연스럽게 정보와 행동을 동일한 클래스 안에 두게 되고 자연스럽게 정보 전문가에게 책임을 할당하게 되고 높은 응집도를 가진 클래스를 얻게 된다 (p186, 187)

 

메서드 이름을 짓는 두번째 방법은 '어떻게'가 아니라 '무엇'을 하는지를 드러내는 것이다. 무엇을 하는지를 드러내는 이름은 코드를 이해하기 쉽고 유연한 코드를 낳는 지름길이다. 무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야한다. 이처럼 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자라고 부른다. (p188, 189, 190)

 

명령 - 쿼리 분리 원칙 

 

어떤 절차를 묶어 호출하도록 이름을 부여한 기능 모듈을 루틴이라 부른다. 루틴은 프로시저와 함수로 구분 되는데 프로시저는 정해진 절차에 따라 내부 상태를 변경하는 루틴이며 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 종류이다. 명령은 객체의 인터페이스 측면에서 프로시저에 해당하고, 쿼리는 함수에 해당한다. (p202)

 

명령-쿼리 분리 원칙의 요지는 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다는 것이다. 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다. 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안되며 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다. (p203)

 

명령-쿼리 분리 원칙은 부수효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리함으로써 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 해준다 (p213)

 

객체 분해

 

프로시저 추상화와 데이터 추상화 

 

현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화다. 프로시저 추상화는 소프트웨어가 무엇을 해야하는지를 추상화하고 테이터 추상화는 소프트웨어가 무엇을 알아야 하는지를 추상화한다. (p218)

 

프로시저 추상화를 중심으로 시스템을 분해하기로 결정했다면 기능분해(functional decomposition)의 길로 들어서는 것이다. 기능 분해는 알고리즘 분해라고 부르기도 한다. 데이터 추상화를 중심으로 시스템을 분해하기로 결정했다면 데이터를 중심으로 타입을 추상화(type abstraction)하는 방향과 데이터를 중심으로 프로시저를 추상화 하는 방법(procedure abstraction)중 하나를 선택한다 (p218)

 

프로시저 추상화와 기능 분해

 

전통적인 기능 분해 방법은 하향식 접근법을 따른다. 하향식 접근법이란 시스템을 구성하는 가장 최상위 기능을 정의하고 좀 더 작은 단계의 하위 기능으로 분해해가는 방법을 말한다. (p219)

 

하향식 접근법과 기능분해는 근본적으로 변경에 취약한 설계를 갖는다. 메인 함수를 유일한 정상으로 간주하는 하향식 기능 분해의 경우에는 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. (p226, 227)

 

하향식 접근법은 비즈니스 로직을 설계하는 초기 단계부터 입력방법과 출력 양식을 함께 고민하도록 강요한다. 그러나 사용자 인터페이스는 시스템 내에서 가장 많은 변경이 발생하는 부분이므로 수정이 발생하면 상대적으로 변경이 적은 비즈니스 로직까지 영향을 미치게 된다. 근본적으로 변경에 불안정한 아키텍처가 만들어진다. (p229)

 

하향식 접근법의 경우 처음부터 구현을 염두에두기 때문에 자연스럽게 함수들의 실행 순서를 정의하는 시간 제약을 강조한다. 분해를 진행할 수 없어 기능 분해 방식은 중앙집중 제어스타일의 형태를 띨 수 밖에 없다. 이를 해결하기 위해선 시간 제약에 대한 미련을 버리고 좀 더 안정적인 논리적 제약을 설계 기준으로 삼아야 한다. 객체 지향은 삼수 간의 호출 순서가 아니라 객체 사이의 논리적인 관계를 중심으로 설계를 이끌어간다. (p230)

 

모듈 

 

시스템을 모듈 단위로 분해하기 위한 기본 원리로 시스템에서 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵심이다. 모듈 분해는 감춰야 하는 비밀을 선택하고 비밀 주변에 안정적인 보호막을 설치하는 보존의 과정이다. 비밀을 결정하고 모듈을 분해한 후에는 기능 분해를 이용해 모듈에 필요한 퍼블릭 인터페이스를 구현할 수 있다. 모듈은 복잡성과 변경 가능성을 감춰야한다. (p235, 236)

 

모듈은 기능이 아니라 변경의 정도에 따라 시스템을 분해한다. 각 모듈은 외부에 감춰야 하는 비밀과 관련성 높은 데이터와 함수의 집합이다. 따라서 모듈 내부는 높은 응집도를 유지한다. 또한 퍼블릭 인터페이스를 통해서만 통신해야 하므로 낮은 결합도를 유지한다. 모듈에게 있어 핵심은 데이터다. (p239)

 

클래스 

 

명확한 의미에서 클래스는 추상 데이터 타입과는 다르다. 핵심적인 차이는 클래스는 상속과 다형성을 지원하는데 비해 추상 데이터 타입은 지원하지 못한다는 것이다.  타입 추상화를 기반으로 하는 대표적인 기법이 바로 추상 데이터 타입이다. 추상 데이터 타입은 오퍼레이션을 기준으로 여러가지 타입을 나타낼 수 있다. 반면 객체 지향은 타입을 기준으로 오퍼레이션을 묵는다. (p245, 246)

 

실제 내부에서 수행되는 절차는 다르지만 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다. 다시 말해 객체 지향은 절차 추상화다.

 

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

Coroutine + Retrofit | Coroutine + Room

개발/안드로이드 2021. 7. 22. 21:00 Posted by 아는 개발자

Coroutine + Retrofit

 

Retrofit 2.6.0 버전부터 suspend 함수로 api를 작성할 수 있게 됐다. 다른 Retrofit 인터페이스처럼 어노테이션을 추가하고 suspend 함수를 추가하면 빌드 될 때 Retrofit 에서 전처리한다. 

 

interface LibraryApi {
    @GET("/1.0/new")
    suspend fun getNew() : BookListResp
}

 

suspend로 쓰였기 때문에, api 를 호출하는 부분에서도 suspend 함수를 받아서 처리할 수 있다. 예로 Repository 인 경우 suspend 함수를 이용해서 아래 코드로 표현이 가능하다. withContext를 받아서 I/O 쓰레드에서 실행하도록 변경해 Main 쓰레드 안전성이 보장됐다.

 

class LibraryRepository(
    private val apiProvider: ApiProvider
) {

    private val libraryApi by lazy { apiProvider.createApi(LibraryApi::class.java) }

    suspend fun loadNew(): BookListResp = withContext(Dispatchers.IO) {
        return@withContext libraryApi.getNew()
    }

 

Coroutine + Room 

 

Room에서 데이터 변화에 따라 UI를 바꾸거나 특정 로직을 실행해야할 때가 있다. Room 에서는 LiveData와 Flow를 이용하는 두가지 방법을 제공하는데, 이 포스트에서는 Flow를 활용한 버전만 다룬다. 

 

@Dao
interface BookSearchDao {
    @Query("select * from BookSearch order by BookSearch.createdAt desc")
    fun selectBookSearchList(): Flow<List<BookSearch>?>
    
class LibraryRepository(
    private val bookSearchDao: BookSearchDao
) {
    suspend fun loadBookSearchHistory(): Flow<List<BookSearch>?> = withContext(Dispatchers.IO) {
        return@withContext bookSearchDao.selectBookSearchList()
    }

 

Dao의 리턴타입을 Flow로 싸고 Repository 에서는 suspend 함수를 이용해 IO 쓰레드에서 실행하도록 변경하고 리턴했다. Flow는 RxJava의 Flowable과 같아서 내부 DB에 변경사항이 생기면 스트림을 따라서 알림을 준다. 

 

class SearchViewModel @Inject constructor(
    private val libraryRepository: LibraryRepository
): ViewModel() {
    fun loadHistory() {
        viewModelScope.launch {
            libraryRepository.loadBookSearchHistory()
                .distinctUntilChanged()
                .collect { list ->
                    searchHistory.value = list ?: listOf()
                }
        }
    }

 

ViewModel에선 loadBookSearchHistory() 함수의 리턴값인 Flow를 viewModelScope 내에서 subscribe 한다. 변화가 있을 때마다 collect 내부의 바디 코드가 실행된다. viewModelScop 으로 실행했기 때문에 ViewModel이 종료되면 subscribe도 자동으로 종료된다.

728x90

'개발 > 안드로이드' 카테고리의 다른 글

RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04
Serializable 과 Parcelable  (0) 2021.06.19