├── cache
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── cache
│ │ │ ├── model
│ │ │ └── CachedBufferoo.kt
│ │ │ ├── db
│ │ │ ├── constants
│ │ │ │ └── BufferooConstants.kt
│ │ │ ├── mapper
│ │ │ │ ├── ModelDbMapper.kt
│ │ │ │ └── BufferooMapper.kt
│ │ │ ├── Db.kt
│ │ │ └── DbOpenHelper.kt
│ │ │ ├── mapper
│ │ │ ├── EntityMapper.kt
│ │ │ └── BufferooEntityMapper.kt
│ │ │ ├── PreferencesHelper.kt
│ │ │ └── BufferooCacheImpl.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── cache
│ │ ├── test
│ │ ├── DefaultConfig.kt
│ │ └── factory
│ │ │ ├── DataFactory.kt
│ │ │ └── BufferooFactory.kt
│ │ ├── mapper
│ │ └── BufferooEntityMapperTest.kt
│ │ ├── db
│ │ └── mapper
│ │ │ └── BufferooMapperTest.kt
│ │ └── BufferooCacheImplTest.kt
└── build.gradle
├── data
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── data
│ │ │ ├── model
│ │ │ └── BufferooEntity.kt
│ │ │ ├── mapper
│ │ │ ├── Mapper.kt
│ │ │ └── BufferooMapper.kt
│ │ │ ├── repository
│ │ │ ├── BufferooRemote.kt
│ │ │ ├── BufferooDataStore.kt
│ │ │ └── BufferooCache.kt
│ │ │ ├── source
│ │ │ ├── BufferooRemoteDataStore.kt
│ │ │ ├── BufferooDataStoreFactory.kt
│ │ │ └── BufferooCacheDataStore.kt
│ │ │ ├── executor
│ │ │ └── JobExecutor.kt
│ │ │ └── BufferooDataRepository.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── data
│ │ ├── test
│ │ └── factory
│ │ │ ├── DataFactory.kt
│ │ │ └── BufferooFactory.kt
│ │ ├── mapper
│ │ └── BufferooMapperTest.kt
│ │ ├── source
│ │ ├── BufferooRemoteDataStoreTest.kt
│ │ ├── BufferooCacheDataStoreTest.kt
│ │ └── BufferooDataStoreFactoryTest.kt
│ │ └── BufferooDataRepositoryTest.kt
└── build.gradle
├── domain
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── domain
│ │ │ ├── model
│ │ │ └── Bufferoo.kt
│ │ │ ├── executor
│ │ │ ├── ThreadExecutor.kt
│ │ │ └── PostExecutionThread.kt
│ │ │ ├── interactor
│ │ │ ├── BaseSingleObserver.kt
│ │ │ ├── browse
│ │ │ │ └── GetBufferoos.kt
│ │ │ ├── CompletableUseCase.kt
│ │ │ └── SingleUseCase.kt
│ │ │ └── repository
│ │ │ └── BufferooRepository.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── domain
│ │ ├── test
│ │ └── factory
│ │ │ ├── DataFactory.kt
│ │ │ └── BufferooFactory.kt
│ │ └── usecase
│ │ └── bufferoo
│ │ └── GetBufferoosTest.kt
└── build.gradle
├── remote
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── remote
│ │ │ ├── model
│ │ │ └── BufferooModel.kt
│ │ │ ├── mapper
│ │ │ ├── EntityMapper.kt
│ │ │ └── BufferooEntityMapper.kt
│ │ │ ├── BufferooService.kt
│ │ │ ├── BufferooRemoteImpl.kt
│ │ │ └── BufferooServiceFactory.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── remote
│ │ ├── test
│ │ └── factory
│ │ │ ├── DataFactory.kt
│ │ │ └── BufferooFactory.kt
│ │ ├── mapper
│ │ └── BufferooEntityMapperTest.kt
│ │ └── BufferooRemoteImplTest.kt
└── build.gradle
├── presentation
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── presentation
│ │ │ ├── model
│ │ │ └── BufferooView.kt
│ │ │ ├── BasePresenter.kt
│ │ │ ├── BaseView.kt
│ │ │ ├── mapper
│ │ │ ├── Mapper.kt
│ │ │ └── BufferooMapper.kt
│ │ │ └── browse
│ │ │ ├── BrowseBufferoosContract.kt
│ │ │ └── BrowseBufferoosPresenter.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── presentation
│ │ ├── test
│ │ └── factory
│ │ │ ├── DataFactory.kt
│ │ │ └── BufferooFactory.kt
│ │ └── browse
│ │ └── BrowseBufferoosPresenterTest.kt
└── build.gradle
├── art
├── ui.png
├── data.png
├── architecture.png
└── device_screenshot.png
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── mobile-ui
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── dimens.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_browse.xml
│ │ │ │ └── item_bufferoo.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── org
│ │ │ │ └── buffer
│ │ │ │ └── android
│ │ │ │ └── boilerplate
│ │ │ │ └── ui
│ │ │ │ ├── injection
│ │ │ │ ├── scopes
│ │ │ │ │ ├── PerActivity.kt
│ │ │ │ │ └── PerApplication.kt
│ │ │ │ ├── component
│ │ │ │ │ └── BrowseActivitySubComponent.kt
│ │ │ │ ├── module
│ │ │ │ │ ├── ActivityBindingModule.kt
│ │ │ │ │ ├── BrowseActivityModule.kt
│ │ │ │ │ └── ApplicationModule.kt
│ │ │ │ └── ApplicationComponent.kt
│ │ │ │ ├── model
│ │ │ │ └── BufferooViewModel.kt
│ │ │ │ ├── mapper
│ │ │ │ ├── Mapper.kt
│ │ │ │ └── BufferooMapper.kt
│ │ │ │ ├── UiThread.kt
│ │ │ │ ├── BufferooApplication.kt
│ │ │ │ └── browse
│ │ │ │ ├── BrowseAdapter.kt
│ │ │ │ └── BrowseActivity.kt
│ │ └── AndroidManifest.xml
│ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── ui
│ │ │ ├── test
│ │ │ ├── factory
│ │ │ │ ├── BufferooFactory.kt
│ │ │ │ └── DataFactory.kt
│ │ │ ├── TestRunner.kt
│ │ │ ├── TestApplication.kt
│ │ │ └── util
│ │ │ │ └── RecyclerViewMatcher.kt
│ │ │ ├── injection
│ │ │ ├── component
│ │ │ │ └── TestApplicationComponent.kt
│ │ │ └── module
│ │ │ │ └── TestApplicationModule.kt
│ │ │ └── browse
│ │ │ └── BrowseActivityTest.kt
│ └── test
│ │ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── ui
│ │ ├── test
│ │ └── factory
│ │ │ ├── BufferooFactory.kt
│ │ │ └── DataFactory.kt
│ │ └── BufferooMapperTest.kt
├── .gitignore
└── build.gradle
├── .gitignore
├── licence.txt
├── .travis.yml
├── gradlew.bat
├── gradlew
├── dependencies.gradle
└── readme.md
/cache/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/remote/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/art/ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/art/ui.png
--------------------------------------------------------------------------------
/art/data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/art/data.png
--------------------------------------------------------------------------------
/art/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/art/architecture.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':cache', ':remote', ':data', ':domain', ':presentation', ':mobile-ui'
2 |
--------------------------------------------------------------------------------
/art/device_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/art/device_screenshot.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/cache/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Clean Arch Boilerplate
3 |
4 |
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/android-play/master/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/test/DefaultConfig.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.test
2 |
3 | object DefaultConfig {
4 | //The api level that RoboElectric will use to run the unit tests
5 | const val EMULATE_SDK = 21
6 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/scopes/PerActivity.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.scopes
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class PerActivity
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/scopes/PerApplication.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.scopes
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class PerApplication
--------------------------------------------------------------------------------
/mobile-ui/.gitignore:
--------------------------------------------------------------------------------
1 | *.apk
2 | *.ap_
3 | *.dex
4 | *.class
5 | bin/
6 | gen/
7 | out/
8 | build/
9 | workspace.xml
10 | .idea
11 | local.properties
12 | ks.properties
13 | .classpath
14 | .project
15 | .DS_Store
16 | lint.xml
17 | protected_strings.xml
18 | .gradle
19 | /dist
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/model/CachedBufferoo.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.model
2 |
3 | /**
4 | * Model used solely for the caching of a bufferroo
5 | */
6 | data class CachedBufferoo(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 11 10:04:50 IST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/model/BufferooModel.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.model
2 |
3 | /**
4 | * Representation for a [BufferooModel] fetched from the API
5 | */
6 | class BufferooModel(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/model/Bufferoo.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.model
2 |
3 | /**
4 | * Representation for a [Bufferoo] fetched from an external layer data source
5 | */
6 | data class Bufferoo(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/model/BufferooEntity.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.model
2 |
3 | /**
4 | * Representation for a [BufferooEntity] fetched from an external layer data source
5 | */
6 | data class BufferooEntity(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/model/BufferooViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.model
2 |
3 | /**
4 | * Representation for a [BufferooViewModel] fetched from an external layer data source
5 | */
6 | class BufferooViewModel(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/model/BufferooView.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.model
2 |
3 | /**
4 | * Representation for a [BufferooView] instance for this layers Model representation
5 | */
6 | class BufferooView(val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | .DS_Store
5 | /build
6 | .idea/
7 | *iml
8 | *.iml
9 | */build
10 | gradle.properties
11 | *.apk
12 | *.ap_
13 | *.dex
14 | *.class
15 | bin/
16 | gen/
17 | out/
18 | build/
19 | workspace.xml
20 | .idea
21 | ks.properties
22 | .classpath
23 | .project
24 | lint.xml
25 | /dist
26 | *.iml
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/executor/ThreadExecutor.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.executor
2 |
3 | import java.util.concurrent.Executor
4 |
5 | /**
6 | * Executor implementation can be based on different frameworks or techniques of asynchronous
7 | * execution, but every implementation will execute the
8 | */
9 | interface ThreadExecutor : Executor
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/BasePresenter.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation
2 |
3 | /**
4 | * Interface class to act as a base for any class that is to take the role of the IPresenter in the
5 | * Model-BaseView-IPresenter pattern.
6 | */
7 | interface BasePresenter {
8 |
9 | fun start()
10 |
11 | fun stop()
12 |
13 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/BaseView.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation
2 |
3 | /**
4 | * Interface class to act as a base for any class that is to take the role of the BaseView in the Model-
5 | * BaseView-Presenter pattern.
6 | */
7 | interface BaseView {
8 |
9 | fun setPresenter(presenter: T)
10 |
11 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/constants/BufferooConstants.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db.constants
2 |
3 | import org.buffer.android.boilerplate.cache.db.Db
4 |
5 | /**
6 | * Defines DB queries for the Bufferoos Table
7 | */
8 | object BufferooConstants {
9 |
10 | internal val QUERY_GET_ALL_BUFFEROOS = "SELECT * FROM " + Db.BufferooTable.TABLE_NAME
11 |
12 | }
--------------------------------------------------------------------------------
/domain/src/test/java/org/buffer/android/boilerplate/domain/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.test.factory
2 |
3 | /**
4 | * Factory class for data instances
5 | */
6 | class DataFactory {
7 |
8 | companion object Factory {
9 |
10 | fun randomUuid(): String {
11 | return java.util.UUID.randomUUID().toString()
12 | }
13 |
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/org/buffer/android/boilerplate/presentation/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.test.factory
2 |
3 | /**
4 | * Factory class for data instances
5 | */
6 | class DataFactory {
7 |
8 | companion object Factory {
9 |
10 | fun randomUuid(): String {
11 | return java.util.UUID.randomUUID().toString()
12 | }
13 |
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.mapper
2 |
3 | /**
4 | * Interface for model mappers. It provides helper methods that facilitate
5 | * retrieving of models from outer layers
6 | *
7 | * @param the view input type
8 | * @param the view model output type
9 | */
10 | interface Mapper {
11 |
12 | fun mapToViewModel(type: D): V
13 |
14 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.mapper
2 |
3 | /**
4 | * Interface for model mappers. It provides helper methods that facilitate
5 | * retrieving of models from outer layers
6 | *
7 | * @param the view model input type
8 | * @param the domain model output type
9 | */
10 | interface Mapper {
11 |
12 | fun mapToView(type: D): V
13 |
14 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/mapper/EntityMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.mapper
2 |
3 | /**
4 | * Interface for model mappers. It provides helper methods that facilitate
5 | * retrieving of models from outer data source layers
6 | *
7 | * @param the remote model input type
8 | * @param the entity model output type
9 | */
10 | interface EntityMapper {
11 |
12 | fun mapFromRemote(type: M): E
13 |
14 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 32sp
5 | 24sp
6 | 20sp
7 | 18sp
8 | 16sp
9 | 14sp
10 | 12sp
11 | 10sp
12 |
13 |
14 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/component/BrowseActivitySubComponent.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.component
2 |
3 | import dagger.Subcomponent
4 | import dagger.android.AndroidInjector
5 | import org.buffer.android.boilerplate.ui.browse.BrowseActivity
6 |
7 | @Subcomponent
8 | interface BrowseActivitySubComponent : AndroidInjector {
9 |
10 | @Subcomponent.Builder
11 | abstract class Builder : AndroidInjector.Builder()
12 |
13 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.mapper
2 |
3 | /**
4 | * Interface for model mappers. It provides helper methods that facilitate
5 | * retrieving of models from outer data source layers
6 | *
7 | * @param the cached model input type
8 | * @param the remote model input type
9 | * @param the model return type
10 | */
11 | interface Mapper {
12 |
13 | fun mapFromEntity(type: E): D
14 |
15 | fun mapToEntity(type: D): E
16 |
17 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/BaseSingleObserver.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor
2 |
3 | import io.reactivex.SingleObserver
4 | import io.reactivex.disposables.Disposable
5 |
6 |
7 | /**
8 | * Default [SingleObserver] base class to define
9 | */
10 | open class BaseSingleObserver : SingleObserver {
11 |
12 | override fun onSubscribe(d: Disposable) { }
13 |
14 | override fun onSuccess(t: T) { }
15 |
16 | override fun onError(exception: Throwable) { }
17 |
18 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/executor/PostExecutionThread.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.executor
2 |
3 | import io.reactivex.Scheduler
4 |
5 | /**
6 | * Thread abstraction created to change the execution context from any thread to any other thread.
7 | * Useful to encapsulate a UI Thread for example, since some job will be done in background, an
8 | * implementation of this interface will change context and update the UI.
9 | */
10 | interface PostExecutionThread {
11 | val scheduler: Scheduler
12 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/mapper/EntityMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.mapper
2 |
3 | /**
4 | * Interface for model mappers. It provides helper methods that facilitate
5 | * retrieving of models from outer data source layers
6 | *
7 | * @param the cached model input type
8 | * @param the remote model input type
9 | * @param the model return type
10 | */
11 | interface EntityMapper {
12 |
13 | fun mapFromCached(type: T): V
14 |
15 | fun mapToCached(type: V): T
16 |
17 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooService.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import io.reactivex.Single
4 | import org.buffer.android.boilerplate.remote.model.BufferooModel
5 | import retrofit2.http.GET
6 |
7 | /**
8 | * Defines the abstract methods used for interacting with the Bufferoo API
9 | */
10 | interface BufferooService {
11 |
12 | @GET("team.json")
13 | fun getBufferoos(): Single
14 |
15 | class BufferooResponse {
16 | lateinit var team: List
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooRemote.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.repository
2 |
3 | import io.reactivex.Single
4 | import org.buffer.android.boilerplate.data.model.BufferooEntity
5 |
6 | /**
7 | * Interface defining methods for the caching of Bufferroos. This is to be implemented by the
8 | * cache layer, using this interface as a way of communicating.
9 | */
10 | interface BufferooRemote {
11 |
12 | /**
13 | * Retrieve a list of Bufferoos, from the cache
14 | */
15 | fun getBufferoos(): Single>
16 |
17 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/ActivityBindingModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Module
4 | import dagger.android.ContributesAndroidInjector
5 | import org.buffer.android.boilerplate.ui.browse.BrowseActivity
6 | import org.buffer.android.boilerplate.ui.injection.scopes.PerActivity
7 |
8 | @Module
9 | abstract class ActivityBindingModule {
10 |
11 | @PerActivity
12 | @ContributesAndroidInjector(modules = arrayOf(BrowseActivityModule::class))
13 | abstract fun bindMainActivity(): BrowseActivity
14 |
15 | }
--------------------------------------------------------------------------------
/mobile-ui/src/test/java/org/buffer/android/boilerplate/ui/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.factory
2 |
3 | import org.buffer.android.boilerplate.presentation.model.BufferooView
4 | import org.buffer.android.boilerplate.ui.test.factory.DataFactory.Factory.randomUuid
5 |
6 | /**
7 | * Factory class for Bufferoo related instances
8 | */
9 | class BufferooFactory {
10 |
11 | companion object Factory {
12 |
13 | fun makeBufferooView(): BufferooView {
14 | return BufferooView(randomUuid(), randomUuid(), randomUuid())
15 | }
16 |
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/UiThread.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui
2 |
3 | import io.reactivex.Scheduler
4 | import io.reactivex.android.schedulers.AndroidSchedulers
5 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
6 | import javax.inject.Inject
7 |
8 | /**
9 | * MainThread (UI Thread) implementation based on a [Scheduler]
10 | * which will execute actions on the Android UI thread
11 | */
12 | class UiThread @Inject internal constructor() : PostExecutionThread {
13 |
14 | override val scheduler: Scheduler
15 | get() = AndroidSchedulers.mainThread()
16 |
17 | }
--------------------------------------------------------------------------------
/data/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.7
4 | targetCompatibility = 1.7
5 |
6 | dependencies {
7 | def dataDependencies = rootProject.ext.dataDependencies
8 | def dataTestDependencies = rootProject.ext.dataTestDependencies
9 |
10 | compile project(':domain')
11 |
12 | compile dataDependencies.javaxAnnotation
13 |
14 | implementation dataDependencies.kotlin
15 | implementation dataDependencies.javaxInject
16 | implementation dataDependencies.rxKotlin
17 |
18 | testImplementation dataTestDependencies.junit
19 | testImplementation dataTestDependencies.kotlinJUnit
20 | testImplementation dataTestDependencies.mockito
21 | testImplementation dataTestDependencies.assertj
22 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooDataStore.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.repository
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 |
7 | /**
8 | * Interface defining methods for the data operations related to Bufferroos.
9 | * This is to be implemented by external data source layers, setting the requirements for the
10 | * operations that need to be implemented
11 | */
12 | interface BufferooDataStore {
13 |
14 | fun clearBufferoos(): Completable
15 |
16 | fun saveBufferoos(bufferoos: List): Completable
17 |
18 | fun getBufferoos(): Single>
19 |
20 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/repository/BufferooRepository.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.repository
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.domain.model.Bufferoo
6 |
7 | /**
8 | * Interface defining methods for how the data layer can pass data to and from the Domain layer.
9 | * This is to be implemented by the data layer, setting the requirements for the
10 | * operations that need to be implemented
11 | */
12 | interface BufferooRepository {
13 |
14 | fun clearBufferoos(): Completable
15 |
16 | fun saveBufferoos(bufferoos: List): Completable
17 |
18 | fun getBufferoos(): Single>
19 |
20 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/mapper/ModelDbMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db.mapper
2 |
3 | import android.content.ContentValues
4 | import android.database.Cursor
5 |
6 | /**
7 | * Interface for database model mappers. It provides helper methods that facilitate
8 | * saving/retrieving java models into/from a relational database.
9 | *
10 | * @param the model type
11 | */
12 | interface ModelDbMapper {
13 |
14 | /**
15 | * Converts the model into ContentValues for the database table
16 | */
17 | fun toContentValues(model: T): ContentValues
18 |
19 | /**
20 | * Parses the Cursor resulting from a database query into the java model
21 | */
22 | fun parseCursor(cursor: Cursor): T
23 |
24 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/mapper/BufferooMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.mapper
2 |
3 | import org.buffer.android.boilerplate.domain.model.Bufferoo
4 | import org.buffer.android.boilerplate.presentation.model.BufferooView
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Map a [BufferooView] to and from a [Bufferoo] instance when data is moving between
9 | * this layer and the Domain layer
10 | */
11 | open class BufferooMapper @Inject constructor(): Mapper {
12 |
13 | /**
14 | * Map a [Bufferoo] instance to a [BufferooView] instance
15 | */
16 | override fun mapToView(type: Bufferoo): BufferooView {
17 | return BufferooView(type.name, type.title, type.avatar)
18 | }
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.7
4 | targetCompatibility = 1.7
5 |
6 | configurations.all {
7 | resolutionStrategy {
8 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
9 | }
10 | }
11 |
12 | dependencies {
13 | def domainDependencies = rootProject.ext.domainDependencies
14 | def domainTestDependencies = rootProject.ext.domainTestDependencies
15 |
16 | implementation domainDependencies.javaxAnnotation
17 | implementation domainDependencies.javaxInject
18 | implementation domainDependencies.rxKotlin
19 | implementation domainDependencies.kotlin
20 |
21 | testImplementation domainTestDependencies.junit
22 | testImplementation domainTestDependencies.mockito
23 | testImplementation domainTestDependencies.assertj
24 |
25 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/mapper/BufferooMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.mapper
2 |
3 | import org.buffer.android.boilerplate.presentation.model.BufferooView
4 | import org.buffer.android.boilerplate.ui.model.BufferooViewModel
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Map a [BufferooView] to and from a [BufferooViewModel] instance when data is moving between
9 | * this layer and the Domain layer
10 | */
11 | open class BufferooMapper @Inject constructor(): Mapper {
12 |
13 | /**
14 | * Map a [BufferooView] instance to a [BufferooViewModel] instance
15 | */
16 | override fun mapToViewModel(type: BufferooView): BufferooViewModel {
17 | return BufferooViewModel(type.name, type.title, type.avatar)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/mapper/BufferooEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.mapper
2 |
3 | import org.buffer.android.boilerplate.data.model.BufferooEntity
4 | import org.buffer.android.boilerplate.remote.model.BufferooModel
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Map a [BufferooModel] to and from a [BufferooEntity] instance when data is moving between
9 | * this later and the Data layer
10 | */
11 | open class BufferooEntityMapper @Inject constructor(): EntityMapper {
12 |
13 | /**
14 | * Map an instance of a [BufferooModel] to a [BufferooEntity] model
15 | */
16 | override fun mapFromRemote(type: BufferooModel): BufferooEntity {
17 | return BufferooEntity(type.name, type.title, type.avatar)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.factory.ui
2 |
3 | import org.buffer.android.boilerplate.domain.model.Bufferoo
4 |
5 | /**
6 | * Factory class for Bufferoo related instances
7 | */
8 | class BufferooFactory {
9 |
10 | companion object Factory {
11 |
12 | fun makeBufferooList(count: Int): List {
13 | val bufferoos = mutableListOf()
14 | repeat(count) {
15 | bufferoos.add(makeBufferooModel())
16 | }
17 | return bufferoos
18 | }
19 |
20 | fun makeBufferooModel(): Bufferoo {
21 | return Bufferoo(DataFactory.randomUuid(), DataFactory.randomUuid(),
22 | DataFactory.randomUuid())
23 | }
24 |
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/domain/src/test/java/org/buffer/android/boilerplate/domain/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.test.factory
2 |
3 | import org.buffer.android.boilerplate.domain.model.Bufferoo
4 | import org.buffer.android.boilerplate.domain.test.factory.DataFactory.Factory.randomUuid
5 |
6 | /**
7 | * Factory class for Bufferoo related instances
8 | */
9 | class BufferooFactory {
10 |
11 | companion object Factory {
12 |
13 | fun makeBufferooList(count: Int): List {
14 | val bufferoos = mutableListOf()
15 | repeat(count) {
16 | bufferoos.add(makeBufferoo())
17 | }
18 | return bufferoos
19 | }
20 |
21 | fun makeBufferoo(): Bufferoo {
22 | return Bufferoo(randomUuid(), randomUuid(), randomUuid())
23 | }
24 |
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/presentation/src/test/java/org/buffer/android/boilerplate/presentation/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.test.factory
2 |
3 | import org.buffer.android.boilerplate.domain.model.Bufferoo
4 | import org.buffer.android.boilerplate.presentation.test.factory.DataFactory.Factory.randomUuid
5 |
6 | /**
7 | * Factory class for Bufferoo related instances
8 | */
9 | class BufferooFactory {
10 |
11 | companion object Factory {
12 |
13 | fun makeBufferooList(count: Int): List {
14 | val bufferoos = mutableListOf()
15 | repeat(count) {
16 | bufferoos.add(makeBufferooModel())
17 | }
18 | return bufferoos
19 | }
20 |
21 | fun makeBufferooModel(): Bufferoo {
22 | return Bufferoo(randomUuid(), randomUuid(), randomUuid())
23 | }
24 |
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/TestRunner.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.support.test.runner.AndroidJUnitRunner
7 | import io.reactivex.plugins.RxJavaPlugins
8 | import io.reactivex.schedulers.Schedulers
9 |
10 | class TestRunner : AndroidJUnitRunner() {
11 |
12 | override fun onCreate(arguments: Bundle) {
13 | super.onCreate(arguments)
14 | RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
15 | }
16 |
17 | @Throws(InstantiationException::class, IllegalAccessException::class, ClassNotFoundException::class)
18 | override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
19 | return super.newApplication(cl, TestApplication::class.java.name, context)
20 | }
21 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/Db.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db
2 |
3 | /**
4 | * This class defines the tables found within the application Database. All table
5 | * definitions should contain column names and a sequence for creating the table.
6 | */
7 | object Db {
8 |
9 | object BufferooTable {
10 | const val TABLE_NAME = "bufferroos"
11 |
12 | const val BUFFEROO_ID = "bufferoo_id"
13 | const val NAME = "name"
14 | const val TITLE = "title"
15 | const val AVATAR = "avatar"
16 |
17 | const val CREATE =
18 | "CREATE TABLE " + TABLE_NAME + " (" +
19 | BUFFEROO_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
20 | NAME + " TEXT NOT NULL," +
21 | TITLE + " TEXT," +
22 | AVATAR + " TEXT" +
23 | "); "
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.7
4 | targetCompatibility = 1.7
5 |
6 | configurations.all {
7 | resolutionStrategy {
8 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
9 | }
10 | }
11 |
12 | dependencies {
13 | def presentationDependencies = rootProject.ext.cacheDependencies
14 | def presentationTestDependencies = rootProject.ext.cacheTestDependencies
15 |
16 | compile presentationDependencies.javaxAnnotation
17 |
18 | implementation presentationDependencies.kotlin
19 | implementation presentationDependencies.javaxInject
20 | implementation presentationDependencies.rxKotlin
21 |
22 | testImplementation presentationTestDependencies.junit
23 | testImplementation presentationTestDependencies.mockito
24 | testImplementation presentationTestDependencies.assertj
25 | testImplementation presentationTestDependencies.robolectric
26 |
27 | compile project(':domain')
28 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/mapper/BufferooMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.mapper
2 |
3 | import org.buffer.android.boilerplate.data.model.BufferooEntity
4 | import org.buffer.android.boilerplate.domain.model.Bufferoo
5 | import javax.inject.Inject
6 |
7 |
8 | /**
9 | * Map a [BufferooEntity] to and from a [Bufferoo] instance when data is moving between
10 | * this later and the Domain layer
11 | */
12 | open class BufferooMapper @Inject constructor(): Mapper {
13 |
14 | /**
15 | * Map a [BufferooEntity] instance to a [Bufferoo] instance
16 | */
17 | override fun mapFromEntity(type: BufferooEntity): Bufferoo {
18 | return Bufferoo(type.name, type.title, type.avatar)
19 | }
20 |
21 | /**
22 | * Map a [Bufferoo] instance to a [BufferooEntity] instance
23 | */
24 | override fun mapToEntity(type: Bufferoo): BufferooEntity {
25 | return BufferooEntity(type.name, type.title, type.avatar)
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosContract.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.browse
2 |
3 | import org.buffer.android.boilerplate.presentation.BasePresenter
4 | import org.buffer.android.boilerplate.presentation.BaseView
5 | import org.buffer.android.boilerplate.presentation.model.BufferooView
6 |
7 | /**
8 | * Defines a contract of operations between the Browse Presenter and Browse View
9 | */
10 | interface BrowseBufferoosContract {
11 |
12 | interface View : BaseView {
13 |
14 | fun showProgress()
15 |
16 | fun hideProgress()
17 |
18 | fun showBufferoos(bufferoos: List)
19 |
20 | fun hideBufferoos()
21 |
22 | fun showErrorState()
23 |
24 | fun hideErrorState()
25 |
26 | fun showEmptyState()
27 |
28 | fun hideEmptyState()
29 |
30 | }
31 |
32 | interface Presenter : BasePresenter {
33 |
34 | fun retrieveBufferoos()
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.test.factory
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | class DataFactory {
9 |
10 | companion object Factory {
11 |
12 | fun randomUuid(): String {
13 | return java.util.UUID.randomUUID().toString()
14 | }
15 |
16 | fun randomInt(): Int {
17 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
18 | }
19 |
20 | fun randomLong(): Long {
21 | return randomInt().toLong()
22 | }
23 |
24 | fun randomBoolean(): Boolean {
25 | return Math.random() < 0.5
26 | }
27 |
28 | fun makeStringList(count: Int): List {
29 | val items: MutableList = mutableListOf()
30 | repeat(count) {
31 | items.add(randomUuid())
32 | }
33 | return items
34 | }
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.test.factory
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | class DataFactory {
9 |
10 | companion object Factory {
11 |
12 | fun randomUuid(): String {
13 | return java.util.UUID.randomUUID().toString()
14 | }
15 |
16 | fun randomInt(): Int {
17 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
18 | }
19 |
20 | fun randomLong(): Long {
21 | return randomInt().toLong()
22 | }
23 |
24 | fun randomBoolean(): Boolean {
25 | return Math.random() < 0.5
26 | }
27 |
28 | fun makeStringList(count: Int): List {
29 | val items: MutableList = mutableListOf()
30 | repeat(count) {
31 | items.add(randomUuid())
32 | }
33 | return items
34 | }
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection
2 |
3 | import android.app.Application
4 | import dagger.BindsInstance
5 | import dagger.Component
6 | import dagger.android.support.AndroidSupportInjectionModule
7 | import org.buffer.android.boilerplate.ui.BufferooApplication
8 | import org.buffer.android.boilerplate.ui.injection.module.ActivityBindingModule
9 | import org.buffer.android.boilerplate.ui.injection.module.ApplicationModule
10 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
11 |
12 | @PerApplication
13 | @Component(modules = arrayOf(ActivityBindingModule::class, ApplicationModule::class,
14 | AndroidSupportInjectionModule::class))
15 | interface ApplicationComponent {
16 |
17 | @Component.Builder
18 | interface Builder {
19 | @BindsInstance fun application(application: Application): Builder
20 | fun build(): ApplicationComponent
21 | }
22 |
23 | fun inject(app: BufferooApplication)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/mobile-ui/src/test/java/org/buffer/android/boilerplate/ui/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.factory
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | class DataFactory {
9 |
10 | companion object Factory {
11 |
12 | fun randomUuid(): String {
13 | return java.util.UUID.randomUUID().toString()
14 | }
15 |
16 | fun randomInt(): Int {
17 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
18 | }
19 |
20 | fun randomLong(): Long {
21 | return randomInt().toLong()
22 | }
23 |
24 | fun randomBoolean(): Boolean {
25 | return Math.random() < 0.5
26 | }
27 |
28 | fun makeStringList(count: Int): List {
29 | val items: MutableList = mutableListOf()
30 | repeat(count) {
31 | items.add(randomUuid())
32 | }
33 | return items
34 | }
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/remote/src/test/java/org/buffer/android/boilerplate/remote/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.test.factory
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | class DataFactory {
9 |
10 | companion object Factory {
11 |
12 | fun randomUuid(): String {
13 | return java.util.UUID.randomUUID().toString()
14 | }
15 |
16 | fun randomInt(): Int {
17 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
18 | }
19 |
20 | fun randomLong(): Long {
21 | return randomInt().toLong()
22 | }
23 |
24 | fun randomBoolean(): Boolean {
25 | return Math.random() < 0.5
26 | }
27 |
28 | fun makeStringList(count: Int): List {
29 | val items: MutableList = mutableListOf()
30 | repeat(count) {
31 | items.add(randomUuid())
32 | }
33 | return items
34 | }
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.factory.ui
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | class DataFactory {
9 |
10 | companion object Factory {
11 |
12 | fun randomUuid(): String {
13 | return java.util.UUID.randomUUID().toString()
14 | }
15 |
16 | fun randomInt(): Int {
17 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
18 | }
19 |
20 | fun randomLong(): Long {
21 | return randomInt().toLong()
22 | }
23 |
24 | fun randomBoolean(): Boolean {
25 | return Math.random() < 0.5
26 | }
27 |
28 | fun makeStringList(count: Int): List {
29 | val items: MutableList = mutableListOf()
30 | repeat(count) {
31 | items.add(randomUuid())
32 | }
33 | return items
34 | }
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/remote/src/test/java/org/buffer/android/boilerplate/remote/mapper/BufferooEntityMapperTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.mapper
2 |
3 | import org.buffer.android.boilerplate.remote.test.factory.BufferooFactory
4 | import org.junit.Before
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.junit.runners.JUnit4
8 | import kotlin.test.assertEquals
9 |
10 | @RunWith(JUnit4::class)
11 | class BufferooEntityMapperTest {
12 |
13 | private lateinit var bufferooEntityMapper: BufferooEntityMapper
14 |
15 | @Before
16 | fun setUp() {
17 | bufferooEntityMapper = BufferooEntityMapper()
18 | }
19 |
20 | @Test
21 | fun mapFromRemoteMapsData() {
22 | val bufferooModel = BufferooFactory.makeBufferooModel()
23 | val bufferooEntity = bufferooEntityMapper.mapFromRemote(bufferooModel)
24 |
25 | assertEquals(bufferooModel.name, bufferooEntity.name)
26 | assertEquals(bufferooModel.title, bufferooEntity.title)
27 | assertEquals(bufferooModel.avatar, bufferooEntity.avatar)
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/mobile-ui/src/test/java/org/buffer/android/boilerplate/ui/BufferooMapperTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui
2 |
3 | import org.buffer.android.boilerplate.ui.mapper.BufferooMapper
4 | import org.buffer.android.boilerplate.ui.test.factory.BufferooFactory
5 | import org.junit.Before
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 | import org.junit.runners.JUnit4
9 | import kotlin.test.assertEquals
10 |
11 | @RunWith(JUnit4::class)
12 | class BufferooMapperTest {
13 |
14 | private lateinit var bufferooMapper: BufferooMapper
15 |
16 | @Before
17 | fun setUp() {
18 | bufferooMapper = BufferooMapper()
19 | }
20 |
21 | @Test
22 | fun mapToViewMapsData() {
23 | val bufferooView = BufferooFactory.makeBufferooView()
24 | val bufferooViewModel = bufferooMapper.mapToViewModel(bufferooView)
25 |
26 | assertEquals(bufferooView.name, bufferooViewModel.name)
27 | assertEquals(bufferooView.title, bufferooViewModel.title)
28 | assertEquals(bufferooView.avatar, bufferooViewModel.avatar)
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/remote/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.7
4 | targetCompatibility = 1.7
5 |
6 | dependencies {
7 | def remoteDependencies = rootProject.ext.remoteDependencies
8 | def remoteTestDependencies = rootProject.ext.remoteTestDependencies
9 |
10 | implementation remoteDependencies.javaxAnnotation
11 |
12 | implementation remoteDependencies.kotlin
13 | implementation remoteDependencies.javaxInject
14 | implementation remoteDependencies.rxKotlin
15 | implementation remoteDependencies.gson
16 | implementation remoteDependencies.okHttp
17 | implementation remoteDependencies.okHttpLogger
18 | implementation remoteDependencies.retrofit
19 | implementation remoteDependencies.retrofitConverter
20 | implementation remoteDependencies.retrofitAdapter
21 |
22 | testImplementation remoteTestDependencies.junit
23 | testImplementation remoteTestDependencies.kotlinJUnit
24 | testImplementation remoteTestDependencies.mockito
25 | testImplementation remoteTestDependencies.assertj
26 |
27 | compile project(':data')
28 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/mapper/BufferooEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.mapper
2 |
3 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
4 | import org.buffer.android.boilerplate.data.model.BufferooEntity
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Map a [CachedBufferoo] instance to and from a [BufferooEntity] instance when data is moving between
9 | * this later and the Data layer
10 | */
11 | class BufferooEntityMapper @Inject constructor(): EntityMapper {
12 |
13 | /**
14 | * Map a [BufferooEntity] instance to a [CachedBufferoo] instance
15 | */
16 | override fun mapToCached(type: BufferooEntity): CachedBufferoo {
17 | return CachedBufferoo(type.name, type.title, type.avatar)
18 | }
19 |
20 | /**
21 | * Map a [CachedBufferoo] instance to a [BufferooEntity] instance
22 | */
23 | override fun mapFromCached(type: CachedBufferoo): BufferooEntity {
24 | return BufferooEntity(type.name, type.title, type.avatar)
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/licence.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2017] [Buffer inc]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/PreferencesHelper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | import javax.inject.Inject
7 | import javax.inject.Singleton
8 |
9 | /**
10 | * General Preferences Helper class, used for storing preference values using the Preference API
11 | */
12 | @Singleton
13 | class PreferencesHelper @Inject constructor(context: Context) {
14 |
15 | companion object {
16 | private val PREF_BUFFER_PACKAGE_NAME = "org.buffer.android.boilerplate.preferences"
17 |
18 | private val PREF_KEY_LAST_CACHE = "last_cache"
19 | }
20 |
21 | private val bufferPref: SharedPreferences
22 |
23 | init {
24 | bufferPref = context.getSharedPreferences(PREF_BUFFER_PACKAGE_NAME, Context.MODE_PRIVATE)
25 | }
26 |
27 | /**
28 | * Store and retrieve the last time data was cached
29 | */
30 | var lastCacheTime: Long
31 | get() = bufferPref.getLong(PREF_KEY_LAST_CACHE, 0)
32 | set(lastCache) = bufferPref.edit().putLong(PREF_KEY_LAST_CACHE, lastCache).apply()
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/activity_browse.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/browse/GetBufferoos.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor.browse
2 |
3 | import io.reactivex.Single
4 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
5 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
6 | import org.buffer.android.boilerplate.domain.interactor.SingleUseCase
7 | import org.buffer.android.boilerplate.domain.model.Bufferoo
8 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
9 | import javax.inject.Inject
10 |
11 | /**
12 | * Use case used for retreiving a [List] of [Bufferoo] instances from the [BufferooRepository]
13 | */
14 | open class GetBufferoos @Inject constructor(val bufferooRepository: BufferooRepository,
15 | threadExecutor: ThreadExecutor,
16 | postExecutionThread: PostExecutionThread):
17 | SingleUseCase, Void?>(threadExecutor, postExecutionThread) {
18 |
19 | public override fun buildUseCaseObservable(params: Void?): Single> {
20 | return bufferooRepository.getBufferoos()
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooRemoteDataStore.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
7 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Implementation of the [BufferooDataStore] interface to provide a means of communicating
12 | * with the remote data source
13 | */
14 | open class BufferooRemoteDataStore @Inject constructor(private val bufferooRemote: BufferooRemote) :
15 | BufferooDataStore {
16 |
17 | override fun clearBufferoos(): Completable {
18 | throw UnsupportedOperationException()
19 | }
20 |
21 | override fun saveBufferoos(bufferoos: List): Completable {
22 | throw UnsupportedOperationException()
23 | }
24 |
25 | /**
26 | * Retrieve a list of [BufferooEntity] instances from the API
27 | */
28 | override fun getBufferoos(): Single> {
29 | return bufferooRemote.getBufferoos()
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/BufferooApplication.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.support.v4.BuildConfig
6 | import dagger.android.AndroidInjector
7 | import dagger.android.DispatchingAndroidInjector
8 | import dagger.android.HasActivityInjector
9 | import org.buffer.android.boilerplate.ui.injection.DaggerApplicationComponent
10 | import timber.log.Timber
11 | import javax.inject.Inject
12 |
13 | class BufferooApplication : Application(), HasActivityInjector {
14 |
15 | @Inject lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector
16 |
17 | override fun onCreate() {
18 | super.onCreate()
19 | DaggerApplicationComponent
20 | .builder()
21 | .application(this)
22 | .build()
23 | .inject(this)
24 | setupTimber()
25 | }
26 |
27 | private fun setupTimber() {
28 | if (BuildConfig.DEBUG) {
29 | Timber.plant(Timber.DebugTree())
30 | }
31 | }
32 |
33 | override fun activityInjector(): AndroidInjector {
34 | return activityDispatchingAndroidInjector
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/remote/src/test/java/org/buffer/android/boilerplate/remote/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote.test.factory
2 |
3 | import org.buffer.android.boilerplate.remote.test.factory.DataFactory.Factory.randomUuid
4 | import org.buffer.android.boilerplate.remote.BufferooService
5 | import org.buffer.android.boilerplate.remote.model.BufferooModel
6 |
7 | /**
8 | * Factory class for Bufferoo related instances
9 | */
10 | class BufferooFactory {
11 |
12 | companion object Factory {
13 |
14 | fun makeBufferooResponse(): BufferooService.BufferooResponse {
15 | val bufferooResponse = BufferooService.BufferooResponse()
16 | bufferooResponse.team = makeBufferooModelList(5)
17 | return bufferooResponse
18 | }
19 |
20 | fun makeBufferooModelList(count: Int): List {
21 | val bufferooEntities = mutableListOf()
22 | repeat(count) {
23 | bufferooEntities.add(makeBufferooModel())
24 | }
25 | return bufferooEntities
26 | }
27 |
28 | fun makeBufferooModel(): BufferooModel {
29 | return BufferooModel(randomUuid(), randomUuid(), randomUuid())
30 | }
31 |
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import io.reactivex.Single
4 | import org.buffer.android.boilerplate.data.model.BufferooEntity
5 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
6 | import org.buffer.android.boilerplate.remote.mapper.BufferooEntityMapper
7 | import javax.inject.Inject
8 |
9 | /**
10 | * Remote implementation for retrieving Bufferoo instances. This class implements the
11 | * [BufferooRemote] from the Data layer as it is that layers responsibility for defining the
12 | * operations in which data store implementation layers can carry out.
13 | */
14 | class BufferooRemoteImpl @Inject constructor(private val bufferooService: BufferooService,
15 | private val entityMapper: BufferooEntityMapper) :
16 | BufferooRemote {
17 |
18 | /**
19 | * Retrieve a list of [BufferooEntity] instances from the [BufferooService].
20 | */
21 | override fun getBufferoos(): Single> {
22 | return bufferooService.getBufferoos()
23 | .map {
24 | it.team.map { listItem ->
25 | entityMapper.mapFromRemote(listItem)
26 | }
27 | }
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/BrowseActivityModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import org.buffer.android.boilerplate.domain.interactor.browse.GetBufferoos
6 | import org.buffer.android.boilerplate.presentation.browse.BrowseBufferoosContract
7 | import org.buffer.android.boilerplate.presentation.browse.BrowseBufferoosPresenter
8 | import org.buffer.android.boilerplate.presentation.mapper.BufferooMapper
9 | import org.buffer.android.boilerplate.ui.browse.BrowseActivity
10 | import org.buffer.android.boilerplate.ui.injection.scopes.PerActivity
11 |
12 |
13 |
14 | /**
15 | * Module used to provide dependencies at an activity-level.
16 | */
17 | @Module
18 | open class BrowseActivityModule {
19 |
20 | @PerActivity
21 | @Provides
22 | internal fun provideBrowseView(browseActivity: BrowseActivity): BrowseBufferoosContract.View {
23 | return browseActivity
24 | }
25 |
26 | @PerActivity
27 | @Provides
28 | internal fun provideBrowsePresenter(mainView: BrowseBufferoosContract.View,
29 | getBufferoos: GetBufferoos, mapper: BufferooMapper):
30 | BrowseBufferoosContract.Presenter {
31 | return BrowseBufferoosPresenter(mainView, getBufferoos, mapper)
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.test.factory
2 |
3 | import org.buffer.android.boilerplate.data.model.BufferooEntity
4 | import org.buffer.android.boilerplate.data.test.factory.DataFactory.Factory.randomUuid
5 | import org.buffer.android.boilerplate.domain.model.Bufferoo
6 |
7 | /**
8 | * Factory class for Bufferoo related instances
9 | */
10 | class BufferooFactory {
11 |
12 | companion object Factory {
13 |
14 | fun makeBufferooEntity(): BufferooEntity {
15 | return BufferooEntity(randomUuid(), randomUuid(), randomUuid())
16 | }
17 |
18 | fun makeBufferoo(): Bufferoo {
19 | return Bufferoo(randomUuid(), randomUuid(), randomUuid())
20 | }
21 |
22 | fun makeBufferooEntityList(count: Int): List {
23 | val bufferooEntities = mutableListOf()
24 | repeat(count) {
25 | bufferooEntities.add(makeBufferooEntity())
26 | }
27 | return bufferooEntities
28 | }
29 |
30 | fun makeBufferooList(count: Int): List {
31 | val bufferoos = mutableListOf()
32 | repeat(count) {
33 | bufferoos.add(makeBufferoo())
34 | }
35 | return bufferoos
36 | }
37 |
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/TestApplication.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.support.test.InstrumentationRegistry
6 | import dagger.android.AndroidInjector
7 | import dagger.android.DispatchingAndroidInjector
8 | import dagger.android.HasActivityInjector
9 | import org.buffer.android.boilerplate.ui.injection.component.DaggerTestApplicationComponent
10 | import org.buffer.android.boilerplate.ui.injection.component.TestApplicationComponent
11 | import javax.inject.Inject
12 |
13 | class TestApplication: Application(), HasActivityInjector {
14 |
15 | @Inject lateinit var injector: DispatchingAndroidInjector
16 |
17 | private lateinit var appComponent: TestApplicationComponent
18 |
19 | override fun onCreate() {
20 | super.onCreate()
21 | appComponent = DaggerTestApplicationComponent.builder().application(this).build()
22 | appComponent.inject(this)
23 | }
24 |
25 | companion object {
26 |
27 | fun appComponent(): TestApplicationComponent {
28 | return (InstrumentationRegistry.getTargetContext().applicationContext as TestApplication).
29 | appComponent
30 | }
31 |
32 | }
33 |
34 | override fun activityInjector(): AndroidInjector {
35 | return injector
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/item_bufferoo.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
25 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/DbOpenHelper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db
2 |
3 |
4 | import android.content.Context
5 | import android.database.sqlite.SQLiteDatabase
6 | import android.database.sqlite.SQLiteOpenHelper
7 | import android.os.Build
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Standard DB class for configuring the Database and handling migrations during upgrades
12 | */
13 | class DbOpenHelper @Inject constructor(context: Context) :
14 | SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
15 |
16 | companion object {
17 | val DATABASE_NAME = "buffer-clean-arch-boilerplate.db"
18 | val DATABASE_VERSION = 1
19 | }
20 |
21 | override fun onConfigure(db: SQLiteDatabase) {
22 | super.onConfigure(db)
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
24 | db.setForeignKeyConstraintsEnabled(true)
25 | } else {
26 | db.execSQL("PRAGMA foreign_keys=ON;")
27 | }
28 | }
29 |
30 | override fun onCreate(db: SQLiteDatabase) {
31 | db.beginTransaction()
32 | try {
33 | db.execSQL(Db.BufferooTable.CREATE)
34 | db.setTransactionSuccessful()
35 | } finally {
36 | db.endTransaction()
37 | }
38 | }
39 |
40 | override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
41 |
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooDataStoreFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import org.buffer.android.boilerplate.data.repository.BufferooCache
4 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
5 | import javax.inject.Inject
6 |
7 | /**
8 | * Create an instance of a BufferooDataStore
9 | */
10 | open class BufferooDataStoreFactory @Inject constructor(
11 | private val bufferooCache: BufferooCache,
12 | private val bufferooCacheDataStore: BufferooCacheDataStore,
13 | private val bufferooRemoteDataStore: BufferooRemoteDataStore) {
14 |
15 | /**
16 | * Returns a DataStore based on whether or not there is content in the cache and the cache
17 | * has not expired
18 | */
19 | open fun retrieveDataStore(): BufferooDataStore {
20 | if (bufferooCache.isCached() && !bufferooCache.isExpired()) {
21 | return retrieveCacheDataStore()
22 | }
23 | return retrieveRemoteDataStore()
24 | }
25 |
26 | /**
27 | * Return an instance of the Remote Data Store
28 | */
29 | open fun retrieveCacheDataStore(): BufferooDataStore {
30 | return bufferooCacheDataStore
31 | }
32 |
33 | /**
34 | * Return an instance of the Cache Data Store
35 | */
36 | open fun retrieveRemoteDataStore(): BufferooDataStore {
37 | return bufferooRemoteDataStore
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/test/factory/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.test.factory
2 |
3 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
4 | import org.buffer.android.boilerplate.data.model.BufferooEntity
5 | import org.buffer.android.boilerplate.cache.test.factory.DataFactory.Factory.randomUuid
6 |
7 | /**
8 | * Factory class for Bufferoo related instances
9 | */
10 | class BufferooFactory {
11 |
12 | companion object Factory {
13 |
14 | fun makeCachedBufferoo(): CachedBufferoo {
15 | return CachedBufferoo(randomUuid(), randomUuid(), randomUuid())
16 | }
17 |
18 | fun makeBufferooEntity(): BufferooEntity {
19 | return BufferooEntity(randomUuid(), randomUuid(), randomUuid())
20 | }
21 |
22 | fun makeBufferooEntityList(count: Int): List {
23 | val bufferooEntities = mutableListOf()
24 | repeat(count) {
25 | bufferooEntities.add(makeBufferooEntity())
26 | }
27 | return bufferooEntities
28 | }
29 |
30 | fun makeCachedBufferooList(count: Int): List {
31 | val cachedBufferoos = mutableListOf()
32 | repeat(count) {
33 | cachedBufferoos.add(makeCachedBufferoo())
34 | }
35 | return cachedBufferoos
36 | }
37 |
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk:
3 | - oraclejdk8
4 | # Use the Travis Container-Based Infrastructure
5 |
6 | env:
7 | global:
8 | - ANDROID_API_LEVEL=26
9 | - EMULATOR_API_LEVEL=22
10 | - ANDROID_BUILD_TOOLS_VERSION=26.0.0
11 | - ANDROID_ABI=armeabi-v7a
12 | - ANDROID_TAG=google_apis
13 | - ANDROID_TARGET=android-$ANDROID_API_LEVEL
14 | - ADB_INSTALL_TIMEOUT=20 # minutes (2 minutes by default)
15 |
16 | android:
17 | components:
18 | - tools
19 | - platform-tools
20 | - android-$EMULATOR_API_LEVEL
21 | - build-tools-$ANDROID_BUILD_TOOLS_VERSION
22 | - android-$ANDROID_API_LEVEL
23 | # For Google APIs
24 | - addon-google_apis-google-$ANDROID_API_LEVEL
25 | # Google Play Services
26 | - extra-google-google_play_services
27 | # Support library
28 | - extra-android-support
29 | # Latest artifacts in local repository
30 | - extra-google-m2repository
31 | - extra-android-m2repository
32 | # Specify at least one system image
33 | - sys-img-armeabi-v7a-android-$EMULATOR_API_LEVEL
34 |
35 | before_script:
36 | - echo no | android create avd --force -n test -t android-$EMULATOR_API_LEVEL --abi armeabi-v7a
37 | - emulator -avd test -no-audio -no-window &
38 | - android-wait-for-emulator
39 | - adb shell input keyevent 82 &
40 |
41 | script:
42 | - ./gradlew build
43 | - ./gradlew build connectedAndroidTest --stacktrace
44 |
45 | after_success:
46 | - bash <(curl -s https://codecov.io/bash)
47 |
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/mapper/BufferooMapper.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db.mapper
2 |
3 | import android.content.ContentValues
4 | import android.database.Cursor
5 | import org.buffer.android.boilerplate.cache.db.Db
6 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
7 | import javax.inject.Inject
8 |
9 | /**
10 | * Maps a [CachedBufferoo] instance to a database entity.
11 | */
12 | class BufferooMapper @Inject constructor(): ModelDbMapper {
13 |
14 | /**
15 | * Construct an instance of [ContentValues] using the given [CachedBufferoo]
16 | */
17 | override fun toContentValues(model: CachedBufferoo): ContentValues {
18 | val values = ContentValues()
19 | values.put(Db.BufferooTable.NAME, model.name)
20 | values.put(Db.BufferooTable.TITLE, model.title)
21 | values.put(Db.BufferooTable.AVATAR, model.avatar)
22 | return values
23 | }
24 |
25 | /**
26 | * Parse the cursor creating a [CachedBufferoo] instance.
27 | */
28 | override fun parseCursor(cursor: Cursor): CachedBufferoo {
29 | val name = cursor.getString(cursor.getColumnIndexOrThrow(Db.BufferooTable.NAME))
30 | val title = cursor.getString(cursor.getColumnIndexOrThrow(Db.BufferooTable.TITLE))
31 | val avatar = cursor.getString(cursor.getColumnIndexOrThrow(Db.BufferooTable.AVATAR))
32 | return CachedBufferoo(name, title, avatar)
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/injection/component/TestApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.component
2 |
3 | import android.app.Application
4 | import dagger.BindsInstance
5 | import dagger.Component
6 | import dagger.android.support.AndroidSupportInjectionModule
7 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
8 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
9 | import org.buffer.android.boilerplate.ui.injection.ApplicationComponent
10 | import org.buffer.android.boilerplate.ui.injection.module.ActivityBindingModule
11 | import org.buffer.android.boilerplate.ui.injection.module.TestApplicationModule
12 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
13 | import org.buffer.android.boilerplate.ui.test.TestApplication
14 |
15 | @Component(modules = arrayOf(TestApplicationModule::class, ActivityBindingModule::class,
16 | AndroidSupportInjectionModule::class))
17 | @PerApplication
18 | interface TestApplicationComponent : ApplicationComponent {
19 |
20 | fun bufferooRepository(): BufferooRepository
21 |
22 | fun postExecutionThread(): PostExecutionThread
23 |
24 | fun inject(application: TestApplication)
25 |
26 | @Component.Builder
27 | interface Builder {
28 | @BindsInstance
29 | fun application(application: Application): TestApplicationComponent.Builder
30 |
31 | fun build(): TestApplicationComponent
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooCacheDataStore.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 | import org.buffer.android.boilerplate.data.repository.BufferooCache
7 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Implementation of the [BufferooDataStore] interface to provide a means of communicating
12 | * with the local data source
13 | */
14 | open class BufferooCacheDataStore @Inject constructor(private val bufferooCache: BufferooCache) :
15 | BufferooDataStore {
16 |
17 | /**
18 | * Clear all Bufferoos from the cache
19 | */
20 | override fun clearBufferoos(): Completable {
21 | return bufferooCache.clearBufferoos()
22 | }
23 |
24 | /**
25 | * Save a given [List] of [BufferooEntity] instances to the cache
26 | */
27 | override fun saveBufferoos(bufferoos: List): Completable {
28 | return bufferooCache.saveBufferoos(bufferoos)
29 | .doOnComplete {
30 | bufferooCache.setLastCacheTime(System.currentTimeMillis())
31 | }
32 | }
33 |
34 | /**
35 | * Retrieve a list of [BufferooEntity] instance from the cache
36 | */
37 | override fun getBufferoos(): Single> {
38 | return bufferooCache.getBufferoos()
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/CompletableUseCase.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.disposables.Disposable
5 | import io.reactivex.disposables.Disposables
6 | import io.reactivex.schedulers.Schedulers
7 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
8 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
9 |
10 | /**
11 | * Abstract class for a UseCase that returns an instance of a [Completable].
12 | */
13 | abstract class CompletableUseCase protected constructor(
14 | private val threadExecutor: ThreadExecutor,
15 | private val postExecutionThread: PostExecutionThread) {
16 |
17 | private val subscription = Disposables.empty()
18 |
19 | /**
20 | * Builds a [Completable] which will be used when the current [CompletableUseCase] is executed.
21 | */
22 | protected abstract fun buildUseCaseObservable(params: Params): Completable
23 |
24 | /**
25 | * Executes the current use case.
26 | */
27 | fun execute(params: Params): Completable {
28 | return this.buildUseCaseObservable(params)
29 | .subscribeOn(Schedulers.from(threadExecutor))
30 | .observeOn(postExecutionThread.scheduler)
31 | }
32 |
33 | /**
34 | * Unsubscribes from current [Disposable].
35 | */
36 | fun unsubscribe() {
37 | if (!subscription.isDisposed) {
38 | subscription.dispose()
39 | }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooCache.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.repository
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 |
7 | /**
8 | * Interface defining methods for the caching of Bufferroos. This is to be implemented by the
9 | * cache layer, using this interface as a way of communicating.
10 | */
11 | interface BufferooCache {
12 |
13 | /**
14 | * Clear all Bufferoos from the cache
15 | */
16 | fun clearBufferoos(): Completable
17 |
18 | /**
19 | * Save a given list of Bufferoos to the cache
20 | */
21 | fun saveBufferoos(bufferoos: List): Completable
22 |
23 | /**
24 | * Retrieve a list of Bufferoos, from the cache
25 | */
26 | fun getBufferoos(): Single>
27 |
28 | /**
29 | * Checks if an element (User) exists in the cache.
30 |
31 | * @param userId The id used to look for inside the cache.
32 | * *
33 | * @return true if the element is cached, otherwise false.
34 | */
35 | fun isCached(): Boolean
36 |
37 | /**
38 | * Checks if an element (User) exists in the cache.
39 |
40 | * @param userId The id used to look for inside the cache.
41 | * *
42 | * @return true if the element is cached, otherwise false.
43 | */
44 | fun setLastCacheTime(lastCache: Long)
45 |
46 | /**
47 | * Checks if the cache is expired.
48 |
49 | * @return true, the cache is expired, otherwise false.
50 | */
51 | fun isExpired(): Boolean
52 |
53 | }
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/mapper/BufferooMapperTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.mapper
2 |
3 | import org.buffer.android.boilerplate.data.model.BufferooEntity
4 | import org.buffer.android.boilerplate.data.test.factory.BufferooFactory
5 | import org.buffer.android.boilerplate.domain.model.Bufferoo
6 | import org.junit.Before
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import org.junit.runners.JUnit4
10 | import kotlin.test.assertEquals
11 |
12 | @RunWith(JUnit4::class)
13 | class BufferooMapperTest {
14 |
15 | private lateinit var bufferooMapper: BufferooMapper
16 |
17 | @Before
18 | fun setUp() {
19 | bufferooMapper = BufferooMapper()
20 | }
21 |
22 | @Test
23 | fun mapFromEntityMapsData() {
24 | val bufferooEntity = BufferooFactory.makeBufferooEntity()
25 | val bufferoo = bufferooMapper.mapFromEntity(bufferooEntity)
26 |
27 | assertBufferooDataEquality(bufferooEntity, bufferoo)
28 | }
29 |
30 | @Test
31 | fun mapToEntityMapsData() {
32 | val cachedBufferoo = BufferooFactory.makeBufferoo()
33 | val bufferooEntity = bufferooMapper.mapToEntity(cachedBufferoo)
34 |
35 | assertBufferooDataEquality(bufferooEntity, cachedBufferoo)
36 | }
37 |
38 | private fun assertBufferooDataEquality(bufferooEntity: BufferooEntity,
39 | bufferoo: Bufferoo) {
40 | assertEquals(bufferooEntity.name, bufferoo.name)
41 | assertEquals(bufferooEntity.title, bufferoo.title)
42 | assertEquals(bufferooEntity.avatar, bufferoo.avatar)
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/mapper/BufferooEntityMapperTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.mapper
2 |
3 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
4 | import org.buffer.android.boilerplate.cache.test.factory.BufferooFactory
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 | import org.junit.Before
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import org.junit.runners.JUnit4
10 | import kotlin.test.assertEquals
11 |
12 | @RunWith(JUnit4::class)
13 | class BufferooEntityMapperTest {
14 |
15 | private lateinit var bufferooEntityMapper: BufferooEntityMapper
16 |
17 | @Before
18 | fun setUp() {
19 | bufferooEntityMapper = BufferooEntityMapper()
20 | }
21 |
22 | @Test
23 | fun mapToCachedMapsData() {
24 | val bufferooEntity = BufferooFactory.makeBufferooEntity()
25 | val cachedBufferoo = bufferooEntityMapper.mapToCached(bufferooEntity)
26 |
27 | assertBufferooDataEquality(bufferooEntity, cachedBufferoo)
28 | }
29 |
30 | @Test
31 | fun mapFromCachedMapsData() {
32 | val cachedBufferoo = BufferooFactory.makeCachedBufferoo()
33 | val bufferooEntity = bufferooEntityMapper.mapFromCached(cachedBufferoo)
34 |
35 | assertBufferooDataEquality(bufferooEntity, cachedBufferoo)
36 | }
37 |
38 | private fun assertBufferooDataEquality(bufferooEntity: BufferooEntity,
39 | cachedBufferoo: CachedBufferoo) {
40 | assertEquals(bufferooEntity.name, cachedBufferoo.name)
41 | assertEquals(bufferooEntity.title, cachedBufferoo.title)
42 | assertEquals(bufferooEntity.avatar, cachedBufferoo.avatar)
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/SingleUseCase.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor
2 |
3 | import io.reactivex.Single
4 | import io.reactivex.disposables.CompositeDisposable
5 | import io.reactivex.disposables.Disposable
6 | import io.reactivex.observers.DisposableSingleObserver
7 | import io.reactivex.schedulers.Schedulers
8 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
9 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
10 |
11 |
12 |
13 | /**
14 | * Abstract class for a UseCase that returns an instance of a [Single].
15 | */
16 | abstract class SingleUseCase constructor(
17 | private val threadExecutor: ThreadExecutor,
18 | private val postExecutionThread: PostExecutionThread) {
19 |
20 | private val disposables = CompositeDisposable()
21 |
22 | /**
23 | * Builds a [Single] which will be used when the current [SingleUseCase] is executed.
24 | */
25 | protected abstract fun buildUseCaseObservable(params: Params? = null): Single
26 |
27 | /**
28 | * Executes the current use case.
29 | */
30 | open fun execute(singleObserver: DisposableSingleObserver, params: Params? = null) {
31 | val single = this.buildUseCaseObservable(params)
32 | .subscribeOn(Schedulers.from(threadExecutor))
33 | .observeOn(postExecutionThread.scheduler) as Single
34 | addDisposable(single.subscribeWith(singleObserver))
35 | }
36 |
37 | /**
38 | * Dispose from current [CompositeDisposable].
39 | */
40 | fun dispose() {
41 | if (!disposables.isDisposed) disposables.dispose()
42 | }
43 |
44 | /**
45 | * Dispose from current [CompositeDisposable].
46 | */
47 | private fun addDisposable(disposable: Disposable) {
48 | disposables.add(disposable)
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/browse/BrowseAdapter.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.browse
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import android.widget.TextView
9 | import com.bumptech.glide.Glide
10 | import com.bumptech.glide.request.RequestOptions
11 | import org.buffer.android.boilerplate.ui.R
12 | import org.buffer.android.boilerplate.ui.model.BufferooViewModel
13 | import javax.inject.Inject
14 |
15 | class BrowseAdapter @Inject constructor(): RecyclerView.Adapter() {
16 |
17 | var bufferoos: List = arrayListOf()
18 |
19 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
20 | val bufferoo = bufferoos[position]
21 | holder.nameText.text = bufferoo.name
22 | holder.titleText.text = bufferoo.title
23 |
24 | Glide.with(holder.itemView.context)
25 | .load(bufferoo.avatar)
26 | .apply(RequestOptions.circleCropTransform())
27 | .into(holder.avatarImage)
28 | }
29 |
30 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
31 | val itemView = LayoutInflater
32 | .from(parent.context)
33 | .inflate(R.layout.item_bufferoo, parent, false)
34 | return ViewHolder(itemView)
35 | }
36 |
37 | override fun getItemCount(): Int {
38 | return bufferoos.size
39 | }
40 |
41 | inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
42 | var avatarImage: ImageView
43 | var nameText: TextView
44 | var titleText: TextView
45 |
46 | init {
47 | avatarImage = view.findViewById(R.id.image_avatar)
48 | nameText = view.findViewById(R.id.text_name)
49 | titleText = view.findViewById(R.id.text_title)
50 | }
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/db/mapper/BufferooMapperTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db.mapper
2 |
3 | import android.database.Cursor
4 | import org.buffer.android.boilerplate.cache.BuildConfig
5 | import org.buffer.android.boilerplate.cache.db.Db
6 | import org.buffer.android.boilerplate.cache.db.DbOpenHelper
7 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
8 | import org.buffer.android.boilerplate.cache.test.DefaultConfig
9 | import org.buffer.android.boilerplate.cache.test.factory.BufferooFactory
10 | import org.junit.Before
11 | import org.junit.Test
12 | import org.junit.runner.RunWith
13 | import org.robolectric.RobolectricTestRunner
14 | import org.robolectric.RuntimeEnvironment
15 | import org.robolectric.annotation.Config
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(RobolectricTestRunner::class)
19 | @Config(constants = BuildConfig::class, sdk = intArrayOf(DefaultConfig.EMULATE_SDK))
20 | class BufferooMapperTest {
21 |
22 | private lateinit var bufferooMapper: BufferooMapper
23 | private val database = DbOpenHelper(RuntimeEnvironment.application).writableDatabase
24 |
25 | @Before
26 | fun setUp() {
27 | bufferooMapper = BufferooMapper()
28 | }
29 |
30 | @Test
31 | fun parseCursorMapsData() {
32 | val cachedBufferoo = BufferooFactory.makeCachedBufferoo()
33 | insertCachedBufferoo(cachedBufferoo)
34 |
35 | val cursor = retrieveCachedBufferooCursor()
36 | assertEquals(cachedBufferoo, bufferooMapper.parseCursor(cursor))
37 | }
38 |
39 | private fun retrieveCachedBufferooCursor(): Cursor {
40 | val cursor = database.rawQuery("SELECT * FROM " + Db.BufferooTable.TABLE_NAME, null)
41 | cursor.moveToFirst()
42 | return cursor
43 | }
44 |
45 | private fun insertCachedBufferoo(cachedBufferoo: CachedBufferoo) {
46 | database.insertOrThrow(Db.BufferooTable.TABLE_NAME, null,
47 | bufferooMapper.toContentValues(cachedBufferoo))
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosPresenter.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.browse
2 |
3 | import io.reactivex.observers.DisposableSingleObserver
4 | import org.buffer.android.boilerplate.domain.interactor.SingleUseCase
5 | import org.buffer.android.boilerplate.domain.model.Bufferoo
6 | import org.buffer.android.boilerplate.presentation.mapper.BufferooMapper
7 | import javax.inject.Inject
8 |
9 | class BrowseBufferoosPresenter @Inject constructor(val browseView: BrowseBufferoosContract.View,
10 | val getBufferoosUseCase: SingleUseCase, Void>,
11 | val bufferooMapper: BufferooMapper):
12 | BrowseBufferoosContract.Presenter {
13 |
14 | init {
15 | browseView.setPresenter(this)
16 | }
17 |
18 | override fun start() {
19 | retrieveBufferoos()
20 | }
21 |
22 | override fun stop() {
23 | getBufferoosUseCase.dispose()
24 | }
25 |
26 | override fun retrieveBufferoos() {
27 | getBufferoosUseCase.execute(BufferooSubscriber())
28 | }
29 |
30 | internal fun handleGetBufferoosSuccess(bufferoos: List) {
31 | browseView.hideErrorState()
32 | if (bufferoos.isNotEmpty()) {
33 | browseView.hideEmptyState()
34 | browseView.showBufferoos(bufferoos.map { bufferooMapper.mapToView(it) })
35 | } else {
36 | browseView.hideBufferoos()
37 | browseView.showEmptyState()
38 | }
39 | }
40 |
41 | inner class BufferooSubscriber: DisposableSingleObserver>() {
42 |
43 | override fun onSuccess(t: List) {
44 | handleGetBufferoosSuccess(t)
45 | }
46 |
47 | override fun onError(exception: Throwable) {
48 | browseView.hideBufferoos()
49 | browseView.hideEmptyState()
50 | browseView.showErrorState()
51 | }
52 |
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/executor/JobExecutor.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.executor
2 |
3 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
4 | import java.util.concurrent.LinkedBlockingQueue
5 | import java.util.concurrent.ThreadFactory
6 | import java.util.concurrent.ThreadPoolExecutor
7 | import java.util.concurrent.TimeUnit
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Decorated [ThreadPoolExecutor]
12 | */
13 | open class JobExecutor @Inject constructor(): ThreadExecutor {
14 |
15 | private val workQueue: LinkedBlockingQueue
16 |
17 | private val threadPoolExecutor: ThreadPoolExecutor
18 |
19 | private val threadFactory: ThreadFactory
20 |
21 | init {
22 | this.workQueue = LinkedBlockingQueue()
23 | this.threadFactory = JobThreadFactory()
24 | this.threadPoolExecutor = ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE,
25 | KEEP_ALIVE_TIME.toLong(), KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory)
26 | }
27 |
28 | override fun execute(runnable: Runnable?) {
29 | if (runnable == null) {
30 | throw IllegalArgumentException("Runnable to execute cannot be null")
31 | }
32 | this.threadPoolExecutor.execute(runnable)
33 | }
34 |
35 | private class JobThreadFactory : ThreadFactory {
36 | private var counter = 0
37 |
38 | override fun newThread(runnable: Runnable): Thread {
39 | return Thread(runnable, THREAD_NAME + counter++)
40 | }
41 |
42 | companion object {
43 | private val THREAD_NAME = "android_"
44 | }
45 | }
46 |
47 | companion object {
48 |
49 | private val INITIAL_POOL_SIZE = 3
50 | private val MAX_POOL_SIZE = 5
51 |
52 | // Sets the amount of time an idle thread waits before terminating
53 | private val KEEP_ALIVE_TIME = 10
54 |
55 | // Sets the Time Unit to seconds
56 | private val KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS
57 | }
58 | }
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/source/BufferooRemoteDataStoreTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import com.nhaarman.mockito_kotlin.whenever
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
8 | import org.buffer.android.boilerplate.data.test.factory.BufferooFactory
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.junit.runners.JUnit4
13 |
14 | @RunWith(JUnit4::class)
15 | class BufferooRemoteDataStoreTest {
16 |
17 | private lateinit var bufferooRemoteDataStore: BufferooRemoteDataStore
18 |
19 | private lateinit var bufferooRemote: BufferooRemote
20 |
21 | @Before
22 | fun setUp() {
23 | bufferooRemote = mock()
24 | bufferooRemoteDataStore = BufferooRemoteDataStore(bufferooRemote)
25 | }
26 |
27 | //
28 | @Test(expected = UnsupportedOperationException::class)
29 | fun clearBufferoosThrowsException() {
30 | bufferooRemoteDataStore.clearBufferoos().test()
31 | }
32 | //
33 |
34 | //
35 | @Test(expected = UnsupportedOperationException::class)
36 | fun saveBufferoosThrowsException() {
37 | bufferooRemoteDataStore.saveBufferoos(BufferooFactory.makeBufferooEntityList(2)).test()
38 | }
39 | //
40 |
41 | //
42 | @Test
43 | fun getBufferoosCompletes() {
44 | stubBufferooCacheGetBufferoos(Single.just(BufferooFactory.makeBufferooEntityList(2)))
45 | val testObserver = bufferooRemote.getBufferoos().test()
46 | testObserver.assertComplete()
47 | }
48 | //
49 |
50 | //
51 | private fun stubBufferooCacheGetBufferoos(single: Single>) {
52 | whenever(bufferooRemote.getBufferoos())
53 | .thenReturn(single)
54 | }
55 | //
56 |
57 | }
--------------------------------------------------------------------------------
/remote/src/test/java/org/buffer/android/boilerplate/remote/BufferooRemoteImplTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import com.nhaarman.mockito_kotlin.whenever
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 | import org.buffer.android.boilerplate.remote.mapper.BufferooEntityMapper
8 | import org.buffer.android.boilerplate.remote.test.factory.BufferooFactory
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.junit.runners.JUnit4
13 |
14 | @RunWith(JUnit4::class)
15 | class BufferooRemoteImplTest {
16 |
17 | private lateinit var entityMapper: BufferooEntityMapper
18 | private lateinit var bufferooService: BufferooService
19 |
20 | private lateinit var bufferooRemoteImpl: BufferooRemoteImpl
21 |
22 | @Before
23 | fun setup() {
24 | entityMapper = mock()
25 | bufferooService = mock()
26 | bufferooRemoteImpl = BufferooRemoteImpl(bufferooService, entityMapper)
27 | }
28 |
29 | //
30 | @Test
31 | fun getBufferoosCompletes() {
32 | stubBufferooServiceGetBufferoos(Single.just(BufferooFactory.makeBufferooResponse()))
33 | val testObserver = bufferooRemoteImpl.getBufferoos().test()
34 | testObserver.assertComplete()
35 | }
36 |
37 | @Test
38 | fun getBufferoosReturnsData() {
39 | val bufferooResponse = BufferooFactory.makeBufferooResponse()
40 | stubBufferooServiceGetBufferoos(Single.just(bufferooResponse))
41 | val bufferooEntities = mutableListOf()
42 | bufferooResponse.team.forEach {
43 | bufferooEntities.add(entityMapper.mapFromRemote(it))
44 | }
45 |
46 | val testObserver = bufferooRemoteImpl.getBufferoos().test()
47 | testObserver.assertValue(bufferooEntities)
48 | }
49 | //
50 |
51 | private fun stubBufferooServiceGetBufferoos(single: Single) {
52 | whenever(bufferooService.getBufferoos())
53 | .thenReturn(single)
54 | }
55 | }
--------------------------------------------------------------------------------
/data/src/main/java/org/buffer/android/boilerplate/data/BufferooDataRepository.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Single
5 | import org.buffer.android.boilerplate.data.mapper.BufferooMapper
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 | import org.buffer.android.boilerplate.data.source.BufferooDataStoreFactory
8 | import org.buffer.android.boilerplate.data.source.BufferooRemoteDataStore
9 | import org.buffer.android.boilerplate.domain.model.Bufferoo
10 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
11 | import javax.inject.Inject
12 |
13 | /**
14 | * Provides an implementation of the [BufferooRepository] interface for communicating to and from
15 | * data sources
16 | */
17 | class BufferooDataRepository @Inject constructor(private val factory: BufferooDataStoreFactory,
18 | private val bufferooMapper: BufferooMapper) :
19 | BufferooRepository {
20 |
21 | override fun clearBufferoos(): Completable {
22 | return factory.retrieveCacheDataStore().clearBufferoos()
23 | }
24 |
25 | override fun saveBufferoos(bufferoos: List): Completable {
26 | val bufferooEntities = bufferoos.map { bufferooMapper.mapToEntity(it) }
27 | return saveBufferooEntities(bufferooEntities)
28 | }
29 |
30 | private fun saveBufferooEntities(bufferoos: List): Completable {
31 | return factory.retrieveCacheDataStore().saveBufferoos(bufferoos)
32 | }
33 |
34 | override fun getBufferoos(): Single> {
35 | val dataStore = factory.retrieveDataStore()
36 | return dataStore.getBufferoos()
37 | .flatMap {
38 | if (dataStore is BufferooRemoteDataStore) {
39 | saveBufferooEntities(it).toSingle { it }
40 | } else {
41 | Single.just(it)
42 | }
43 | }
44 | .map { list ->
45 | list.map { listItem ->
46 | bufferooMapper.mapFromEntity(listItem)
47 | }
48 | }
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/injection/module/TestApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.nhaarman.mockito_kotlin.mock
6 | import dagger.Module
7 | import dagger.Provides
8 | import org.buffer.android.boilerplate.cache.PreferencesHelper
9 | import org.buffer.android.boilerplate.data.executor.JobExecutor
10 | import org.buffer.android.boilerplate.data.repository.BufferooCache
11 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
12 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
13 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
14 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
15 | import org.buffer.android.boilerplate.remote.BufferooService
16 | import org.buffer.android.boilerplate.ui.UiThread
17 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
18 |
19 | @Module
20 | class TestApplicationModule {
21 |
22 | @Provides
23 | @PerApplication
24 | fun provideContext(application: Application): Context {
25 | return application
26 | }
27 |
28 | @Provides
29 | @PerApplication
30 | internal fun providePreferencesHelper(): PreferencesHelper {
31 | return mock()
32 | }
33 |
34 | @Provides
35 | @PerApplication
36 | internal fun provideBufferooRepository(): BufferooRepository {
37 | return mock()
38 | }
39 |
40 | @Provides
41 | @PerApplication
42 | internal fun provideBufferooCache(): BufferooCache {
43 | return mock()
44 | }
45 |
46 | @Provides
47 | @PerApplication
48 | internal fun provideBufferooRemote(): BufferooRemote {
49 | return mock()
50 | }
51 |
52 | @Provides
53 | @PerApplication
54 | internal fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor {
55 | return jobExecutor
56 | }
57 |
58 | @Provides
59 | @PerApplication
60 | internal fun providePostExecutionThread(uiThread: UiThread): PostExecutionThread {
61 | return uiThread
62 | }
63 |
64 | @Provides
65 | @PerApplication
66 | internal fun provideBufferooService(): BufferooService {
67 | return mock()
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/cache/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | def globalConfiguration = rootProject.extensions.getByName("ext")
6 |
7 | compileSdkVersion globalConfiguration["androidCompileSdkVersion"]
8 | buildToolsVersion globalConfiguration["androidBuildToolsVersion"]
9 |
10 | defaultConfig {
11 | minSdkVersion globalConfiguration["androidMinSdkVersion"]
12 | targetSdkVersion globalConfiguration["androidTargetSdkVersion"]
13 | multiDexEnabled = true
14 | }
15 |
16 | dexOptions {
17 | preDexLibraries = false
18 | dexInProcess = false
19 | javaMaxHeapSize "4g"
20 | }
21 |
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_7
24 | targetCompatibility JavaVersion.VERSION_1_7
25 | }
26 |
27 | packagingOptions {
28 | exclude 'LICENSE.txt'
29 | exclude 'META-INF/DEPENDENCIES'
30 | exclude 'META-INF/ASL2.0'
31 | exclude 'META-INF/NOTICE'
32 | exclude 'META-INF/LICENSE'
33 | }
34 |
35 | lintOptions {
36 | quiet true
37 | abortOnError false
38 | ignoreWarnings true
39 | disable 'InvalidPackage' //Some libraries have issues with this.
40 | disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta.
41 | disable 'IconDensities' //For testing purpose. This is safe to remove.
42 | disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove.
43 | }
44 |
45 | }
46 |
47 | dependencies {
48 | def cacheDependencies = rootProject.ext.cacheDependencies
49 | def cacheTestDependencies = rootProject.ext.cacheTestDependencies
50 |
51 | compileOnly cacheDependencies.javaxAnnotation
52 |
53 | implementation project(':data')
54 |
55 | implementation cacheDependencies.kotlin
56 | implementation cacheDependencies.javaxInject
57 | implementation cacheDependencies.rxKotlin
58 | implementation cacheDependencies.gson
59 |
60 | testImplementation cacheTestDependencies.junit
61 | testImplementation cacheTestDependencies.kotlinJUnit
62 | testImplementation cacheTestDependencies.mockito
63 | testImplementation cacheTestDependencies.assertj
64 | testImplementation cacheTestDependencies.robolectric
65 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/browse/BrowseActivity.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.browse
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.support.v7.widget.LinearLayoutManager
6 | import android.view.View
7 | import dagger.android.AndroidInjection
8 | import kotlinx.android.synthetic.main.activity_browse.*
9 | import org.buffer.android.boilerplate.presentation.browse.BrowseBufferoosContract
10 | import org.buffer.android.boilerplate.presentation.model.BufferooView
11 | import org.buffer.android.boilerplate.ui.R
12 | import org.buffer.android.boilerplate.ui.mapper.BufferooMapper
13 | import javax.inject.Inject
14 |
15 | class BrowseActivity: AppCompatActivity(), BrowseBufferoosContract.View {
16 |
17 | @Inject lateinit var onboardingPresenter: BrowseBufferoosContract.Presenter
18 | @Inject lateinit var browseAdapter: BrowseAdapter
19 | @Inject lateinit var mapper: BufferooMapper
20 |
21 | override fun setPresenter(presenter: BrowseBufferoosContract.Presenter) {
22 | onboardingPresenter = presenter
23 | }
24 |
25 | override fun hideProgress() {
26 | progress.visibility = View.VISIBLE
27 | }
28 |
29 | override fun showProgress() {
30 | progress.visibility = View.GONE
31 | }
32 |
33 | override fun showBufferoos(bufferoos: List) {
34 | browseAdapter.bufferoos = bufferoos.map { mapper.mapToViewModel(it) }
35 | browseAdapter.notifyDataSetChanged()
36 | recycler_browse.visibility = View.VISIBLE
37 | }
38 |
39 | override fun hideBufferoos() {
40 | recycler_browse.visibility = View.VISIBLE
41 | }
42 |
43 | override fun showErrorState() {
44 | }
45 |
46 | override fun hideErrorState() {
47 | }
48 |
49 | override fun showEmptyState() {
50 | }
51 |
52 | override fun hideEmptyState() {
53 | }
54 |
55 | override fun onCreate(savedInstanceState: Bundle?) {
56 | super.onCreate(savedInstanceState)
57 | setContentView(R.layout.activity_browse)
58 | AndroidInjection.inject(this)
59 | setupBrowseRecycler()
60 | }
61 |
62 | override fun onStart() {
63 | super.onStart()
64 | onboardingPresenter.start()
65 | }
66 |
67 | private fun setupBrowseRecycler() {
68 | recycler_browse.layoutManager = LinearLayoutManager(this)
69 | recycler_browse.adapter = browseAdapter
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/domain/src/test/java/org/buffer/android/boilerplate/domain/usecase/bufferoo/GetBufferoosTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.usecase.bufferoo
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import com.nhaarman.mockito_kotlin.verify
5 | import com.nhaarman.mockito_kotlin.whenever
6 | import io.reactivex.Single
7 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
8 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
9 | import org.buffer.android.boilerplate.domain.interactor.browse.GetBufferoos
10 | import org.buffer.android.boilerplate.domain.model.Bufferoo
11 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
12 | import org.buffer.android.boilerplate.domain.test.factory.BufferooFactory
13 | import org.junit.Before
14 | import org.junit.Test
15 |
16 | class GetBufferoosTest {
17 |
18 | private lateinit var getBufferoos: GetBufferoos
19 |
20 | private lateinit var mockThreadExecutor: ThreadExecutor
21 | private lateinit var mockPostExecutionThread: PostExecutionThread
22 | private lateinit var mockBufferooRepository: BufferooRepository
23 |
24 | @Before
25 | fun setUp() {
26 | mockThreadExecutor = mock()
27 | mockPostExecutionThread = mock()
28 | mockBufferooRepository = mock()
29 | getBufferoos = GetBufferoos(mockBufferooRepository, mockThreadExecutor,
30 | mockPostExecutionThread)
31 | }
32 |
33 | @Test
34 | fun buildUseCaseObservableCallsRepository() {
35 | getBufferoos.buildUseCaseObservable(null)
36 | verify(mockBufferooRepository).getBufferoos()
37 | }
38 |
39 | @Test
40 | fun buildUseCaseObservableCompletes() {
41 | stubBufferooRepositoryGetBufferoos(Single.just(BufferooFactory.makeBufferooList(2)))
42 | val testObserver = getBufferoos.buildUseCaseObservable(null).test()
43 | testObserver.assertComplete()
44 | }
45 |
46 | @Test
47 | fun buildUseCaseObservableReturnsData() {
48 | val bufferoos = BufferooFactory.makeBufferooList(2)
49 | stubBufferooRepositoryGetBufferoos(Single.just(bufferoos))
50 | val testObserver = getBufferoos.buildUseCaseObservable(null).test()
51 | testObserver.assertValue(bufferoos)
52 | }
53 |
54 | private fun stubBufferooRepositoryGetBufferoos(single: Single>) {
55 | whenever(mockBufferooRepository.getBufferoos())
56 | .thenReturn(single)
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooServiceFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import com.google.gson.FieldNamingPolicy
4 | import com.google.gson.Gson
5 | import com.google.gson.GsonBuilder
6 | import okhttp3.OkHttpClient
7 | import okhttp3.logging.HttpLoggingInterceptor
8 | import retrofit2.Retrofit
9 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
10 | import retrofit2.converter.gson.GsonConverterFactory
11 | import java.util.concurrent.TimeUnit
12 |
13 | /**
14 | * Provide "make" methods to create instances of [BufferooService]
15 | * and its related dependencies, such as OkHttpClient, Gson, etc.
16 | */
17 | object BufferooServiceFactory {
18 |
19 | fun makeBuffeoorService(isDebug: Boolean): BufferooService {
20 | val okHttpClient = makeOkHttpClient(
21 | makeLoggingInterceptor(isDebug))
22 | return makeBufferooService(okHttpClient, makeGson())
23 | }
24 |
25 | private fun makeBufferooService(okHttpClient: OkHttpClient, gson: Gson): BufferooService {
26 | val retrofit = Retrofit.Builder()
27 | .baseUrl("https://joe-birch-dsdb.squarespace.com/s/")
28 | .client(okHttpClient)
29 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
30 | .addConverterFactory(GsonConverterFactory.create(gson))
31 | .build()
32 | return retrofit.create(BufferooService::class.java)
33 | }
34 |
35 | private fun makeOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
36 | return OkHttpClient.Builder()
37 | .addInterceptor(httpLoggingInterceptor)
38 | .connectTimeout(120, TimeUnit.SECONDS)
39 | .readTimeout(120, TimeUnit.SECONDS)
40 | .build()
41 | }
42 |
43 | private fun makeGson(): Gson {
44 | return GsonBuilder()
45 | .setLenient()
46 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
47 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
48 | .create()
49 | }
50 |
51 | private fun makeLoggingInterceptor(isDebug: Boolean): HttpLoggingInterceptor {
52 | val logging = HttpLoggingInterceptor()
53 | logging.level = if (isDebug)
54 | HttpLoggingInterceptor.Level.BODY
55 | else
56 | HttpLoggingInterceptor.Level.NONE
57 | return logging
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/util/RecyclerViewMatcher.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.util
2 |
3 | import android.content.res.Resources
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.View
6 | import org.hamcrest.Description
7 | import org.hamcrest.Matcher
8 | import org.hamcrest.TypeSafeMatcher
9 |
10 |
11 | open class RecyclerViewMatcher constructor(var recyclerViewId: Int) {
12 |
13 | fun atPosition(position: Int): Matcher {
14 | return atPositionOnView(position, -1)
15 | }
16 |
17 | fun atPositionOnView(position: Int, targetViewId: Int): Matcher {
18 |
19 | return object : TypeSafeMatcher() {
20 | internal var resources: Resources? = null
21 | internal var childView: View? = null
22 |
23 | override fun describeTo(description: Description) {
24 | var idDescription = Integer.toString(recyclerViewId)
25 | if (this.resources != null) {
26 | try {
27 | idDescription = this.resources!!.getResourceName(recyclerViewId)
28 | } catch (var4: Resources.NotFoundException) {
29 | idDescription = String.format("%s (resource name not found)",
30 | *arrayOf(Integer.valueOf(recyclerViewId)))
31 | }
32 |
33 | }
34 |
35 | description.appendText("with id: " + idDescription)
36 | }
37 |
38 | override fun matchesSafely(view: View): Boolean {
39 |
40 | this.resources = view.resources
41 |
42 | if (childView == null) {
43 | val recyclerView = view.rootView.findViewById(recyclerViewId)
44 | as RecyclerView
45 | if (recyclerView != null && recyclerView.id == recyclerViewId) {
46 | childView = recyclerView.findViewHolderForAdapterPosition(position).itemView
47 | } else {
48 | return false
49 | }
50 | }
51 |
52 | if (targetViewId == -1) {
53 | return view === childView
54 | } else {
55 | val targetView = childView?.findViewById(targetViewId)
56 | return view === targetView
57 | }
58 |
59 | }
60 | }
61 | }
62 |
63 | companion object {
64 |
65 | fun withRecyclerView(recyclerViewId: Int): RecyclerViewMatcher {
66 | return RecyclerViewMatcher(recyclerViewId)
67 | }
68 |
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/source/BufferooCacheDataStoreTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import com.nhaarman.mockito_kotlin.any
4 | import com.nhaarman.mockito_kotlin.mock
5 | import com.nhaarman.mockito_kotlin.whenever
6 | import io.reactivex.Completable
7 | import io.reactivex.Single
8 | import org.buffer.android.boilerplate.data.model.BufferooEntity
9 | import org.buffer.android.boilerplate.data.repository.BufferooCache
10 | import org.buffer.android.boilerplate.data.test.factory.BufferooFactory
11 | import org.junit.Before
12 | import org.junit.Test
13 | import org.junit.runner.RunWith
14 | import org.junit.runners.JUnit4
15 |
16 | @RunWith(JUnit4::class)
17 | class BufferooCacheDataStoreTest {
18 |
19 | private lateinit var bufferooCacheDataStore: BufferooCacheDataStore
20 |
21 | private lateinit var bufferooCache: BufferooCache
22 |
23 | @Before
24 | fun setUp() {
25 | bufferooCache = mock()
26 | bufferooCacheDataStore = BufferooCacheDataStore(bufferooCache)
27 | }
28 |
29 | //
30 | @Test
31 | fun clearBufferoosCompletes() {
32 | stubBufferooCacheClearBufferoos(Completable.complete())
33 | val testObserver = bufferooCacheDataStore.clearBufferoos().test()
34 | testObserver.assertComplete()
35 | }
36 | //
37 |
38 | //
39 | @Test
40 | fun saveBufferoosCompletes() {
41 | stubBufferooCacheSaveBufferoos(Completable.complete())
42 | val testObserver = bufferooCacheDataStore.saveBufferoos(
43 | BufferooFactory.makeBufferooEntityList(2)).test()
44 | testObserver.assertComplete()
45 | }
46 | //
47 |
48 | //
49 | @Test
50 | fun getBufferoosCompletes() {
51 | stubBufferooCacheGetBufferoos(Single.just(BufferooFactory.makeBufferooEntityList(2)))
52 | val testObserver = bufferooCacheDataStore.getBufferoos().test()
53 | testObserver.assertComplete()
54 | }
55 | //
56 |
57 | //
58 | private fun stubBufferooCacheSaveBufferoos(completable: Completable) {
59 | whenever(bufferooCache.saveBufferoos(any()))
60 | .thenReturn(completable)
61 | }
62 |
63 | private fun stubBufferooCacheGetBufferoos(single: Single>) {
64 | whenever(bufferooCache.getBufferoos())
65 | .thenReturn(single)
66 | }
67 |
68 | private fun stubBufferooCacheClearBufferoos(completable: Completable) {
69 | whenever(bufferooCache.clearBufferoos())
70 | .thenReturn(completable)
71 | }
72 | //
73 |
74 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/browse/BrowseActivityTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.browse
2 |
3 | import android.support.test.espresso.Espresso.onView
4 | import android.support.test.espresso.assertion.ViewAssertions.matches
5 | import android.support.test.espresso.contrib.RecyclerViewActions
6 | import android.support.test.espresso.matcher.ViewMatchers.*
7 | import android.support.test.rule.ActivityTestRule
8 | import android.support.test.runner.AndroidJUnit4
9 | import android.support.v7.widget.RecyclerView
10 | import com.nhaarman.mockito_kotlin.whenever
11 | import io.reactivex.Single
12 | import org.buffer.android.boilerplate.domain.model.Bufferoo
13 | import org.buffer.android.boilerplate.ui.R
14 | import org.buffer.android.boilerplate.ui.test.TestApplication
15 | import org.buffer.android.boilerplate.ui.test.factory.ui.BufferooFactory
16 | import org.buffer.android.boilerplate.ui.test.util.RecyclerViewMatcher
17 | import org.junit.Rule
18 | import org.junit.Test
19 | import org.junit.runner.RunWith
20 |
21 |
22 | @RunWith(AndroidJUnit4::class)
23 | class BrowseActivityTest {
24 |
25 | @Rule @JvmField
26 | val activity = ActivityTestRule(BrowseActivity::class.java, false, false)
27 |
28 | @Test
29 | fun activityLaunches() {
30 | stubBufferooRepositoryGetBufferoos(Single.just(BufferooFactory.makeBufferooList(2)))
31 | activity.launchActivity(null)
32 | }
33 |
34 | @Test
35 | fun bufferoosDisplay() {
36 | val bufferoos = BufferooFactory.makeBufferooList(1)
37 | stubBufferooRepositoryGetBufferoos(Single.just(bufferoos))
38 | activity.launchActivity(null)
39 |
40 | checkBufferooDetailsDisplay(bufferoos[0], 0)
41 | }
42 |
43 | @Test
44 | fun bufferoosAreScrollable() {
45 | val bufferoos = BufferooFactory.makeBufferooList(20)
46 | stubBufferooRepositoryGetBufferoos(Single.just(bufferoos))
47 | activity.launchActivity(null)
48 |
49 | bufferoos.forEachIndexed { index, bufferoo ->
50 | onView(withId(R.id.recycler_browse)).perform(RecyclerViewActions.
51 | scrollToPosition(index))
52 | checkBufferooDetailsDisplay(bufferoo, index) }
53 | }
54 |
55 | private fun checkBufferooDetailsDisplay(bufferoo: Bufferoo, position: Int) {
56 | onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_browse).atPosition(position))
57 | .check(matches(hasDescendant(withText(bufferoo.name))))
58 | onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_browse).atPosition(position))
59 | .check(matches(hasDescendant(withText(bufferoo.title))))
60 | }
61 |
62 | private fun stubBufferooRepositoryGetBufferoos(single: Single>) {
63 | whenever(TestApplication.appComponent().bufferooRepository().getBufferoos())
64 | .thenReturn(single)
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/source/BufferooDataStoreFactoryTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data.source
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import com.nhaarman.mockito_kotlin.whenever
5 | import org.buffer.android.boilerplate.data.repository.BufferooCache
6 | import org.junit.Before
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import org.junit.runners.JUnit4
10 |
11 | @RunWith(JUnit4::class)
12 | class BufferooDataStoreFactoryTest {
13 |
14 | private lateinit var bufferooDataStoreFactory: BufferooDataStoreFactory
15 |
16 | private lateinit var bufferooCache: BufferooCache
17 | private lateinit var bufferooCacheDataStore: BufferooCacheDataStore
18 | private lateinit var bufferooRemoteDataStore: BufferooRemoteDataStore
19 |
20 | @Before
21 | fun setUp() {
22 | bufferooCache = mock()
23 | bufferooCacheDataStore = mock()
24 | bufferooRemoteDataStore = mock()
25 | bufferooDataStoreFactory = BufferooDataStoreFactory(bufferooCache,
26 | bufferooCacheDataStore, bufferooRemoteDataStore)
27 | }
28 |
29 | //
30 | @Test
31 | fun retrieveDataStoreWhenNotCachedReturnsRemoteDataStore() {
32 | stubBufferooCacheIsCached(false)
33 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore()
34 | assert(bufferooDataStore is BufferooRemoteDataStore)
35 | }
36 |
37 | @Test
38 | fun retrieveDataStoreWhenCacheExpiredReturnsRemoteDataStore() {
39 | stubBufferooCacheIsCached(true)
40 | stubBufferooCacheIsExpired(true)
41 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore()
42 | assert(bufferooDataStore is BufferooRemoteDataStore)
43 | }
44 |
45 | @Test
46 | fun retrieveDataStoreReturnsCacheDataStore() {
47 | stubBufferooCacheIsCached(true)
48 | stubBufferooCacheIsExpired(false)
49 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore()
50 | assert(bufferooDataStore is BufferooCacheDataStore)
51 | }
52 | //
53 |
54 | //
55 | @Test
56 | fun retrieveRemoteDataStoreReturnsRemoteDataStore() {
57 | val bufferooDataStore = bufferooDataStoreFactory.retrieveRemoteDataStore()
58 | assert(bufferooDataStore is BufferooRemoteDataStore)
59 | }
60 | //
61 |
62 | //
63 | @Test
64 | fun retrieveCacheDataStoreReturnsCacheDataStore() {
65 | val bufferooDataStore = bufferooDataStoreFactory.retrieveCacheDataStore()
66 | assert(bufferooDataStore is BufferooCacheDataStore)
67 | }
68 | //
69 |
70 | //
71 | private fun stubBufferooCacheIsCached(isCached: Boolean) {
72 | whenever(bufferooCache.isCached())
73 | .thenReturn(isCached)
74 | }
75 |
76 | private fun stubBufferooCacheIsExpired(isExpired: Boolean) {
77 | whenever(bufferooCache.isExpired())
78 | .thenReturn(isExpired)
79 | }
80 | //
81 |
82 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import dagger.Module
6 | import dagger.Provides
7 | import org.buffer.android.boilerplate.cache.BufferooCacheImpl
8 | import org.buffer.android.boilerplate.cache.PreferencesHelper
9 | import org.buffer.android.boilerplate.cache.db.DbOpenHelper
10 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
11 | import org.buffer.android.boilerplate.data.BufferooDataRepository
12 | import org.buffer.android.boilerplate.data.executor.JobExecutor
13 | import org.buffer.android.boilerplate.data.mapper.BufferooMapper
14 | import org.buffer.android.boilerplate.data.repository.BufferooCache
15 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
16 | import org.buffer.android.boilerplate.data.source.BufferooDataStoreFactory
17 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
18 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
19 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
20 | import org.buffer.android.boilerplate.remote.BufferooRemoteImpl
21 | import org.buffer.android.boilerplate.remote.BufferooService
22 | import org.buffer.android.boilerplate.remote.BufferooServiceFactory
23 | import org.buffer.android.boilerplate.ui.BuildConfig
24 | import org.buffer.android.boilerplate.ui.UiThread
25 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
26 |
27 | /**
28 | * Module used to provide dependencies at an application-level.
29 | */
30 | @Module
31 | open class ApplicationModule {
32 |
33 | @Provides
34 | @PerApplication
35 | fun provideContext(application: Application): Context {
36 | return application
37 | }
38 |
39 | @Provides
40 | @PerApplication
41 | internal fun providePreferencesHelper(context: Context): PreferencesHelper {
42 | return PreferencesHelper(context)
43 | }
44 |
45 |
46 | @Provides
47 | @PerApplication
48 | internal fun provideBufferooRepository(factory: BufferooDataStoreFactory,
49 | mapper: BufferooMapper): BufferooRepository {
50 | return BufferooDataRepository(factory, mapper)
51 | }
52 |
53 | @Provides
54 | @PerApplication
55 | internal fun provideBufferooCache(factory: DbOpenHelper,
56 | entityMapper: BufferooEntityMapper,
57 | mapper: org.buffer.android.boilerplate.cache.db.mapper.BufferooMapper,
58 | helper: PreferencesHelper): BufferooCache {
59 | return BufferooCacheImpl(factory, entityMapper, mapper, helper)
60 | }
61 |
62 | @Provides
63 | @PerApplication
64 | internal fun provideBufferooRemote(service: BufferooService,
65 | factory: org.buffer.android.boilerplate.remote.mapper.BufferooEntityMapper): BufferooRemote {
66 | return BufferooRemoteImpl(service, factory)
67 | }
68 |
69 | @Provides
70 | @PerApplication
71 | internal fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor {
72 | return jobExecutor
73 | }
74 |
75 | @Provides
76 | @PerApplication
77 | internal fun providePostExecutionThread(uiThread: UiThread): PostExecutionThread {
78 | return uiThread
79 | }
80 |
81 | @Provides
82 | @PerApplication
83 | internal fun provideBufferooService(): BufferooService {
84 | return BufferooServiceFactory.makeBuffeoorService(BuildConfig.DEBUG)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/BufferooCacheImplTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache
2 |
3 | import org.buffer.android.boilerplate.cache.db.Db
4 | import org.buffer.android.boilerplate.cache.db.DbOpenHelper
5 | import org.buffer.android.boilerplate.cache.db.mapper.BufferooMapper
6 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
7 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
8 | import org.buffer.android.boilerplate.cache.test.factory.BufferooFactory
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.robolectric.RobolectricTestRunner
13 | import org.robolectric.RuntimeEnvironment
14 | import org.robolectric.annotation.Config
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(RobolectricTestRunner::class)
18 | @Config(sdk = intArrayOf(21))
19 | class BufferooCacheImplTest {
20 |
21 | private var entityMapper = BufferooEntityMapper()
22 | private var mapper = BufferooMapper()
23 | private var preferencesHelper = PreferencesHelper(RuntimeEnvironment.application)
24 |
25 | private val databaseHelper = BufferooCacheImpl(DbOpenHelper(RuntimeEnvironment.application),
26 | entityMapper, mapper, preferencesHelper)
27 |
28 | @Before
29 | fun setup() {
30 | databaseHelper.getDatabase().rawQuery("DELETE FROM " + Db.BufferooTable.TABLE_NAME, null)
31 | }
32 |
33 | @Test
34 | fun clearTablesCompletes() {
35 | val testObserver = databaseHelper.clearBufferoos().test()
36 | testObserver.assertComplete()
37 | }
38 |
39 | //
40 | @Test
41 | fun saveBufferoosCompletes() {
42 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
43 |
44 | val testObserver = databaseHelper.saveBufferoos(bufferooEntities).test()
45 | testObserver.assertComplete()
46 | }
47 |
48 | @Test
49 | fun saveBufferoosSavesData() {
50 | val bufferooCount = 2
51 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(bufferooCount)
52 |
53 | databaseHelper.saveBufferoos(bufferooEntities).test()
54 | checkNumRowsInBufferoosTable(bufferooCount)
55 | }
56 | //
57 |
58 | //
59 | @Test
60 | fun getBufferoosCompletes() {
61 | val testObserver = databaseHelper.getBufferoos().test()
62 | testObserver.assertComplete()
63 | }
64 |
65 | @Test
66 | fun getBufferoosReturnsData() {
67 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
68 | val cachedBufferoos = mutableListOf()
69 | bufferooEntities.forEach {
70 | cachedBufferoos.add(entityMapper.mapToCached(it))
71 | }
72 | insertBufferoos(cachedBufferoos)
73 |
74 | val testObserver = databaseHelper.getBufferoos().test()
75 | testObserver.assertValue(bufferooEntities)
76 | }
77 | //
78 |
79 | private fun insertBufferoos(cachedBufferoos: List) {
80 | val database = databaseHelper.getDatabase()
81 | cachedBufferoos.forEach {
82 | database.insertOrThrow(Db.BufferooTable.TABLE_NAME, null,
83 | mapper.toContentValues(it))
84 | }
85 | }
86 |
87 | private fun checkNumRowsInBufferoosTable(expectedRows: Int) {
88 | val bufferoosCursor = databaseHelper.getDatabase().query(Db.BufferooTable.TABLE_NAME,
89 | null, null, null, null, null, null)
90 | bufferoosCursor.moveToFirst()
91 | val numberOfRows = bufferoosCursor.count
92 | bufferoosCursor.close()
93 | assertEquals(expectedRows, numberOfRows)
94 | }
95 |
96 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
22 |
27 |
32 |
37 |
42 |
47 |
52 |
57 |
62 |
67 |
72 |
77 |
82 |
87 |
92 |
97 |
102 |
107 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/presentation/src/test/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosPresenterTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.browse
2 |
3 | import com.nhaarman.mockito_kotlin.*
4 | import io.reactivex.observers.DisposableSingleObserver
5 | import org.buffer.android.boilerplate.domain.interactor.browse.GetBufferoos
6 | import org.buffer.android.boilerplate.domain.model.Bufferoo
7 | import org.buffer.android.boilerplate.presentation.mapper.BufferooMapper
8 | import org.buffer.android.boilerplate.presentation.test.factory.BufferooFactory
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.junit.runners.JUnit4
13 |
14 | @RunWith(JUnit4::class)
15 | class BrowseBufferoosPresenterTest {
16 |
17 | private lateinit var mockBrowseBufferoosView: BrowseBufferoosContract.View
18 | private lateinit var mockBufferooMapper: BufferooMapper
19 | private lateinit var mockGetBufferoos: GetBufferoos
20 |
21 | private lateinit var browseBufferoosPresenter: BrowseBufferoosPresenter
22 | private lateinit var captor: KArgumentCaptor>>
23 |
24 | @Before
25 | fun setup() {
26 | captor = argumentCaptor>>()
27 | mockBrowseBufferoosView = mock()
28 | mockBufferooMapper = mock()
29 | mockGetBufferoos = mock()
30 | browseBufferoosPresenter = BrowseBufferoosPresenter(mockBrowseBufferoosView,
31 | mockGetBufferoos, mockBufferooMapper)
32 | }
33 |
34 | //
35 | @Test
36 | fun retrieveBufferoosHidesErrorState() {
37 | browseBufferoosPresenter.retrieveBufferoos()
38 |
39 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
40 | captor.firstValue.onSuccess(BufferooFactory.makeBufferooList(2))
41 | verify(mockBrowseBufferoosView).hideErrorState()
42 | }
43 |
44 | @Test
45 | fun retrieveBufferoosHidesEmptyState() {
46 | browseBufferoosPresenter.retrieveBufferoos()
47 |
48 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
49 | captor.firstValue.onSuccess(BufferooFactory.makeBufferooList(2))
50 | verify(mockBrowseBufferoosView).hideEmptyState()
51 | }
52 |
53 | @Test
54 | fun retrieveBufferoosShowsBufferoos() {
55 | val bufferoos = BufferooFactory.makeBufferooList(2)
56 | browseBufferoosPresenter.retrieveBufferoos()
57 |
58 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
59 | captor.firstValue.onSuccess(bufferoos)
60 | verify(mockBrowseBufferoosView).showBufferoos(
61 | bufferoos.map { mockBufferooMapper.mapToView(it) })
62 | }
63 |
64 | @Test
65 | fun retrieveBufferoosShowsEmptyState() {
66 | browseBufferoosPresenter.retrieveBufferoos()
67 |
68 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
69 | captor.firstValue.onSuccess(BufferooFactory.makeBufferooList(0))
70 | verify(mockBrowseBufferoosView).showEmptyState()
71 | }
72 |
73 | @Test
74 | fun retrieveBufferoosHidesBufferoos() {
75 | browseBufferoosPresenter.retrieveBufferoos()
76 |
77 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
78 | captor.firstValue.onSuccess(BufferooFactory.makeBufferooList(0))
79 | verify(mockBrowseBufferoosView).hideBufferoos()
80 | }
81 |
82 | @Test
83 | fun retrieveBufferoosShowsErrorState() {
84 | browseBufferoosPresenter.retrieveBufferoos()
85 |
86 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
87 | captor.firstValue.onError(RuntimeException())
88 | verify(mockBrowseBufferoosView).showErrorState()
89 | }
90 |
91 | @Test
92 | fun retrieveBufferoosHidesBufferoosWhenErrorThrown() {
93 | browseBufferoosPresenter.retrieveBufferoos()
94 |
95 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
96 | captor.firstValue.onError(RuntimeException())
97 | verify(mockBrowseBufferoosView).hideBufferoos()
98 | }
99 |
100 | @Test
101 | fun retrieveBufferoosHidesEmptyStateWhenErrorThrown() {
102 | browseBufferoosPresenter.retrieveBufferoos()
103 |
104 | verify(mockGetBufferoos).execute(captor.capture(), eq(null))
105 | captor.firstValue.onError(RuntimeException())
106 | verify(mockBrowseBufferoosView).hideEmptyState()
107 | }
108 | //
109 |
110 | }
--------------------------------------------------------------------------------
/mobile-ui/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 | apply plugin: 'jacoco-android'
6 |
7 | android {
8 | def globalConfiguration = rootProject.extensions.getByName("ext")
9 |
10 | compileSdkVersion globalConfiguration["androidCompileSdkVersion"]
11 | buildToolsVersion globalConfiguration["androidBuildToolsVersion"]
12 |
13 | defaultConfig {
14 | minSdkVersion globalConfiguration["androidMinSdkVersion"]
15 | targetSdkVersion globalConfiguration["androidTargetSdkVersion"]
16 | multiDexEnabled = true
17 | testInstrumentationRunner "org.buffer.android.boilerplate.ui.test.TestRunner"
18 | }
19 |
20 | buildTypes {
21 | debug {
22 | testCoverageEnabled true
23 | }
24 | }
25 |
26 | dexOptions {
27 | preDexLibraries = false
28 | dexInProcess = false
29 | javaMaxHeapSize "4g"
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | packagingOptions {
38 | exclude 'LICENSE.txt'
39 | exclude 'META-INF/DEPENDENCIES'
40 | exclude 'META-INF/ASL2.0'
41 | exclude 'META-INF/NOTICE'
42 | exclude 'META-INF/LICENSE'
43 | }
44 |
45 | lintOptions {
46 | quiet true
47 | abortOnError false
48 | ignoreWarnings true
49 | }
50 |
51 | }
52 |
53 | kapt {
54 | correctErrorTypes = true
55 | }
56 |
57 | configurations.all {
58 | resolutionStrategy {
59 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
60 | }
61 | }
62 |
63 | dependencies {
64 | def mobileUiDependencies = rootProject.ext.mobileUiDependencies
65 | def mobileUiTestDependencies = rootProject.ext.mobileUiTestDependencies
66 |
67 | implementation project(':presentation')
68 | implementation project(':data')
69 | implementation project(':cache')
70 | implementation project(':remote')
71 |
72 | implementation mobileUiDependencies.javaxAnnotation
73 |
74 | implementation mobileUiDependencies.kotlin
75 | implementation mobileUiDependencies.javaxInject
76 | implementation mobileUiDependencies.rxKotlin
77 | implementation mobileUiDependencies.androidAnnotations
78 | implementation mobileUiDependencies.androidSupportV4
79 | implementation mobileUiDependencies.androidSupportV13
80 | implementation mobileUiDependencies.appCompatV7
81 | implementation mobileUiDependencies.supportRecyclerView
82 | implementation mobileUiDependencies.supportDesign
83 | implementation mobileUiDependencies.timber
84 | implementation mobileUiDependencies.rxAndroid
85 | implementation mobileUiDependencies.glide
86 | implementation mobileUiDependencies.dagger
87 | implementation mobileUiDependencies.daggerSupport
88 |
89 | testImplementation mobileUiTestDependencies.kotlinJUnit
90 |
91 | kapt mobileUiDependencies.daggerCompiler
92 | kapt mobileUiDependencies.daggerProcessor
93 | compileOnly mobileUiDependencies.glassfishAnnotation
94 |
95 | // Instrumentation test dependencies
96 | androidTestImplementation mobileUiTestDependencies.junit
97 | androidTestImplementation mobileUiTestDependencies.mockito
98 | androidTestImplementation mobileUiTestDependencies.mockitoAndroid
99 | androidTestImplementation (mobileUiTestDependencies.espressoCore) {
100 | exclude group: 'com.android.support', module: 'support-annotations'
101 | }
102 | androidTestImplementation (mobileUiTestDependencies.androidRunner) {
103 | exclude group: 'com.android.support', module: 'support-annotations'
104 | }
105 | androidTestImplementation (mobileUiTestDependencies.androidRules) {
106 | exclude group: 'com.android.support', module: 'support-annotations'
107 | }
108 | androidTestImplementation (mobileUiTestDependencies.espressoIntents) {
109 | exclude group: 'com.android.support', module: 'support-annotations'
110 | }
111 | androidTestImplementation(mobileUiTestDependencies.espressoContrib) {
112 | exclude module: 'appcompat'
113 | exclude module: 'appcompat-v7'
114 | exclude module: 'support-v4'
115 | exclude module: 'support-v13'
116 | exclude module: 'support-annotations'
117 | exclude module: 'recyclerview-v7'
118 | exclude module: 'design'
119 | }
120 |
121 | kaptTest mobileUiDependencies.daggerCompiler
122 | kaptAndroidTest mobileUiDependencies.daggerCompiler
123 | }
124 |
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/BufferooCacheImpl.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache
2 |
3 | import android.database.sqlite.SQLiteDatabase
4 | import io.reactivex.Completable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.cache.db.Db
7 | import org.buffer.android.boilerplate.cache.db.DbOpenHelper
8 | import org.buffer.android.boilerplate.cache.db.constants.BufferooConstants
9 | import org.buffer.android.boilerplate.cache.db.mapper.BufferooMapper
10 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
11 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
12 | import org.buffer.android.boilerplate.data.model.BufferooEntity
13 | import org.buffer.android.boilerplate.data.repository.BufferooCache
14 | import javax.inject.Inject
15 |
16 | /**
17 | * Cached implementation for retrieving and saving Bufferoo instances. This class implements the
18 | * [BufferooCache] from the Data layer as it is that layers responsibility for defining the
19 | * operations in which data store implementation layers can carry out.
20 | */
21 | class BufferooCacheImpl @Inject constructor(dbOpenHelper: DbOpenHelper,
22 | private val entityMapper: BufferooEntityMapper,
23 | private val mapper: BufferooMapper,
24 | private val preferencesHelper: PreferencesHelper):
25 | BufferooCache {
26 |
27 | private val EXPIRATION_TIME = (60 * 10 * 1000).toLong()
28 |
29 | private var database: SQLiteDatabase = dbOpenHelper.writableDatabase
30 |
31 | /**
32 | * Retrieve an instance from the database, used for tests
33 | */
34 | internal fun getDatabase(): SQLiteDatabase {
35 | return database
36 | }
37 |
38 | /**
39 | * Remove all the data from all the tables in the database.
40 | */
41 | override fun clearBufferoos(): Completable {
42 | return Completable.defer {
43 | database.beginTransaction()
44 | try {
45 | database.delete(Db.BufferooTable.TABLE_NAME, null, null)
46 | database.setTransactionSuccessful()
47 | } finally {
48 | database.endTransaction()
49 | }
50 | Completable.complete()
51 | }
52 | }
53 |
54 | /**
55 | * Save the given list of [BufferooEntity] instances to the database.
56 | */
57 | override fun saveBufferoos(bufferoos: List): Completable {
58 | return Completable.defer {
59 | database.beginTransaction()
60 | try {
61 | bufferoos.forEach {
62 | saveBufferoo(entityMapper.mapToCached(it))
63 | }
64 | database.setTransactionSuccessful()
65 | } finally {
66 | database.endTransaction()
67 | }
68 | Completable.complete()
69 | }
70 | }
71 |
72 | /**
73 | * Retrieve a list of [BufferooEntity] instances from the database.
74 | */
75 | override fun getBufferoos(): Single> {
76 | return Single.defer> {
77 | val updatesCursor = database.rawQuery(BufferooConstants.QUERY_GET_ALL_BUFFEROOS, null)
78 | val bufferoos = mutableListOf()
79 |
80 | while (updatesCursor.moveToNext()) {
81 | val cachedBufferoo = mapper.parseCursor(updatesCursor)
82 | bufferoos.add(entityMapper.mapFromCached(cachedBufferoo))
83 | }
84 |
85 | updatesCursor.close()
86 | Single.just>(bufferoos)
87 | }
88 | }
89 |
90 | /**
91 | * Helper method for saving a [CachedBufferoo] instance to the database.
92 | */
93 | private fun saveBufferoo(cachedBufferoo: CachedBufferoo) {
94 | database.insert(Db.BufferooTable.TABLE_NAME, null, mapper.toContentValues(cachedBufferoo))
95 | }
96 |
97 | /**
98 | * Checked whether there are instances of [CachedBufferoo] stored in the cache
99 | */
100 | override fun isCached(): Boolean {
101 | return database.rawQuery(BufferooConstants.QUERY_GET_ALL_BUFFEROOS, null).count > 0
102 | }
103 |
104 | /**
105 | * Set a point in time at when the cache was last updated
106 | */
107 | override fun setLastCacheTime(lastCache: Long) {
108 | preferencesHelper.lastCacheTime = lastCache
109 | }
110 |
111 | /**
112 | * Check whether the current cached data exceeds the defined [EXPIRATION_TIME] time
113 | */
114 | override fun isExpired(): Boolean {
115 | val currentTime = System.currentTimeMillis()
116 | val lastUpdateTime = this.getLastCacheUpdateTimeMillis()
117 | return currentTime - lastUpdateTime > EXPIRATION_TIME
118 | }
119 |
120 | /**
121 | * Get in millis, the last time the cache was accessed.
122 | */
123 | private fun getLastCacheUpdateTimeMillis(): Long {
124 | return preferencesHelper.lastCacheTime
125 | }
126 |
127 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/data/src/test/java/org/buffer/android/boilerplate/data/BufferooDataRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data
2 |
3 | import com.nhaarman.mockito_kotlin.*
4 | import io.reactivex.Completable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.mapper.BufferooMapper
7 | import org.buffer.android.boilerplate.data.model.BufferooEntity
8 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
9 | import org.buffer.android.boilerplate.data.source.BufferooCacheDataStore
10 | import org.buffer.android.boilerplate.data.source.BufferooDataStoreFactory
11 | import org.buffer.android.boilerplate.data.source.BufferooRemoteDataStore
12 | import org.buffer.android.boilerplate.data.test.factory.BufferooFactory
13 | import org.buffer.android.boilerplate.domain.model.Bufferoo
14 | import org.junit.Before
15 | import org.junit.Test
16 | import org.junit.runner.RunWith
17 | import org.junit.runners.JUnit4
18 |
19 | @RunWith(JUnit4::class)
20 | class BufferooDataRepositoryTest {
21 |
22 | private lateinit var bufferooDataRepository: BufferooDataRepository
23 |
24 | private lateinit var bufferooDataStoreFactory: BufferooDataStoreFactory
25 | private lateinit var bufferooMapper: BufferooMapper
26 | private lateinit var bufferooCacheDataStore: BufferooCacheDataStore
27 | private lateinit var bufferooRemoteDataStore: BufferooRemoteDataStore
28 |
29 | @Before
30 | fun setUp() {
31 | bufferooDataStoreFactory = mock()
32 | bufferooMapper = mock()
33 | bufferooCacheDataStore = mock()
34 | bufferooRemoteDataStore = mock()
35 | bufferooDataRepository = BufferooDataRepository(bufferooDataStoreFactory, bufferooMapper)
36 | stubBufferooDataStoreFactoryRetrieveCacheDataStore()
37 | stubBufferooDataStoreFactoryRetrieveRemoteDataStore()
38 | }
39 |
40 | //
41 | @Test
42 | fun clearBufferoosCompletes() {
43 | stubBufferooCacheClearBufferoos(Completable.complete())
44 | val testObserver = bufferooDataRepository.clearBufferoos().test()
45 | testObserver.assertComplete()
46 | }
47 |
48 | @Test
49 | fun clearBufferoosCallsCacheDataStore() {
50 | stubBufferooCacheClearBufferoos(Completable.complete())
51 | bufferooDataRepository.clearBufferoos().test()
52 | verify(bufferooCacheDataStore).clearBufferoos()
53 | }
54 |
55 | @Test
56 | fun clearBufferoosNeverCallsRemoteDataStore() {
57 | stubBufferooCacheClearBufferoos(Completable.complete())
58 | bufferooDataRepository.clearBufferoos().test()
59 | verify(bufferooRemoteDataStore, never()).clearBufferoos()
60 | }
61 | //
62 |
63 | //
64 | @Test
65 | fun saveBufferoosCompletes() {
66 | stubBufferooCacheSaveBufferoos(Completable.complete())
67 | val testObserver = bufferooDataRepository.saveBufferoos(
68 | BufferooFactory.makeBufferooList(2)).test()
69 | testObserver.assertComplete()
70 | }
71 |
72 | @Test
73 | fun saveBufferoosCallsCacheDataStore() {
74 | stubBufferooCacheSaveBufferoos(Completable.complete())
75 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
76 | verify(bufferooCacheDataStore).saveBufferoos(any())
77 | }
78 |
79 | @Test
80 | fun saveBufferoosNeverCallsRemoteDataStore() {
81 | stubBufferooCacheSaveBufferoos(Completable.complete())
82 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
83 | verify(bufferooRemoteDataStore, never()).saveBufferoos(any())
84 | }
85 | //
86 |
87 | //
88 | @Test
89 | fun getBufferoosCompletes() {
90 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
91 | stubBufferooCacheDataStoreGetBufferoos(Single.just(
92 | BufferooFactory.makeBufferooEntityList(2)))
93 | val testObserver = bufferooDataRepository.getBufferoos().test()
94 | testObserver.assertComplete()
95 | }
96 |
97 | @Test
98 | fun getBufferoosReturnsData() {
99 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
100 | val bufferoos = BufferooFactory.makeBufferooList(2)
101 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
102 | bufferoos.forEachIndexed { index, bufferoo ->
103 | stubBufferooMapperMapFromEntity(bufferooEntities[index], bufferoo) }
104 | stubBufferooCacheDataStoreGetBufferoos(Single.just(bufferooEntities))
105 |
106 | val testObserver = bufferooDataRepository.getBufferoos().test()
107 | testObserver.assertValue(bufferoos)
108 | }
109 |
110 | @Test
111 | fun getBufferoosSavesBufferoosWhenFromCacheDataStore() {
112 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
113 | stubBufferooCacheSaveBufferoos(Completable.complete())
114 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
115 | verify(bufferooCacheDataStore).saveBufferoos(any())
116 | }
117 |
118 | @Test
119 | fun getBufferoosNeverSavesBufferoosWhenFromRemoteDataStore() {
120 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooRemoteDataStore)
121 | stubBufferooCacheSaveBufferoos(Completable.complete())
122 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
123 | verify(bufferooRemoteDataStore, never()).saveBufferoos(any())
124 | }
125 | //
126 |
127 | //
128 | private fun stubBufferooCacheSaveBufferoos(completable: Completable) {
129 | whenever(bufferooCacheDataStore.saveBufferoos(any()))
130 | .thenReturn(completable)
131 | }
132 |
133 | private fun stubBufferooCacheDataStoreGetBufferoos(single: Single>) {
134 | whenever(bufferooCacheDataStore.getBufferoos())
135 | .thenReturn(single)
136 | }
137 |
138 | private fun stubBufferooRemoteDataStoreGetBufferoos(single: Single>) {
139 | whenever(bufferooRemoteDataStore.getBufferoos())
140 | .thenReturn(single)
141 | }
142 |
143 | private fun stubBufferooCacheClearBufferoos(completable: Completable) {
144 | whenever(bufferooCacheDataStore.clearBufferoos())
145 | .thenReturn(completable)
146 | }
147 |
148 | private fun stubBufferooDataStoreFactoryRetrieveCacheDataStore() {
149 | whenever(bufferooDataStoreFactory.retrieveCacheDataStore())
150 | .thenReturn(bufferooCacheDataStore)
151 | }
152 |
153 | private fun stubBufferooDataStoreFactoryRetrieveRemoteDataStore() {
154 | whenever(bufferooDataStoreFactory.retrieveRemoteDataStore())
155 | .thenReturn(bufferooCacheDataStore)
156 | }
157 |
158 | private fun stubBufferooDataStoreFactoryRetrieveDataStore(dataStore: BufferooDataStore) {
159 | whenever(bufferooDataStoreFactory.retrieveDataStore())
160 | .thenReturn(dataStore)
161 | }
162 |
163 | private fun stubBufferooMapperMapFromEntity(bufferooEntity: BufferooEntity,
164 | bufferoo: Bufferoo) {
165 | whenever(bufferooMapper.mapFromEntity(bufferooEntity))
166 | .thenReturn(bufferoo)
167 | }
168 | //
169 |
170 | }
--------------------------------------------------------------------------------
/dependencies.gradle:
--------------------------------------------------------------------------------
1 |
2 | allprojects {
3 | repositories {
4 | jcenter()
5 | }
6 | }
7 |
8 | ext {
9 | //Android
10 | androidBuildToolsVersion = "26.0.0"
11 | androidMinSdkVersion = 15
12 | androidTargetSdkVersion = 26
13 | androidCompileSdkVersion = 26
14 |
15 | //Libraries
16 | kotlinVersion = '1.1.3-2'
17 | butterKnifeVersion = '7.0.1'
18 | recyclerViewVersion = '21.0.3'
19 | rxJavaVersion = '2.0.2'
20 | rxKotlinVersion = '2.1.0'
21 | rxAndroidVersion = '2.0.1'
22 | javaxAnnotationVersion = '1.0'
23 | javaxInjectVersion = '1'
24 | gsonVersion = '2.8.1'
25 | okHttpVersion = '3.8.1'
26 | androidAnnotationsVersion = '21.0.3'
27 | retrofitVersion = '2.3.0'
28 | roomVersion = '1.0.0-alpha6'
29 | supportLibraryVersion = '26.0.1'
30 | timberVersion = '4.5.1'
31 | glideVersion = '4.0.0'
32 | daggerVersion = '2.11'
33 | glassfishAnnotationVersion = '10.0-b28'
34 |
35 | //Testing
36 | robolectricVersion = '3.4.2'
37 | jUnitVersion = '4.12'
38 | assertJVersion = '3.8.0'
39 | mockitoVersion = '1.9.5'
40 | dexmakerVersion = '1.0'
41 | espressoVersion = '3.0.0'
42 | testingSupportLibVersion = '0.1'
43 | mockitoKotlinVersion = '1.5.0'
44 | mockitoAndroidVersion = '2.8.47'
45 | androidSupportRunnerVersion = '1.0.0'
46 | androidSupportRulesVersion = '1.0.0'
47 | dexmakerMockitoversion = '2.2.0'
48 | runnerVersion = '0.5'
49 |
50 | domainDependencies = [
51 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
52 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
53 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
54 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}"
55 | ]
56 |
57 | domainTestDependencies = [
58 | junit: "junit:junit:${jUnitVersion}",
59 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
60 | assertj: "org.assertj:assertj-core:${assertJVersion}"
61 | ]
62 |
63 | presentationDependencies = [
64 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
65 | dagger: "com.google.dagger:dagger:${daggerVersion}",
66 | okHttp: "com.squareup.okhttp3:okhttp:${okHttpVersion}",
67 | okHttpLogger: "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
68 | gson: "com.google.code.gson:gson:${gsonVersion}",
69 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
70 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
71 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
72 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
73 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
74 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
75 | retrofit: "com.squareup.retrofit2:retrofit:${retrofitVersion}",
76 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
77 | retrofitAdapter: "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
78 | ]
79 |
80 | presentationTestDependencies = [
81 | junit: "junit:junit:${jUnitVersion}",
82 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
83 | assertj: "org.assertj:assertj-core:${assertJVersion}",
84 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
85 | robolectric: "org.robolectric:robolectric:${robolectricVersion}"
86 | ]
87 |
88 | dataDependencies = [
89 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
90 | dagger: "com.google.dagger:dagger:${daggerVersion}",
91 | okHttp: "com.squareup.okhttp3:okhttp:${okHttpVersion}",
92 | okHttpLogger: "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
93 | gson: "com.google.code.gson:gson:${gsonVersion}",
94 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
95 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
96 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
97 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
98 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
99 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
100 | retrofit: "com.squareup.retrofit2:retrofit:${retrofitVersion}",
101 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
102 | retrofitAdapter: "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
103 | ]
104 |
105 | dataTestDependencies = [
106 | junit: "junit:junit:${jUnitVersion}",
107 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
108 | assertj: "org.assertj:assertj-core:${assertJVersion}",
109 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
110 | robolectric: "org.robolectric:robolectric:${robolectricVersion}"
111 | ]
112 |
113 | cacheDependencies = [
114 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
115 | dagger: "com.google.dagger:dagger:${daggerVersion}",
116 | gson: "com.google.code.gson:gson:${gsonVersion}",
117 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
118 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
119 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
120 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
121 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
122 | roomRuntime: "android.arch.persistence.room:runtime:${roomVersion}",
123 | roomCompiler: "android.arch.persistence.room:compiler:${roomVersion}",
124 | roomRxJava: "android.arch.persistence.room:rxjava2:${roomVersion}",
125 | ]
126 |
127 | cacheTestDependencies = [
128 | junit: "junit:junit:${jUnitVersion}",
129 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
130 | assertj: "org.assertj:assertj-core:${assertJVersion}",
131 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
132 | robolectric: "org.robolectric:robolectric:${robolectricVersion}",
133 | roomTesting: "android.arch.persistence.room:testing:${roomVersion}",
134 | archTesting: "android.arch.core:core-testing:${roomVersion}",
135 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
136 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}"
137 | ]
138 |
139 | remoteDependencies = [
140 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
141 | dagger: "com.google.dagger:dagger:${daggerVersion}",
142 | gson: "com.google.code.gson:gson:${gsonVersion}",
143 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
144 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
145 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
146 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
147 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
148 | okHttp: "com.squareup.okhttp3:okhttp:${okHttpVersion}",
149 | okHttpLogger: "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
150 | retrofit: "com.squareup.retrofit2:retrofit:${retrofitVersion}",
151 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
152 | retrofitAdapter: "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
153 | ]
154 |
155 | remoteTestDependencies = [
156 | junit: "junit:junit:${jUnitVersion}",
157 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
158 | assertj: "org.assertj:assertj-core:${assertJVersion}",
159 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
160 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
161 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}"
162 | ]
163 |
164 | mobileUiDependencies = [
165 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
166 | dagger: "com.google.dagger:dagger:${daggerVersion}",
167 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
168 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
169 | glide: "com.github.bumptech.glide:glide:${glideVersion}",
170 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
171 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
172 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
173 | androidAnnotations: "com.android.support:support-annotations:${supportLibraryVersion}",
174 | androidSupportV4: "com.android.support:support-v4:${supportLibraryVersion}",
175 | androidSupportV13: "com.android.support:support-v13:${supportLibraryVersion}",
176 | appCompatV7: "com.android.support:appcompat-v7:${supportLibraryVersion}",
177 | supportRecyclerView:"com.android.support:recyclerview-v7:${supportLibraryVersion}",
178 | supportDesign: "com.android.support:design:${supportLibraryVersion}",
179 | timber: "com.jakewharton.timber:timber:${timberVersion}",
180 | daggerSupport: "com.google.dagger:dagger-android-support:${daggerVersion}",
181 | daggerProcessor: "com.google.dagger:dagger-android-processor:${daggerVersion}",
182 | glassfishAnnotation: "org.glassfish:javax.annotation:${glassfishAnnotationVersion}"
183 | ]
184 |
185 | mobileUiTestDependencies = [
186 | junit: "junit:junit:${jUnitVersion}",
187 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
188 | assertj: "org.assertj:assertj-core:${assertJVersion}",
189 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
190 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
191 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}",
192 | mockitoAndroid: "org.mockito:mockito-android:${mockitoAndroidVersion}",
193 | espressoCore: "com.android.support.test.espresso:espresso-core:${espressoVersion}",
194 | espressoIntents: "com.android.support.test.espresso:espresso-intents:${espressoVersion}",
195 | espressoContrib: "com.android.support.test.espresso:espresso-contrib:${espressoVersion}",
196 | androidRunner: "com.android.support.test:runner:${runnerVersion}",
197 | androidRules: "com.android.support.test:rules:${runnerVersion}"
198 | ]
199 |
200 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/bufferapp/android-clean-architecture-boilerplate) [](https://codecov.io/gh/bufferapp/android-clean-architecture-boilerplate) [](https://www.codacy.com/app/hitherejoe/android-clean-architecture-boilerplate?utm_source=github.com&utm_medium=referral&utm_content=bufferapp/android-clean-architecture-boilerplate&utm_campaign=Badge_Grade)
2 |
3 | # Android Clean Architecture Boilerplate
4 |
5 | Welcome 👋 We hope this boilerplate is not only helpful to other developers, but also that it helps to educate in the area of architecture. We created this boilerplate for a few reasons:
6 |
7 | 1. To experiment with modularisation
8 | 2. To share some approaches to clean architecture, especially as we've been [talking a lot about it](https://academy.realm.io/posts/converting-an-app-to-use-clean-architecture/)
9 | 3. To use as a starting point in future projects where clean architecture feels appropriate
10 |
11 | It is written 100% in Kotlin with both UI and Unit tests - we will also be keeping this up-to-date as libraries change!
12 |
13 | ### Disclaimer
14 |
15 | Note: The use of clean architecture may seem over-complicated for this sample project. However, this allows us to keep the amount of boilerplate code to a minimum and also demonstrate the approach in a simpler form.
16 |
17 | Clean Architecture will not be appropriate for every project, so it is down to you to decide whether or not it fits your needs 🙂
18 |
19 | ## Languages, libraries and tools used
20 |
21 | * [Kotlin](https://kotlinlang.org/)
22 | * Android Support Libraries
23 | * [RxJava2](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0)
24 | * [Dagger 2 (2.11)](https://github.com/google/dagger)
25 | * [Glide](https://github.com/bumptech/glide)
26 | * [Retrofit](http://square.github.io/retrofit/)
27 | * [OkHttp](http://square.github.io/okhttp/)
28 | * [Gson](https://github.com/google/gson)
29 | * [Timber](https://github.com/JakeWharton/timber)
30 | * [Mockito](http://site.mockito.org/)
31 | * [Espresso](https://developer.android.com/training/testing/espresso/index.html)
32 | * [Robolectric](http://robolectric.org/)
33 |
34 | ## Requirements
35 |
36 | * JDK 1.8
37 | * [Android SDK](https://developer.android.com/studio/index.html)
38 | * Android O ([API 26](https://developer.android.com/preview/api-overview.html))
39 | * Latest Android SDK Tools and build tools.
40 |
41 | ## Architecture
42 |
43 | The architecture of the project follows the principles of Clean Archicture. Here's how the sample project implements it:
44 |
45 | 
46 |
47 | The sample app when run will show you a simple list of all the Bufferoos (Buffer team members!).
48 |
49 |
50 |
51 |
52 | Let's look at each of the architecture layers and the role each one plays :)
53 |
54 | 
55 |
56 | ### User Interface
57 |
58 | This layer makes use of the Android Framework and is used to create all of our UI components to display inside of the [Browse Activity](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/9a1308c42c0c882fc724a0e579ee1ce4d454f961/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/browse/BrowseActivity.kt). The layer receives its data from the Presentation layer and when retrieved, the received models are mapped using the [Bufferoo Mapper](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/9a1308c42c0c882fc724a0e579ee1ce4d454f961/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/mapper/BufferooMapper.kt) so that the model can be mapped to this layer's interpretation of the Bufferoo instance, which is the [BufferooViewModel](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/9a1308c42c0c882fc724a0e579ee1ce4d454f961/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/model/BufferooViewModel.kt). The Activity makes use of the [BrowseContract](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosContract.kt) to enable communication to and from the presenter
59 |
60 | ### Presentation
61 |
62 | This layer's responsibilty is to handle the presentation of the User Interface, but at the same time knows nothing about the user interface itself. This layer has no dependance on the Android Framework, it is a pure Kotlin module. Each Presenter class that is created implements the [Presenter](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/BasePresenter.kt) interface defined within an instance of a contract - in this case the [BrowseContract](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosContract.kt), which also contains an interface for the [View](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/BaseView.kt) interface.
63 |
64 | When a Presenter is constructed, an instance of this View is passed in. This view is then used and the presenter is set for it using the implemented [setPresenter()](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosPresenter.kt#L15) call.
65 |
66 | The presenters use an instance of a [SingleUseCase](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/SingleUseCase.kt) from the Domain layer to retrieve data. Note here that there is no direct name reference to the UseCase that we are using - we do inject an instance of the [GetBufferoos](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/browse/GetBufferoos.kt) UseCase, however.
67 |
68 | The presenter receives data from the Domain layer in the form of a [Bufferoo](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/model/BufferooView.kt). These instances are mapped to instance of this layers model, which is a BufferooView using the [BufferooMapper](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/mapper/BufferooMapper.kt).
69 |
70 | ### Domain
71 |
72 | The domain layer responsibility is to simply contain the UseCase instance used to retrieve data from the Data layer and pass it onto the Presentation layer. In our case, we define a [GetBufferoos](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/browse/GetBufferoos.kt) - this use case handles the subscribing and observing of our request for data from the BufferooRepository interface. This UseCase extends the [SingleUseCase](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/SingleUseCase.kt) base class - therefore we can reference it from outer layers and avoid a direct reference to a specific implementation.
73 |
74 | The layer defines the [Bufferoo](https://github.com/bufferapp/android-clean-architecture-boilerplate/tree/master/domain/src/main/java/org/buffer/android/boilerplate/domain/model) class but no mapper. This is because the Domain layer is our central layer, it knows nothing of the layers outside of it so has no need to map data to any other type of model.
75 |
76 | The Domain layer defines the [BufferooRepository](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/repository/BufferooRepository.kt) interface which provides a set of methods for an external layer to implement as the UseCase classes use the interface when requesting data.
77 |
78 | 
79 |
80 | ### Data
81 |
82 | The Data layer is our access point to external data layers and is used to fetch data from multiple sources (the cache and network in our case). It contains an implementation of the BufferooRepository, which is the [BufferooDataRepository](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/BufferooDataRepository.kt). To begin with, this class uses the [BufferooDataStoreFactory](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooDataStoreFactory.kt) to decide which data store class will be used when fetching data - this will be either the [BufferooRemoteDataStore](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooRemoteDataStore.kt) or the [BufferooCacheDataStore](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/source/BufferooCacheDataStore.kt) - both of these classes implement the [BufferooDataStore](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooDataStore.kt) repository so that our DataStore classes are enforced.
83 |
84 | Each of these DataStore classes also references a corresponding [BufferooCache](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooCache.kt) and [BufferooRemote](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooRemote.kt) interface, which is used when requesting data from an external data source module.
85 |
86 | This layers data model is the [BufferooEntity](https://github.com/bufferapp/android-clean-architecture-boilerplate/tree/master/data/src/main/java/org/buffer/android/boilerplate/data/model). Here the [BufferooMapper](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/mapper/BufferooMapper.kt) is used to map data to and from a Bufferoo instance from the domain layer and BufferooEntity instance from this layer as required.
87 |
88 | ### Remote
89 |
90 | The Remote layer handles all communications with remote sources, in our case it makes a simple API call using a Retrofit interface. The [BufferooRemoteImpl](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooRemoteImpl.kt) class implements the [BufferooRemote](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/data/src/main/java/org/buffer/android/boilerplate/data/repository/BufferooRemote.kt) interface from the Data layer and uses the [BufferooService](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooService.kt) to retrieve data from the API.
91 |
92 | The API returns us instances of a [BufferooModel](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/remote/src/main/java/org/buffer/android/boilerplate/remote/model/BufferooModel.kt) and these are mapped to BufferooEntity instance from the Data layer using the [BufferooEntityMapper](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/remote/src/main/java/org/buffer/android/boilerplate/remote/mapper/BufferooEntityMapper.kt) class.
93 |
94 | ### Cache
95 |
96 | The Cache layer handles all communication with the local database which is used to cache data.
97 |
98 | The data model for this layer is the [CachedBufferoo](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/cache/src/main/java/org/buffer/android/boilerplate/cache/model/CachedBufferoo.kt) and this is mapped to and from a BufferooEntity instance from the Data layer using the [BufferooEntityMapper](https://github.com/bufferapp/android-clean-architecture-boilerplate/blob/master/cache/src/main/java/org/buffer/android/boilerplate/cache/mapper/BufferooEntityMapper.kt) class.
99 |
100 | ## Conclusion
101 |
102 | We will be happy to answer any questions that you may have on this approach, and if you want to lend a hand with the boilerplate then please feel free to submit an issue and/or pull request 🙂
103 |
104 | Again to note, use Clean Architecture where appropriate. This is example can appear as over-architectured for what it is - but it is an example only. The same can be said for individual models for each layer, this decision is down to you. In this example, the data used for ever model is exactly the same, so some may argue that "hey, maybe we don't need to map between the presentation and user-interface layer". Or maybe you don't want to modularise your data layer into data/remote/cache and want to just have it in a single 'data' module. That decision is down to you and the the project that you are working on 🙌🏻
105 |
106 | ## Thanks
107 |
108 | A special thanks to the authors involved with these two repositories, they were a great resource during our learning!
109 |
110 | - https://github.com/android10/Android-CleanArchitecture
111 |
112 | - https://github.com/googlesamples/android-architecture
113 |
--------------------------------------------------------------------------------