Android application to search for books with Daum API
-
Product APK 받기
-
실행 영상 YouTube 아래 이미지 클릭
- Kotlin을 이용하여 개발
- 카카오 도서 검색 API를 이용하여 도서 검색
- 스크롤 시 연속 Paging 기능 제공
- 검색 리스트 결과(메인화면) 및 상세화면으로 구성
- 메인 리스트와 상세화면은 Fragment로 구성
- 메인 리스트에서 특정 Item 클릭 시 상세화면으로 이동
-
기본 환경
- OS : Mac OS X
- IDE : Android Studio 4.0.1
- JVM target : Java-1.8
- Kotlin 1.3.72
- Gradle : 4.0.1
-
AndroidX 라이브러리
- Core 1.3.1
- Coroutine 1.3.8
- MultiDex 2.0.1
- Lifecycle 1.3.8
- Room 2.3.0-alpha01
- Paging 3.0.0-alpha05
-
기타 라이브러리
- Dagger 2.27 // Dependency Injection Tool
- Retrofit 2.8.1 // REST API Tool
- OkHttp 4.7.0 // HTTP Client Tool
- Glide 4.11.0 // Image Loading Tool
- Timber 4.7.1 // Logging Tool
- Logger 2.2.0 // Logging Tool
-
이미지 출처
- 앱 아이콘 : 자체 제작
- 기본 아이콘 : MATERIAL DESIGN
- 카카오 캐릭터 : 카카오 프렌즈 월페이퍼
/com/meuus90
│
├── base ---------> # base package
│ ├── arch/util ---------> # architecture util source
│ ├── common/util ---------> # common util source
│ ├── constant ---------> # constant source
│ └── view ---------> # custom view source
│
└── daumbooksearch ---------> # project package
├── di ---------> # dependency injection
├── model
│ ├── data/source
│ │ ├── api ----------> # remote server api
│ │ ├── local ----------> # local room dao
│ │ └── repository ----------> # repository source
│ ├── paging ---------> # paging source
│ └── schema ---------> # schema collection
├── viewmodel ---------> # viewmodel source
├── view ---------> # view source
└── DaumBookSearch.kt ---> # application
-
아키텍쳐 디자인 패턴은 MVVM 패턴을 적용하였다.
-
각 컴포넌트들은 필요시 다른 컴포넌트를 Inject하여 사용하였다. Dependency Injection
-
View는 MainActivity, SplashFragment, BookListFragment, BookDetailFragment로 구성하였다.화면 구성
- View 컴포넌트들은 필요 시 ViewModel에게 트리거를 발생시키고 필요한 데이터를 관찰한다.
-
ViewModel은 SplashViewModel, BooksViewModel로 구성하였다.
-
각 ViewModel은 필요한 비즈니스 로직을 처리하고 데이터를 저장하거나 변경된 내용을 알린다.
-
SplashViewModel은 로컬 캐시 데이터를 초기화하는 기능이 있다.
-
BooksViewModel은 BooksRepository로 View와 Model 간 paging 관련 처리를 중계한다.
-
-
Model은 BooksRepository와 Local DB, Newwork API로 구성하였다.
-
BooksRepository는 Pager를 이용하여 Remote DataSource에서 필요한 데이터를 Room에 저장한다.
-
Config 파라미터에 따라 PagingData를 메모리에 캐싱하여 관찰자에게 알린다.
-
-
-
BookListFragment의 RecyclerView에 페이징 기능을 적용하였다.
-
AndroidX Paging 3.0.0-alpha05 라이브러리를 사용하였다.
-
사전에 Paging 2 버전을 사용하여 구성하였지만 PagedListBoundaryCallback와 Adapter의 비정상 처리 등의 이슈가 발생하여 Paging 3 버전으로 업데이트 하였다.
-
Paging 3 이상 버전에서는 PagedList와 PagedListAdapter가 Deprecated 되었고 PagingData와 PagingDataAdapter가 생겼으며 사용방법에 다소 차이점이 있다.
-
-
Paging 처리 방식은 'Network Storage -> Local Storage -> Repository -> Adapter'로 구성하였다.
-
BooksRepository에서 제공하는 기본 페이징 처리는 Room 로컬 스토리지에서 캐싱처리 하도록 하였다.
-
로컬 스토리지 데이터가 모두 로드 되었고 추가 데이터가 필요할 시 BooksPageKeyedMediator를 이용하여 네트워크에서 추가 데이터를 수집하여 로컬 스토리지에 저장한다.
-
BookListAdapter는 PagingDataAdapter를 상속하고 Diff Callback을 설정하여 아이템이 중복으로 나오는 것을 방지하였다.
-
페이징 처리에 적용한 파라미터는 다음과 같다.
-
const val localPagingSize = 25 // Room에서 페이지당 불러오는 아이템 개수 const val localInitialLoadSize = 40 // PagingData를 초기화할 때 Room에서 불러오는 초기 아이템 개수 const val localPrefetchDistance = 25 // PagingDataAdapter에서 스크롤 시 // 아이템을 미리 불러오기 위해 메모리상 남은 개수 const val remotePagingSize = 50 // Network에 요청할 페이지 당 아이템 개수 // (PagingConfig에 적용하지 않고 BooksPageKeyedMediator로 넘긴다. // BooksPageKeyedMediator에서 API Request 파라미터로 전송한다.)
-
- Fragment를 각각 모듈화 하였고, Activity도 각각 모듈화하여 사용할 Fragment들을 서브모듈로 등록하였다. MainActivityModule
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector(
modules = [
SplashFragmentModule::class,
BookListFragmentModule::class,
BookDetailFragmentModule::class
]
)
internal abstract fun contributeMainActivity(): MainActivity
}
- AppModule에서는 Application Context, 네트워크 API, Room Database 등을 모듈화하였다. AppModule
@Provides
@Singleton
fun appContext(application: Application): Context {
return application
}
@Provides
@Singleton
fun provideOkHttpClient(interceptor: Interceptor): OkHttpClient {
val ok = OkHttpClient.Builder()
.connectTimeout(timeout_connect, TimeUnit.SECONDS)
.readTimeout(timeout_read, TimeUnit.SECONDS)
.writeTimeout(timeout_write, TimeUnit.SECONDS)
.
.
.
ok.addInterceptor(interceptor)
return ok.build()
}
- 생성된 컴포넌트 모듈들은 AppComponent로 등록하여 AppInjector를 통해 Application에 주입하였다. AppComponent
@Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
MainActivityModule::class
]
)
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance app: Application): AppComponent
}
fun inject(app: DaumBookSearch)
}
-
Github Actions Workflow를 이용해 테스트 자동화를 등록하였다. Github Actions
-
주요 기능
-
develop branch에서 commit push 완료시 실행
-
JDK i.8 테스트 환경 셋업
-
Kotlin linter 체크
-
Android linter 체크
-
Test code Unit test 실시
-
-
로컬 캐시 데이터를 초기화한다.
-
500ms 이후 메인 검색 화면으로 이동한다.
-
디바운싱 검색 기능을 제공한다. 입력창에 텍스트를 입력하고 500ms가 지나면 최종 입력된 문자열로 조회한다.
-
리스트를 아래로 스크롤 시 정해진 페이징 처리방식에 따라 아이템을 자동으로 추가한다.
-
아이템이 추가되면 RecyclerView의 Adapter에 submit 한다.
-
appbar_scrolling_view_behavior를 적용하였다.
-
스피너를 이용하여 정렬방식이나 검색 필드를 제한하는 기능을 제공한다.
-
좌측 상단 아이콘을 클릭하면 리스트 최상단으로 이동한다.
-
아이템을 클릭하면 상세 화면으로 이동한다. Back 버튼을 클릭하면 앱을 종료한다.
-
상세 화면 이동 시 이미지 transition animation 애니메이션을 적용하였다.
-
API 검색으로 받을 수 있는 item 내용을 표현한다.
-
하단 버튼 클릭 시 브라우저를 열어 링크 페이지로 보낸다.
-
Back 버튼을 클릭하거나 좌측 상단 버튼 클릭 시 메인 검색 화면으로 이동한다.
- 프로젝트 세팅
- 스키마 디자인
- Model 세팅
- Repository 세팅
- Room 세팅
- Paging Data 세팅
- ViewModel 세팅
- Unit Test 테스트코드 작성
- UI 디자인
- API 에러 타입 별 대응
- 애니메이션 등 UX 설정
- 디바이스 퍼포먼스 체크
- UI 테스트 및 기타 버그 픽스
- Release
Completely free (MIT)! See LICENSE.md for more.