├── .gitignore
├── .travis.yml
├── art
├── architecture.png
├── data.png
├── device_screenshot.png
└── ui.png
├── build.gradle
├── cache
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── cache
│ │ ├── BufferooCacheImpl.kt
│ │ ├── PreferencesHelper.kt
│ │ ├── dao
│ │ └── CachedBufferooDao.kt
│ │ ├── db
│ │ ├── BufferoosDatabase.kt
│ │ └── constants
│ │ │ └── BufferooConstants.kt
│ │ ├── mapper
│ │ ├── BufferooEntityMapper.kt
│ │ └── EntityMapper.kt
│ │ └── model
│ │ └── CachedBufferoo.kt
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── cache
│ ├── BufferooCacheImplTest.kt
│ ├── dao
│ └── CachedBufferooDaoTest.kt
│ ├── mapper
│ └── BufferooEntityMapperTest.kt
│ └── test
│ ├── DefaultConfig.kt
│ └── factory
│ ├── BufferooFactory.kt
│ └── DataFactory.kt
├── data
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── data
│ │ ├── BufferooDataRepository.kt
│ │ ├── executor
│ │ └── JobExecutor.kt
│ │ ├── mapper
│ │ ├── BufferooMapper.kt
│ │ └── Mapper.kt
│ │ ├── model
│ │ └── BufferooEntity.kt
│ │ ├── repository
│ │ ├── BufferooCache.kt
│ │ ├── BufferooDataStore.kt
│ │ └── BufferooRemote.kt
│ │ └── source
│ │ ├── BufferooCacheDataStore.kt
│ │ ├── BufferooDataStoreFactory.kt
│ │ └── BufferooRemoteDataStore.kt
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── data
│ ├── BufferooDataRepositoryTest.kt
│ ├── mapper
│ └── BufferooMapperTest.kt
│ ├── source
│ ├── BufferooCacheDataStoreTest.kt
│ ├── BufferooDataStoreFactoryTest.kt
│ └── BufferooRemoteDataStoreTest.kt
│ └── test
│ └── factory
│ ├── BufferooFactory.kt
│ └── DataFactory.kt
├── dependencies.gradle
├── domain
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── domain
│ │ ├── executor
│ │ ├── PostExecutionThread.kt
│ │ └── ThreadExecutor.kt
│ │ ├── interactor
│ │ ├── BaseFlowableObserver.kt
│ │ ├── BaseSingleObserver.kt
│ │ ├── CompletableUseCase.kt
│ │ ├── FlowableUseCase.kt
│ │ └── browse
│ │ │ └── GetBufferoos.kt
│ │ ├── model
│ │ └── Bufferoo.kt
│ │ └── repository
│ │ └── BufferooRepository.kt
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── domain
│ ├── test
│ └── factory
│ │ ├── BufferooFactory.kt
│ │ └── DataFactory.kt
│ └── usecase
│ └── bufferoo
│ └── GetBufferoosTest.kt
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── licence.txt
├── mobile-ui
├── .gitignore
├── build.gradle
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── ui
│ │ ├── browse
│ │ └── BrowseActivityTest.kt
│ │ ├── injection
│ │ ├── component
│ │ │ └── TestApplicationComponent.kt
│ │ └── module
│ │ │ ├── TestApplicationModule.kt
│ │ │ ├── TestCacheModule.kt
│ │ │ ├── TestDataModule.kt
│ │ │ └── TestRemoteModule.kt
│ │ └── test
│ │ ├── TestApplication.kt
│ │ ├── TestRunner.kt
│ │ └── util
│ │ ├── BufferooFactory.kt
│ │ ├── DataFactory.kt
│ │ └── RecyclerViewMatcher.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── org
│ │ │ └── buffer
│ │ │ └── android
│ │ │ └── boilerplate
│ │ │ └── ui
│ │ │ ├── BufferooApplication.kt
│ │ │ ├── UiThread.kt
│ │ │ ├── browse
│ │ │ ├── BrowseActivity.kt
│ │ │ └── BrowseAdapter.kt
│ │ │ ├── injection
│ │ │ ├── ApplicationComponent.kt
│ │ │ ├── module
│ │ │ │ ├── ApplicationModule.kt
│ │ │ │ ├── CacheModule.kt
│ │ │ │ ├── DataModule.kt
│ │ │ │ ├── DomainModule.kt
│ │ │ │ ├── PresentationModule.kt
│ │ │ │ ├── RemoteModule.kt
│ │ │ │ └── UiModule.kt
│ │ │ └── scopes
│ │ │ │ ├── PerActivity.kt
│ │ │ │ └── PerApplication.kt
│ │ │ ├── mapper
│ │ │ ├── BufferooMapper.kt
│ │ │ └── Mapper.kt
│ │ │ ├── model
│ │ │ └── BufferooViewModel.kt
│ │ │ └── widget
│ │ │ ├── empty
│ │ │ ├── EmptyListener.kt
│ │ │ └── EmptyView.kt
│ │ │ └── error
│ │ │ ├── ErrorListener.kt
│ │ │ └── ErrorView.kt
│ └── res
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_browse.xml
│ │ ├── item_bufferoo.xml
│ │ ├── view_empty.xml
│ │ └── view_error.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── ui
│ ├── BufferooMapperTest.kt
│ └── test
│ └── factory
│ ├── BufferooFactory.kt
│ └── DataFactory.kt
├── presentation
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── presentation
│ │ ├── ViewModelFactory.kt
│ │ ├── browse
│ │ └── BrowseBufferoosViewModel.kt
│ │ ├── data
│ │ ├── Resource.kt
│ │ └── ResourceState.kt
│ │ ├── mapper
│ │ ├── BufferooMapper.kt
│ │ └── Mapper.kt
│ │ └── model
│ │ └── BufferooView.kt
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── presentation
│ ├── browse
│ └── BrowseBufferoosViewModelTest.kt
│ └── test
│ └── factory
│ ├── BufferooFactory.kt
│ └── DataFactory.kt
├── readme.md
├── remote
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── buffer
│ │ └── android
│ │ └── boilerplate
│ │ └── remote
│ │ ├── BufferooRemoteImpl.kt
│ │ ├── BufferooService.kt
│ │ ├── BufferooServiceFactory.kt
│ │ ├── mapper
│ │ ├── BufferooEntityMapper.kt
│ │ └── EntityMapper.kt
│ │ └── model
│ │ └── BufferooModel.kt
│ └── test
│ └── java
│ └── org
│ └── buffer
│ └── android
│ └── boilerplate
│ └── remote
│ ├── BufferooRemoteImplTest.kt
│ ├── mapper
│ └── BufferooEntityMapperTest.kt
│ └── test
│ └── factory
│ ├── BufferooFactory.kt
│ └── DataFactory.kt
└── settings.gradle
/.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
--------------------------------------------------------------------------------
/.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=25
9 | - ANDROID_API_LEVEL_22=22
10 | - ANDROID_BUILD_TOOLS_VERSION=25.0.3
11 | - ANDROID_ABI=armeabi-v7a
12 | - ANDROID_TAG=google_apis
13 | - ANDROID_TARGET=android-25
14 | - ADB_INSTALL_TIMEOUT=20 # minutes (2 minutes by default)
15 |
16 | android:
17 | components:
18 | - tools
19 | - platform-tools
20 | - android-$ANDROID_API_LEVEL_22
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 | - android-sdk-license-.+
33 | - '.+'
34 | # Specify at least one system image
35 | - sys-img-armeabi-v7a-google_apis-$ANDROID_API_LEVEL
36 | - sys-img-armeabi-v7a-android-$ANDROID_API_LEVEL_22
37 |
38 | # list of directories to Cache
39 |
40 | before_install:
41 | - mkdir "$ANDROID_HOME/licenses" || true
42 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
43 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
44 |
45 | licenses:
46 | - 'android-sdk-preview-license-.+'
47 | - 'android-sdk-license-.+'
48 | - 'google-gdk-license-.+'
49 |
50 | before_script:
51 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
52 | - emulator -avd test -no-audio -no-window &
53 | - android-wait-for-emulator
54 | - adb shell input keyevent 82 &
55 |
56 | script:
57 | - ./gradlew build
58 | - ./gradlew build connectedAndroidTest --stacktrace
59 |
60 | after_success:
61 | - bash <(curl -s https://codecov.io/bash)
62 |
--------------------------------------------------------------------------------
/art/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/art/architecture.png
--------------------------------------------------------------------------------
/art/data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/art/data.png
--------------------------------------------------------------------------------
/art/device_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/art/device_screenshot.png
--------------------------------------------------------------------------------
/art/ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/art/ui.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.1.51'
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.0-rc1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath "com.dicedmelon.gradle:jacoco-android:0.1.1"
13 |
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | apply from: 'dependencies.gradle'
21 |
22 | allprojects {
23 | repositories {
24 | google()
25 | jcenter()
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/cache/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/cache/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 |
5 | android {
6 | def globalConfiguration = rootProject.extensions.getByName("ext")
7 |
8 | compileSdkVersion globalConfiguration["androidCompileSdkVersion"]
9 | buildToolsVersion globalConfiguration["androidBuildToolsVersion"]
10 |
11 | defaultConfig {
12 | minSdkVersion globalConfiguration["androidMinSdkVersion"]
13 | targetSdkVersion globalConfiguration["androidTargetSdkVersion"]
14 | multiDexEnabled = true
15 | }
16 |
17 | dexOptions {
18 | preDexLibraries = false
19 | dexInProcess = false
20 | javaMaxHeapSize "4g"
21 | }
22 |
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_7
25 | targetCompatibility JavaVersion.VERSION_1_7
26 | }
27 |
28 | packagingOptions {
29 | exclude 'LICENSE.txt'
30 | exclude 'META-INF/DEPENDENCIES'
31 | exclude 'META-INF/ASL2.0'
32 | exclude 'META-INF/NOTICE'
33 | exclude 'META-INF/LICENSE'
34 | }
35 |
36 | lintOptions {
37 | quiet true
38 | abortOnError false
39 | ignoreWarnings true
40 | disable 'InvalidPackage' //Some libraries have issues with this.
41 | disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta.
42 | disable 'IconDensities' //For testing purpose. This is safe to remove.
43 | disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove.
44 | }
45 |
46 | }
47 |
48 | dependencies {
49 | def cacheDependencies = rootProject.ext.cacheDependencies
50 | def cacheTestDependencies = rootProject.ext.cacheTestDependencies
51 |
52 | compileOnly cacheDependencies.javaxAnnotation
53 |
54 | implementation project(':data')
55 |
56 | implementation cacheDependencies.kotlin
57 | implementation cacheDependencies.javaxInject
58 | implementation cacheDependencies.rxKotlin
59 | implementation cacheDependencies.gson
60 | implementation cacheDependencies.roomRuntime
61 | kapt cacheDependencies.roomCompiler
62 |
63 | testImplementation cacheTestDependencies.junit
64 | testImplementation cacheTestDependencies.kotlinJUnit
65 | testImplementation cacheTestDependencies.mockito
66 | testImplementation cacheTestDependencies.assertj
67 | testImplementation cacheTestDependencies.robolectric
68 | testImplementation cacheTestDependencies.archTesting
69 | testImplementation cacheTestDependencies.roomTesting
70 | }
--------------------------------------------------------------------------------
/cache/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/BufferooCacheImpl.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Flowable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.cache.db.BufferoosDatabase
7 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
8 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
9 | import org.buffer.android.boilerplate.data.model.BufferooEntity
10 | import org.buffer.android.boilerplate.data.repository.BufferooCache
11 | import javax.inject.Inject
12 |
13 | /**
14 | * Cached implementation for retrieving and saving Bufferoo instances. This class implements the
15 | * [BufferooCache] from the Data layer as it is that layers responsibility for defining the
16 | * operations in which data store implementation layers can carry out.
17 | */
18 | class BufferooCacheImpl @Inject constructor(val bufferoosDatabase: BufferoosDatabase,
19 | private val entityMapper: BufferooEntityMapper,
20 | private val preferencesHelper: PreferencesHelper) :
21 | BufferooCache {
22 |
23 | private val EXPIRATION_TIME = (60 * 10 * 1000).toLong()
24 |
25 | /**
26 | * Retrieve an instance from the database, used for tests.
27 | */
28 | internal fun getDatabase(): BufferoosDatabase {
29 | return bufferoosDatabase
30 | }
31 |
32 | /**
33 | * Remove all the data from all the tables in the database.
34 | */
35 | override fun clearBufferoos(): Completable {
36 | return Completable.defer {
37 | bufferoosDatabase.cachedBufferooDao().clearBufferoos()
38 | Completable.complete()
39 | }
40 | }
41 |
42 | /**
43 | * Save the given list of [BufferooEntity] instances to the database.
44 | */
45 | override fun saveBufferoos(bufferoos: List): Completable {
46 | return Completable.defer {
47 | bufferoos.forEach {
48 | bufferoosDatabase.cachedBufferooDao().insertBufferoo(
49 | entityMapper.mapToCached(it))
50 | }
51 | Completable.complete()
52 | }
53 | }
54 |
55 | /**
56 | * Retrieve a list of [BufferooEntity] instances from the database.
57 | */
58 | override fun getBufferoos(): Flowable> {
59 | return Flowable.defer {
60 | Flowable.just(bufferoosDatabase.cachedBufferooDao().getBufferoos())
61 | }.map {
62 | it.map { entityMapper.mapFromCached(it) }
63 | }
64 | }
65 |
66 | /**
67 | * Check whether there are instances of [CachedBufferoo] stored in the cache.
68 | */
69 | override fun isCached(): Single {
70 | return Single.defer {
71 | Single.just(bufferoosDatabase.cachedBufferooDao().getBufferoos().isNotEmpty())
72 | }
73 | }
74 |
75 | /**
76 | * Set a point in time at when the cache was last updated.
77 | */
78 | override fun setLastCacheTime(lastCache: Long) {
79 | preferencesHelper.lastCacheTime = lastCache
80 | }
81 |
82 | /**
83 | * Check whether the current cached data exceeds the defined [EXPIRATION_TIME] time.
84 | */
85 | override fun isExpired(): Boolean {
86 | val currentTime = System.currentTimeMillis()
87 | val lastUpdateTime = this.getLastCacheUpdateTimeMillis()
88 | return currentTime - lastUpdateTime > EXPIRATION_TIME
89 | }
90 |
91 | /**
92 | * Get in millis, the last time the cache was accessed.
93 | */
94 | private fun getLastCacheUpdateTimeMillis(): Long {
95 | return preferencesHelper.lastCacheTime
96 | }
97 |
98 | }
--------------------------------------------------------------------------------
/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 | open 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 |
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/dao/CachedBufferooDao.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.dao
2 |
3 | import android.arch.persistence.room.Dao
4 | import android.arch.persistence.room.Insert
5 | import android.arch.persistence.room.OnConflictStrategy
6 | import android.arch.persistence.room.Query
7 | import org.buffer.android.boilerplate.cache.db.constants.BufferooConstants
8 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
9 |
10 | @Dao
11 | abstract class CachedBufferooDao {
12 |
13 | @Query(BufferooConstants.QUERY_BUFFEROOS)
14 | abstract fun getBufferoos(): List
15 |
16 | @Query(BufferooConstants.DELETE_ALL_BUFFEROOS)
17 | abstract fun clearBufferoos()
18 |
19 | @Insert(onConflict = OnConflictStrategy.REPLACE)
20 | abstract fun insertBufferoo(cachedBufferoo: CachedBufferoo)
21 |
22 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/BufferoosDatabase.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db
2 |
3 | import android.arch.persistence.room.Database
4 | import android.arch.persistence.room.Room
5 | import android.arch.persistence.room.RoomDatabase
6 | import android.content.Context
7 | import org.buffer.android.boilerplate.cache.dao.CachedBufferooDao
8 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
9 | import javax.inject.Inject
10 |
11 | @Database(entities = arrayOf(CachedBufferoo::class), version = 1)
12 | abstract class BufferoosDatabase @Inject constructor() : RoomDatabase() {
13 |
14 | abstract fun cachedBufferooDao(): CachedBufferooDao
15 |
16 | private var INSTANCE: BufferoosDatabase? = null
17 |
18 | private val sLock = Any()
19 |
20 | fun getInstance(context: Context): BufferoosDatabase {
21 | if (INSTANCE == null) {
22 | synchronized(sLock) {
23 | if (INSTANCE == null) {
24 | INSTANCE = Room.databaseBuilder(context.applicationContext,
25 | BufferoosDatabase::class.java, "bufferoos.db")
26 | .build()
27 | }
28 | return INSTANCE!!
29 | }
30 | }
31 | return INSTANCE!!
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/db/constants/BufferooConstants.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.db.constants
2 |
3 | /**
4 | * Defines constants for the Bufferoos Table
5 | */
6 | object BufferooConstants {
7 |
8 | const val TABLE_NAME = "bufferoos"
9 |
10 | const val QUERY_BUFFEROOS = "SELECT * FROM" + " " + TABLE_NAME
11 |
12 | const val DELETE_ALL_BUFFEROOS = "DELETE FROM" + " " + TABLE_NAME
13 |
14 | }
--------------------------------------------------------------------------------
/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 | open class BufferooEntityMapper @Inject constructor():
12 | EntityMapper {
13 |
14 | /**
15 | * Map a [BufferooEntity] instance to a [CachedBufferoo] instance
16 | */
17 | override fun mapToCached(type: BufferooEntity): CachedBufferoo {
18 | return CachedBufferoo(type.id, type.name, type.title, type.avatar)
19 | }
20 |
21 | /**
22 | * Map a [CachedBufferoo] instance to a [BufferooEntity] instance
23 | */
24 | override fun mapFromCached(type: CachedBufferoo): BufferooEntity {
25 | return BufferooEntity(type.id, type.name, type.title, type.avatar)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/cache/src/main/java/org/buffer/android/boilerplate/cache/model/CachedBufferoo.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.model
2 |
3 | import android.arch.persistence.room.Entity
4 | import android.arch.persistence.room.PrimaryKey
5 | import org.buffer.android.boilerplate.cache.db.constants.BufferooConstants
6 |
7 | /**
8 | * Model used solely for the caching of a bufferroo
9 | */
10 | @Entity(tableName = BufferooConstants.TABLE_NAME)
11 | data class CachedBufferoo(
12 |
13 | @PrimaryKey
14 | var id: Long,
15 | val name: String,
16 | val title: String,
17 | val avatar: String
18 |
19 | )
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/BufferooCacheImplTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache
2 |
3 | import android.arch.persistence.room.Room
4 | import org.buffer.android.boilerplate.cache.db.BufferoosDatabase
5 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
6 | import org.buffer.android.boilerplate.cache.model.CachedBufferoo
7 | import org.buffer.android.boilerplate.cache.test.factory.BufferooFactory
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.robolectric.RobolectricTestRunner
11 | import org.robolectric.RuntimeEnvironment
12 | import org.robolectric.annotation.Config
13 | import kotlin.test.assertEquals
14 |
15 | @RunWith(RobolectricTestRunner::class)
16 | @Config(sdk = intArrayOf(21))
17 | class BufferooCacheImplTest {
18 |
19 | private var bufferoosDatabase = Room.inMemoryDatabaseBuilder(RuntimeEnvironment.application,
20 | BufferoosDatabase::class.java).allowMainThreadQueries().build()
21 | private var entityMapper = BufferooEntityMapper()
22 | private var preferencesHelper = PreferencesHelper(RuntimeEnvironment.application)
23 |
24 |
25 | private val databaseHelper = BufferooCacheImpl(bufferoosDatabase,
26 | entityMapper, preferencesHelper)
27 |
28 | @Test
29 | fun clearTablesCompletes() {
30 | val testObserver = databaseHelper.clearBufferoos().test()
31 | testObserver.assertComplete()
32 | }
33 |
34 | //
35 | @Test
36 | fun saveBufferoosCompletes() {
37 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
38 |
39 | val testObserver = databaseHelper.saveBufferoos(bufferooEntities).test()
40 | testObserver.assertComplete()
41 | }
42 |
43 | @Test
44 | fun saveBufferoosSavesData() {
45 | val bufferooCount = 2
46 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(bufferooCount)
47 |
48 | databaseHelper.saveBufferoos(bufferooEntities).test()
49 | checkNumRowsInBufferoosTable(bufferooCount)
50 | }
51 | //
52 |
53 | //
54 | @Test
55 | fun getBufferoosCompletes() {
56 | val testObserver = databaseHelper.getBufferoos().test()
57 | testObserver.assertComplete()
58 | }
59 |
60 | @Test
61 | fun getBufferoosReturnsData() {
62 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
63 | val cachedBufferoos = mutableListOf()
64 | bufferooEntities.forEach {
65 | cachedBufferoos.add(entityMapper.mapToCached(it))
66 | }
67 | insertBufferoos(cachedBufferoos)
68 |
69 | val testObserver = databaseHelper.getBufferoos().test()
70 | // testObserver.assertValue(bufferooEntities)
71 | }
72 | //
73 |
74 | private fun insertBufferoos(cachedBufferoos: List) {
75 | cachedBufferoos.forEach {
76 | bufferoosDatabase.cachedBufferooDao().insertBufferoo(it)
77 | }
78 | }
79 |
80 | private fun checkNumRowsInBufferoosTable(expectedRows: Int) {
81 | val numberOfRows = bufferoosDatabase.cachedBufferooDao().getBufferoos().size
82 | assertEquals(expectedRows, numberOfRows)
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/cache/src/test/java/org/buffer/android/boilerplate/cache/dao/CachedBufferooDaoTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.cache.dao
2 |
3 | import android.arch.persistence.room.Room
4 | import org.buffer.android.boilerplate.cache.db.BufferoosDatabase
5 | import org.buffer.android.boilerplate.cache.test.factory.BufferooFactory
6 | import org.junit.After
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.robolectric.RobolectricTestRunner
11 | import org.robolectric.RuntimeEnvironment
12 |
13 | @RunWith(RobolectricTestRunner::class)
14 | open class CachedBufferooDaoTest {
15 |
16 | private lateinit var bufferoosDatabase: BufferoosDatabase
17 |
18 | @Before
19 | fun initDb() {
20 | bufferoosDatabase = Room.inMemoryDatabaseBuilder(
21 | RuntimeEnvironment.application.baseContext,
22 | BufferoosDatabase::class.java)
23 | .allowMainThreadQueries()
24 | .build()
25 | }
26 |
27 | @After
28 | fun closeDb() {
29 | bufferoosDatabase.close()
30 | }
31 |
32 | @Test
33 | fun insertBufferoosSavesData() {
34 | val cachedBufferoo = BufferooFactory.makeCachedBufferoo()
35 | bufferoosDatabase.cachedBufferooDao().insertBufferoo(cachedBufferoo)
36 |
37 | val bufferoos = bufferoosDatabase.cachedBufferooDao().getBufferoos()
38 | assert(bufferoos.isNotEmpty())
39 | }
40 |
41 | @Test
42 | fun getBufferoosRetrievesData() {
43 | val cachedBufferoos = BufferooFactory.makeCachedBufferooList(5)
44 |
45 | cachedBufferoos.forEach {
46 | bufferoosDatabase.cachedBufferooDao().insertBufferoo(it) }
47 |
48 | val retrievedBufferoos = bufferoosDatabase.cachedBufferooDao().getBufferoos()
49 | assert(retrievedBufferoos == cachedBufferoos.sortedWith(compareBy({ it.id }, { it.id })))
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.cache.test.factory.DataFactory.Factory.randomLong
5 | import org.buffer.android.boilerplate.cache.test.factory.DataFactory.Factory.randomUuid
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 |
8 | /**
9 | * Factory class for Bufferoo related instances
10 | */
11 | class BufferooFactory {
12 |
13 | companion object Factory {
14 |
15 | fun makeCachedBufferoo(): CachedBufferoo {
16 | return CachedBufferoo(randomLong(), randomUuid(), randomUuid(), randomUuid())
17 | }
18 |
19 | fun makeBufferooEntity(): BufferooEntity {
20 | return BufferooEntity(randomLong(), randomUuid(), randomUuid(), randomUuid())
21 | }
22 |
23 | fun makeBufferooEntityList(count: Int): List {
24 | val bufferooEntities = mutableListOf()
25 | repeat(count) {
26 | bufferooEntities.add(makeBufferooEntity())
27 | }
28 | return bufferooEntities
29 | }
30 |
31 | fun makeCachedBufferooList(count: Int): List {
32 | val cachedBufferoos = mutableListOf()
33 | repeat(count) {
34 | cachedBufferoos.add(makeCachedBufferoo())
35 | }
36 | return cachedBufferoos
37 | }
38 |
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/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/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/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/BufferooDataRepository.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.data
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Flowable
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.domain.model.Bufferoo
9 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
10 | import javax.inject.Inject
11 |
12 | /**
13 | * Provides an implementation of the [BufferooRepository] interface for communicating to and from
14 | * data sources
15 | */
16 | class BufferooDataRepository @Inject constructor(private val factory: BufferooDataStoreFactory,
17 | private val bufferooMapper: BufferooMapper) :
18 | BufferooRepository {
19 |
20 | override fun clearBufferoos(): Completable {
21 | return factory.retrieveCacheDataStore().clearBufferoos()
22 | }
23 |
24 | override fun saveBufferoos(bufferoos: List): Completable {
25 | val bufferooEntities = mutableListOf()
26 | bufferoos.map { bufferooEntities.add(bufferooMapper.mapToEntity(it)) }
27 | return factory.retrieveCacheDataStore().saveBufferoos(bufferooEntities)
28 | }
29 |
30 | override fun getBufferoos(): Flowable> {
31 | return factory.retrieveCacheDataStore().isCached()
32 | .flatMapPublisher {
33 | factory.retrieveDataStore(it).getBufferoos()
34 | }
35 | .flatMap {
36 | Flowable.just(it.map { bufferooMapper.mapFromEntity(it) })
37 | }
38 | .flatMap {
39 | saveBufferoos(it).toSingle { it }.toFlowable()
40 | }
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/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/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.id, 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.id, type.name, type.title, type.avatar)
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 id: Long, val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/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.Flowable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 |
8 | /**
9 | * Interface defining methods for the caching of Bufferroos. This is to be implemented by the
10 | * cache layer, using this interface as a way of communicating.
11 | */
12 | interface BufferooCache {
13 |
14 | /**
15 | * Clear all Bufferoos from the cache.
16 | */
17 | fun clearBufferoos(): Completable
18 |
19 | /**
20 | * Save a given list of Bufferoos to the cache.
21 | */
22 | fun saveBufferoos(bufferoos: List): Completable
23 |
24 | /**
25 | * Retrieve a list of Bufferoos, from the cache.
26 | */
27 | fun getBufferoos(): Flowable>
28 |
29 | /**
30 | * Check whether there is a list of Bufferoos stored in the cache.
31 | *
32 | * @return true if the list is cached, otherwise false
33 | */
34 | fun isCached(): Single
35 |
36 | /**
37 | * Set a point in time at when the cache was last updated.
38 | *
39 | * @param lastCache the point in time at when the cache was last updated
40 | */
41 | fun setLastCacheTime(lastCache: Long)
42 |
43 | /**
44 | * Check if the cache is expired.
45 | *
46 | * @return true if the cache is expired, otherwise false
47 | */
48 | fun isExpired(): Boolean
49 |
50 | }
--------------------------------------------------------------------------------
/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.Flowable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 |
8 | /**
9 | * Interface defining methods for the data operations related to Bufferroos.
10 | * This is to be implemented by external data source layers, setting the requirements for the
11 | * operations that need to be implemented
12 | */
13 | interface BufferooDataStore {
14 |
15 | fun clearBufferoos(): Completable
16 |
17 | fun saveBufferoos(bufferoos: List): Completable
18 |
19 | fun getBufferoos(): Flowable>
20 |
21 | fun isCached(): Single
22 |
23 | }
--------------------------------------------------------------------------------
/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.Flowable
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(): Flowable>
16 |
17 | }
--------------------------------------------------------------------------------
/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.Flowable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 | import org.buffer.android.boilerplate.data.repository.BufferooCache
8 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
9 | import javax.inject.Inject
10 |
11 | /**
12 | * Implementation of the [BufferooDataStore] interface to provide a means of communicating
13 | * with the local data source
14 | */
15 | open class BufferooCacheDataStore @Inject constructor(private val bufferooCache: BufferooCache) :
16 | BufferooDataStore {
17 |
18 | /**
19 | * Clear all Bufferoos from the cache
20 | */
21 | override fun clearBufferoos(): Completable {
22 | return bufferooCache.clearBufferoos()
23 | }
24 |
25 | /**
26 | * Save a given [List] of [BufferooEntity] instances to the cache
27 | */
28 | override fun saveBufferoos(bufferoos: List): Completable {
29 | return bufferooCache.saveBufferoos(bufferoos)
30 | .doOnComplete {
31 | bufferooCache.setLastCacheTime(System.currentTimeMillis())
32 | }
33 | }
34 |
35 | /**
36 | * Retrieve a list of [BufferooEntity] instance from the cache
37 | */
38 | override fun getBufferoos(): Flowable> {
39 | return bufferooCache.getBufferoos()
40 | }
41 |
42 | /**
43 | * Retrieve a list of [BufferooEntity] instance from the cache
44 | */
45 | override fun isCached(): Single {
46 | return bufferooCache.isCached()
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/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(isCached: Boolean): BufferooDataStore {
20 | if (isCached && !bufferooCache.isExpired()) {
21 | return retrieveCacheDataStore()
22 | }
23 | return retrieveRemoteDataStore()
24 | }
25 |
26 | /**
27 | * Return an instance of the Cache Data Store
28 | */
29 | open fun retrieveCacheDataStore(): BufferooDataStore {
30 | return bufferooCacheDataStore
31 | }
32 |
33 | /**
34 | * Return an instance of the Remote Data Store
35 | */
36 | open fun retrieveRemoteDataStore(): BufferooDataStore {
37 | return bufferooRemoteDataStore
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/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.Flowable
5 | import io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.model.BufferooEntity
7 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
8 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
9 | import javax.inject.Inject
10 |
11 | /**
12 | * Implementation of the [BufferooDataStore] interface to provide a means of communicating
13 | * with the remote data source
14 | */
15 | open class BufferooRemoteDataStore @Inject constructor(private val bufferooRemote: BufferooRemote) :
16 | BufferooDataStore {
17 |
18 | override fun clearBufferoos(): Completable {
19 | throw UnsupportedOperationException()
20 | }
21 |
22 | override fun saveBufferoos(bufferoos: List): Completable {
23 | throw UnsupportedOperationException()
24 | }
25 |
26 | /**
27 | * Retrieve a list of [BufferooEntity] instances from the API
28 | */
29 | override fun getBufferoos(): Flowable> {
30 | return bufferooRemote.getBufferoos()
31 | }
32 |
33 | override fun isCached(): Single {
34 | throw UnsupportedOperationException()
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/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.Flowable
6 | import io.reactivex.Single
7 | import org.buffer.android.boilerplate.data.mapper.BufferooMapper
8 | import org.buffer.android.boilerplate.data.model.BufferooEntity
9 | import org.buffer.android.boilerplate.data.repository.BufferooDataStore
10 | import org.buffer.android.boilerplate.data.source.BufferooCacheDataStore
11 | import org.buffer.android.boilerplate.data.source.BufferooDataStoreFactory
12 | import org.buffer.android.boilerplate.data.source.BufferooRemoteDataStore
13 | import org.buffer.android.boilerplate.data.test.factory.BufferooFactory
14 | import org.buffer.android.boilerplate.domain.model.Bufferoo
15 | import org.junit.Before
16 | import org.junit.Test
17 | import org.junit.runner.RunWith
18 | import org.junit.runners.JUnit4
19 |
20 | @RunWith(JUnit4::class)
21 | class BufferooDataRepositoryTest {
22 |
23 | private lateinit var bufferooDataRepository: BufferooDataRepository
24 |
25 | private lateinit var bufferooDataStoreFactory: BufferooDataStoreFactory
26 | private lateinit var bufferooMapper: BufferooMapper
27 | private lateinit var bufferooCacheDataStore: BufferooCacheDataStore
28 | private lateinit var bufferooRemoteDataStore: BufferooRemoteDataStore
29 |
30 | @Before
31 | fun setUp() {
32 | bufferooDataStoreFactory = mock()
33 | bufferooMapper = mock()
34 | bufferooCacheDataStore = mock()
35 | bufferooRemoteDataStore = mock()
36 | bufferooDataRepository = BufferooDataRepository(bufferooDataStoreFactory, bufferooMapper)
37 | stubBufferooDataStoreFactoryRetrieveCacheDataStore()
38 | stubBufferooDataStoreFactoryRetrieveRemoteDataStore()
39 | }
40 |
41 | //
42 | @Test
43 | fun clearBufferoosCompletes() {
44 | stubBufferooCacheClearBufferoos(Completable.complete())
45 | val testObserver = bufferooDataRepository.clearBufferoos().test()
46 | testObserver.assertComplete()
47 | }
48 |
49 | @Test
50 | fun clearBufferoosCallsCacheDataStore() {
51 | stubBufferooCacheClearBufferoos(Completable.complete())
52 | bufferooDataRepository.clearBufferoos().test()
53 | verify(bufferooCacheDataStore).clearBufferoos()
54 | }
55 |
56 | @Test
57 | fun clearBufferoosNeverCallsRemoteDataStore() {
58 | stubBufferooCacheClearBufferoos(Completable.complete())
59 | bufferooDataRepository.clearBufferoos().test()
60 | verify(bufferooRemoteDataStore, never()).clearBufferoos()
61 | }
62 | //
63 |
64 | //
65 | @Test
66 | fun saveBufferoosCompletes() {
67 | stubBufferooCacheSaveBufferoos(Completable.complete())
68 | val testObserver = bufferooDataRepository.saveBufferoos(
69 | BufferooFactory.makeBufferooList(2)).test()
70 | testObserver.assertComplete()
71 | }
72 |
73 | @Test
74 | fun saveBufferoosCallsCacheDataStore() {
75 | stubBufferooCacheSaveBufferoos(Completable.complete())
76 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
77 | verify(bufferooCacheDataStore).saveBufferoos(any())
78 | }
79 |
80 | @Test
81 | fun saveBufferoosNeverCallsRemoteDataStore() {
82 | stubBufferooCacheSaveBufferoos(Completable.complete())
83 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
84 | verify(bufferooRemoteDataStore, never()).saveBufferoos(any())
85 | }
86 | //
87 |
88 | //
89 | @Test
90 | fun getBufferoosCompletes() {
91 | stubBufferooCacheDataStoreIsCached(Single.just(true))
92 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
93 | stubBufferooCacheDataStoreGetBufferoos(Flowable.just(
94 | BufferooFactory.makeBufferooEntityList(2)))
95 | stubBufferooCacheSaveBufferoos(Completable.complete())
96 | val testObserver = bufferooDataRepository.getBufferoos().test()
97 | testObserver.assertComplete()
98 | }
99 |
100 | @Test
101 | fun getBufferoosReturnsData() {
102 | stubBufferooCacheDataStoreIsCached(Single.just(true))
103 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
104 | stubBufferooCacheSaveBufferoos(Completable.complete())
105 | val bufferoos = BufferooFactory.makeBufferooList(2)
106 | val bufferooEntities = BufferooFactory.makeBufferooEntityList(2)
107 | bufferoos.forEachIndexed { index, bufferoo ->
108 | stubBufferooMapperMapFromEntity(bufferooEntities[index], bufferoo) }
109 | stubBufferooCacheDataStoreGetBufferoos(Flowable.just(bufferooEntities))
110 |
111 | val testObserver = bufferooDataRepository.getBufferoos().test()
112 | testObserver.assertValue(bufferoos)
113 | }
114 |
115 | @Test
116 | fun getBufferoosSavesBufferoosWhenFromCacheDataStore() {
117 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooCacheDataStore)
118 | stubBufferooCacheSaveBufferoos(Completable.complete())
119 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
120 | verify(bufferooCacheDataStore).saveBufferoos(any())
121 | }
122 |
123 | @Test
124 | fun getBufferoosNeverSavesBufferoosWhenFromRemoteDataStore() {
125 | stubBufferooDataStoreFactoryRetrieveDataStore(bufferooRemoteDataStore)
126 | stubBufferooCacheSaveBufferoos(Completable.complete())
127 | bufferooDataRepository.saveBufferoos(BufferooFactory.makeBufferooList(2)).test()
128 | verify(bufferooRemoteDataStore, never()).saveBufferoos(any())
129 | }
130 | //
131 |
132 | //
133 | private fun stubBufferooCacheSaveBufferoos(completable: Completable) {
134 | whenever(bufferooCacheDataStore.saveBufferoos(any()))
135 | .thenReturn(completable)
136 | }
137 |
138 | private fun stubBufferooCacheDataStoreIsCached(single: Single) {
139 | whenever(bufferooCacheDataStore.isCached())
140 | .thenReturn(single)
141 | }
142 |
143 | private fun stubBufferooCacheDataStoreGetBufferoos(single: Flowable>) {
144 | whenever(bufferooCacheDataStore.getBufferoos())
145 | .thenReturn(single)
146 | }
147 |
148 | private fun stubBufferooRemoteDataStoreGetBufferoos(single: Flowable>) {
149 | whenever(bufferooRemoteDataStore.getBufferoos())
150 | .thenReturn(single)
151 | }
152 |
153 | private fun stubBufferooCacheClearBufferoos(completable: Completable) {
154 | whenever(bufferooCacheDataStore.clearBufferoos())
155 | .thenReturn(completable)
156 | }
157 |
158 | private fun stubBufferooDataStoreFactoryRetrieveCacheDataStore() {
159 | whenever(bufferooDataStoreFactory.retrieveCacheDataStore())
160 | .thenReturn(bufferooCacheDataStore)
161 | }
162 |
163 | private fun stubBufferooDataStoreFactoryRetrieveRemoteDataStore() {
164 | whenever(bufferooDataStoreFactory.retrieveRemoteDataStore())
165 | .thenReturn(bufferooCacheDataStore)
166 | }
167 |
168 | private fun stubBufferooDataStoreFactoryRetrieveDataStore(dataStore: BufferooDataStore) {
169 | whenever(bufferooDataStoreFactory.retrieveDataStore(any()))
170 | .thenReturn(dataStore)
171 | }
172 |
173 | private fun stubBufferooMapperMapFromEntity(bufferooEntity: BufferooEntity,
174 | bufferoo: Bufferoo) {
175 | whenever(bufferooMapper.mapFromEntity(bufferooEntity))
176 | .thenReturn(bufferoo)
177 | }
178 | //
179 |
180 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.Flowable
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(Flowable.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: Flowable>) {
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 | }
--------------------------------------------------------------------------------
/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 io.reactivex.Single
6 | import org.buffer.android.boilerplate.data.repository.BufferooCache
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.junit.runners.JUnit4
11 |
12 | @RunWith(JUnit4::class)
13 | class BufferooDataStoreFactoryTest {
14 |
15 | private lateinit var bufferooDataStoreFactory: BufferooDataStoreFactory
16 |
17 | private lateinit var bufferooCache: BufferooCache
18 | private lateinit var bufferooCacheDataStore: BufferooCacheDataStore
19 | private lateinit var bufferooRemoteDataStore: BufferooRemoteDataStore
20 |
21 | @Before
22 | fun setUp() {
23 | bufferooCache = mock()
24 | bufferooCacheDataStore = mock()
25 | bufferooRemoteDataStore = mock()
26 | bufferooDataStoreFactory = BufferooDataStoreFactory(bufferooCache,
27 | bufferooCacheDataStore, bufferooRemoteDataStore)
28 | }
29 |
30 | //
31 | @Test
32 | fun retrieveDataStoreWhenNotCachedReturnsRemoteDataStore() {
33 | stubBufferooCacheIsCached(Single.just(false))
34 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore(false)
35 | assert(bufferooDataStore is BufferooRemoteDataStore)
36 | }
37 |
38 | @Test
39 | fun retrieveDataStoreWhenCacheExpiredReturnsRemoteDataStore() {
40 | stubBufferooCacheIsCached(Single.just(true))
41 | stubBufferooCacheIsExpired(true)
42 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore(true)
43 | assert(bufferooDataStore is BufferooRemoteDataStore)
44 | }
45 |
46 | @Test
47 | fun retrieveDataStoreReturnsCacheDataStore() {
48 | stubBufferooCacheIsCached(Single.just(true))
49 | stubBufferooCacheIsExpired(false)
50 | val bufferooDataStore = bufferooDataStoreFactory.retrieveDataStore(true)
51 | assert(bufferooDataStore is BufferooCacheDataStore)
52 | }
53 | //
54 |
55 | //
56 | @Test
57 | fun retrieveRemoteDataStoreReturnsRemoteDataStore() {
58 | val bufferooDataStore = bufferooDataStoreFactory.retrieveRemoteDataStore()
59 | assert(bufferooDataStore is BufferooRemoteDataStore)
60 | }
61 | //
62 |
63 | //
64 | @Test
65 | fun retrieveCacheDataStoreReturnsCacheDataStore() {
66 | val bufferooDataStore = bufferooDataStoreFactory.retrieveCacheDataStore()
67 | assert(bufferooDataStore is BufferooCacheDataStore)
68 | }
69 | //
70 |
71 | //
72 | private fun stubBufferooCacheIsCached(single: Single) {
73 | whenever(bufferooCache.isCached())
74 | .thenReturn(single)
75 | }
76 |
77 | private fun stubBufferooCacheIsExpired(isExpired: Boolean) {
78 | whenever(bufferooCache.isExpired())
79 | .thenReturn(isExpired)
80 | }
81 | //
82 |
83 | }
--------------------------------------------------------------------------------
/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.Flowable
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(Flowable.just(BufferooFactory.makeBufferooEntityList(2)))
45 | val testObserver = bufferooRemote.getBufferoos().test()
46 | testObserver.assertComplete()
47 | }
48 | //
49 |
50 | //
51 | private fun stubBufferooCacheGetBufferoos(single: Flowable>) {
52 | whenever(bufferooRemote.getBufferoos())
53 | .thenReturn(single)
54 | }
55 | //
56 |
57 | }
--------------------------------------------------------------------------------
/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.randomLong
5 | import org.buffer.android.boilerplate.data.test.factory.DataFactory.Factory.randomUuid
6 | import org.buffer.android.boilerplate.domain.model.Bufferoo
7 |
8 | /**
9 | * Factory class for Bufferoo related instances
10 | */
11 | class BufferooFactory {
12 |
13 | companion object Factory {
14 |
15 | fun makeBufferooEntity(): BufferooEntity {
16 | return BufferooEntity(randomLong(), randomUuid(), randomUuid(), randomUuid())
17 | }
18 |
19 | fun makeBufferoo(): Bufferoo {
20 | return Bufferoo(randomLong(), randomUuid(), randomUuid(), randomUuid())
21 | }
22 |
23 | fun makeBufferooEntityList(count: Int): List {
24 | val bufferooEntities = mutableListOf()
25 | repeat(count) {
26 | bufferooEntities.add(makeBufferooEntity())
27 | }
28 | return bufferooEntities
29 | }
30 |
31 | fun makeBufferooList(count: Int): List {
32 | val bufferoos = mutableListOf()
33 | repeat(count) {
34 | bufferoos.add(makeBufferoo())
35 | }
36 | return bufferoos
37 | }
38 |
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/dependencies.gradle:
--------------------------------------------------------------------------------
1 |
2 | allprojects {
3 | repositories {
4 | jcenter()
5 | }
6 | }
7 |
8 | ext {
9 | //Android
10 | androidBuildToolsVersion = "26.0.2"
11 | androidMinSdkVersion = 15
12 | androidTargetSdkVersion = 26
13 | androidCompileSdkVersion = 26
14 |
15 | //Libraries
16 | kotlinVersion = '1.1.4-3'
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-alpha9-1'
29 | supportLibraryVersion = '26.1.0'
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 | archRuntime: "android.arch.lifecycle:runtime:${roomVersion}",
79 | archExtensions: "android.arch.lifecycle:extensions:${roomVersion}",
80 | archCompiler: "android.arch.lifecycle:compiler:${roomVersion}",
81 | ]
82 |
83 | presentationTestDependencies = [
84 | junit: "junit:junit:${jUnitVersion}",
85 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
86 | assertj: "org.assertj:assertj-core:${assertJVersion}",
87 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
88 | robolectric: "org.robolectric:robolectric:${robolectricVersion}"
89 | ]
90 |
91 | dataDependencies = [
92 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
93 | dagger: "com.google.dagger:dagger:${daggerVersion}",
94 | okHttp: "com.squareup.okhttp3:okhttp:${okHttpVersion}",
95 | okHttpLogger: "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
96 | gson: "com.google.code.gson:gson:${gsonVersion}",
97 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
98 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
99 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
100 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
101 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
102 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
103 | retrofit: "com.squareup.retrofit2:retrofit:${retrofitVersion}",
104 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
105 | retrofitAdapter: "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
106 | ]
107 |
108 | dataTestDependencies = [
109 | junit: "junit:junit:${jUnitVersion}",
110 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
111 | assertj: "org.assertj:assertj-core:${assertJVersion}",
112 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
113 | robolectric: "org.robolectric:robolectric:${robolectricVersion}"
114 | ]
115 |
116 | cacheDependencies = [
117 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
118 | dagger: "com.google.dagger:dagger:${daggerVersion}",
119 | gson: "com.google.code.gson:gson:${gsonVersion}",
120 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
121 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
122 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
123 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
124 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
125 | roomRuntime: "android.arch.persistence.room:runtime:${roomVersion}",
126 | roomCompiler: "android.arch.persistence.room:compiler:${roomVersion}",
127 | roomRxJava: "android.arch.persistence.room:rxjava2:${roomVersion}"
128 | ]
129 |
130 | cacheTestDependencies = [
131 | junit: "junit:junit:${jUnitVersion}",
132 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
133 | assertj: "org.assertj:assertj-core:${assertJVersion}",
134 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
135 | robolectric: "org.robolectric:robolectric:${robolectricVersion}",
136 | roomTesting: "android.arch.persistence.room:testing:${roomVersion}",
137 | archTesting: "android.arch.core:core-testing:${roomVersion}",
138 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
139 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}"
140 | ]
141 |
142 | remoteDependencies = [
143 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
144 | dagger: "com.google.dagger:dagger:${daggerVersion}",
145 | gson: "com.google.code.gson:gson:${gsonVersion}",
146 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
147 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
148 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
149 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
150 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
151 | okHttp: "com.squareup.okhttp3:okhttp:${okHttpVersion}",
152 | okHttpLogger: "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
153 | retrofit: "com.squareup.retrofit2:retrofit:${retrofitVersion}",
154 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
155 | retrofitAdapter: "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
156 | ]
157 |
158 | remoteTestDependencies = [
159 | junit: "junit:junit:${jUnitVersion}",
160 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
161 | assertj: "org.assertj:assertj-core:${assertJVersion}",
162 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
163 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
164 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}"
165 | ]
166 |
167 | mobileUiDependencies = [
168 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}",
169 | dagger: "com.google.dagger:dagger:${daggerVersion}",
170 | rxKotlin: "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
171 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
172 | glide: "com.github.bumptech.glide:glide:${glideVersion}",
173 | kotlin: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}",
174 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
175 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}",
176 | androidAnnotations: "com.android.support:support-annotations:${supportLibraryVersion}",
177 | androidSupportV4: "com.android.support:support-v4:${supportLibraryVersion}",
178 | androidSupportV13: "com.android.support:support-v13:${supportLibraryVersion}",
179 | appCompatV7: "com.android.support:appcompat-v7:${supportLibraryVersion}",
180 | supportRecyclerView:"com.android.support:recyclerview-v7:${supportLibraryVersion}",
181 | supportDesign: "com.android.support:design:${supportLibraryVersion}",
182 | timber: "com.jakewharton.timber:timber:${timberVersion}",
183 | daggerSupport: "com.google.dagger:dagger-android-support:${daggerVersion}",
184 | daggerProcessor: "com.google.dagger:dagger-android-processor:${daggerVersion}",
185 | glassfishAnnotation: "org.glassfish:javax.annotation:${glassfishAnnotationVersion}",
186 | roomRuntime: "android.arch.persistence.room:runtime:${roomVersion}",
187 | roomCompiler: "android.arch.persistence.room:compiler:${roomVersion}",
188 | roomRxJava: "android.arch.persistence.room:rxjava2:${roomVersion}",
189 | ]
190 |
191 | mobileUiTestDependencies = [
192 | junit: "junit:junit:${jUnitVersion}",
193 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
194 | assertj: "org.assertj:assertj-core:${assertJVersion}",
195 | mockito: "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
196 | supportRunner: "com.android.support.test:runner:${androidSupportRunnerVersion}",
197 | supportRules: "com.android.support.test:rules:${androidSupportRulesVersion}",
198 | mockitoAndroid: "org.mockito:mockito-android:${mockitoAndroidVersion}",
199 | espressoCore: "com.android.support.test.espresso:espresso-core:${espressoVersion}",
200 | espressoIntents: "com.android.support.test.espresso:espresso-intents:${espressoVersion}",
201 | espressoContrib: "com.android.support.test.espresso:espresso-contrib:${espressoVersion}",
202 | androidRunner: "com.android.support.test:runner:${runnerVersion}",
203 | androidRules: "com.android.support.test:rules:${runnerVersion}"
204 | ]
205 |
206 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.8
4 | targetCompatibility = 1.8
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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/BaseFlowableObserver.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor
2 |
3 | import io.reactivex.SingleObserver
4 | import io.reactivex.disposables.Disposable
5 |
6 | /**
7 | * Default [SingleObserver] base class to define
8 | */
9 | open class BaseFlowableObserver : SingleObserver {
10 |
11 | override fun onSubscribe(d: Disposable) { }
12 |
13 | override fun onSuccess(t: T) { }
14 |
15 | override fun onError(exception: Throwable) { }
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/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 | }
--------------------------------------------------------------------------------
/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/FlowableUseCase.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.interactor
2 |
3 | import io.reactivex.Flowable
4 | import io.reactivex.Single
5 | import io.reactivex.disposables.CompositeDisposable
6 | import io.reactivex.disposables.Disposable
7 | import io.reactivex.schedulers.Schedulers
8 | import io.reactivex.subscribers.DisposableSubscriber
9 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
10 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
11 |
12 |
13 |
14 | /**
15 | * Abstract class for a UseCase that returns an instance of a [Single].
16 | */
17 | abstract class FlowableUseCase constructor(
18 | private val threadExecutor: ThreadExecutor,
19 | private val postExecutionThread: PostExecutionThread) {
20 |
21 | private val disposables = CompositeDisposable()
22 |
23 | /**
24 | * Builds a [Single] which will be used when the current [FlowableUseCase] is executed.
25 | */
26 | protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable
27 |
28 | /**
29 | * Executes the current use case.
30 | */
31 | open fun execute(observer: DisposableSubscriber, params: Params? = null) {
32 | val observable = this.buildUseCaseObservable(params)
33 | .subscribeOn(Schedulers.from(threadExecutor))
34 | .observeOn(postExecutionThread.scheduler) as Flowable
35 | addDisposable(observable.subscribeWith(observer))
36 | }
37 |
38 | /**
39 | * Dispose from current [CompositeDisposable].
40 | */
41 | fun dispose() {
42 | if (!disposables.isDisposed) disposables.dispose()
43 | }
44 |
45 | /**
46 | * Dispose from current [CompositeDisposable].
47 | */
48 | private fun addDisposable(disposable: Disposable) {
49 | disposables.add(disposable)
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/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.Flowable
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.FlowableUseCase
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 | FlowableUseCase, Void?>(threadExecutor, postExecutionThread) {
18 |
19 | public override fun buildUseCaseObservable(params: Void?): Flowable> {
20 | return bufferooRepository.getBufferoos()
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/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 id: Long, val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/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.Flowable
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(): Flowable>
19 |
20 | }
--------------------------------------------------------------------------------
/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.randomLong
5 | import org.buffer.android.boilerplate.domain.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 makeBufferooList(count: Int): List {
15 | val bufferoos = mutableListOf()
16 | repeat(count) {
17 | bufferoos.add(makeBufferoo())
18 | }
19 | return bufferoos
20 | }
21 |
22 | fun makeBufferoo(): Bufferoo {
23 | return Bufferoo(randomLong(), randomUuid(), randomUuid(), randomUuid())
24 | }
25 |
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/domain/src/test/java/org/buffer/android/boilerplate/domain/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.domain.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 randomInt(): Int {
13 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
14 | }
15 |
16 | fun randomLong(): Long {
17 | return randomInt().toLong()
18 | }
19 |
20 | fun randomUuid(): String {
21 | return java.util.UUID.randomUUID().toString()
22 | }
23 |
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/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.Flowable
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(Flowable.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(Flowable.just(bufferoos))
50 | val testObserver = getBufferoos.buildUseCaseObservable(null).test()
51 | testObserver.assertValue(bufferoos)
52 | }
53 |
54 | private fun stubBufferooRepositoryGetBufferoos(single: Flowable>) {
55 | whenever(mockBufferooRepository.getBufferoos())
56 | .thenReturn(single)
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | compile presentationDependencies.archRuntime
90 | compile presentationDependencies.archExtensions
91 | compile "android.arch.persistence.room:rxjava2:1.0.0-alpha9-1"
92 | kapt presentationDependencies.archCompiler
93 |
94 | testImplementation mobileUiTestDependencies.kotlinJUnit
95 |
96 | kapt mobileUiDependencies.daggerCompiler
97 | kapt mobileUiDependencies.daggerProcessor
98 | compileOnly mobileUiDependencies.glassfishAnnotation
99 |
100 | // Instrumentation test dependencies
101 | androidTestImplementation mobileUiTestDependencies.junit
102 | androidTestImplementation mobileUiTestDependencies.mockito
103 | androidTestImplementation mobileUiTestDependencies.mockitoAndroid
104 | androidTestImplementation (mobileUiTestDependencies.espressoCore) {
105 | exclude group: 'com.android.support', module: 'support-annotations'
106 | }
107 | androidTestImplementation (mobileUiTestDependencies.androidRunner) {
108 | exclude group: 'com.android.support', module: 'support-annotations'
109 | }
110 | androidTestImplementation (mobileUiTestDependencies.androidRules) {
111 | exclude group: 'com.android.support', module: 'support-annotations'
112 | }
113 | androidTestImplementation (mobileUiTestDependencies.espressoIntents) {
114 | exclude group: 'com.android.support', module: 'support-annotations'
115 | }
116 | androidTestImplementation(mobileUiTestDependencies.espressoContrib) {
117 | exclude module: 'appcompat'
118 | exclude module: 'appcompat-v7'
119 | exclude module: 'support-v4'
120 | exclude module: 'support-v13'
121 | exclude module: 'support-annotations'
122 | exclude module: 'recyclerview-v7'
123 | exclude module: 'design'
124 | }
125 |
126 | kaptTest mobileUiDependencies.daggerCompiler
127 | kaptAndroidTest mobileUiDependencies.daggerCompiler
128 | }
129 |
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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.Flowable
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.util.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(Flowable.just(BufferooFactory.makeBufferooList(2)))
31 | activity.launchActivity(null)
32 | }
33 |
34 | @Test
35 | fun bufferoosDisplay() {
36 | val bufferoos = BufferooFactory.makeBufferooList(1)
37 | stubBufferooRepositoryGetBufferoos(Flowable.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(Flowable.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: Flowable>) {
63 | whenever(TestApplication.appComponent().bufferooRepository().getBufferoos())
64 | .thenReturn(single)
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/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.*
11 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
12 | import org.buffer.android.boilerplate.ui.test.TestApplication
13 | import javax.inject.Singleton
14 |
15 | @Singleton
16 | @Component(modules = arrayOf(
17 | TestApplicationModule::class,
18 | AndroidSupportInjectionModule::class,
19 | TestCacheModule::class,
20 | TestRemoteModule::class,
21 | TestDataModule::class,
22 | PresentationModule::class,
23 | UiModule::class))
24 | interface TestApplicationComponent : ApplicationComponent {
25 |
26 | fun bufferooRepository(): BufferooRepository
27 |
28 | fun postExecutionThread(): PostExecutionThread
29 |
30 | fun inject(application: TestApplication)
31 |
32 | @Component.Builder
33 | interface Builder {
34 | @BindsInstance
35 | fun application(application: Application): TestApplicationComponent.Builder
36 |
37 | fun build(): TestApplicationComponent
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/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.Binds
7 | import dagger.Module
8 | import dagger.Provides
9 | import org.buffer.android.boilerplate.cache.PreferencesHelper
10 | import org.buffer.android.boilerplate.data.executor.JobExecutor
11 | import org.buffer.android.boilerplate.data.repository.BufferooCache
12 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
13 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
14 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
15 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
16 | import org.buffer.android.boilerplate.remote.BufferooService
17 | import org.buffer.android.boilerplate.ui.UiThread
18 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
19 |
20 | @Module
21 | abstract class TestApplicationModule {
22 |
23 | @Binds
24 | abstract fun bindContext(application: Application): Context
25 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/injection/module/TestCacheModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import android.app.Application
4 | import android.arch.persistence.room.Room
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.cache.db.BufferoosDatabase
10 | import org.buffer.android.boilerplate.data.repository.BufferooCache
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | object TestCacheModule {
15 |
16 | @Provides
17 | @JvmStatic
18 | fun provideBufferoosDatabase(application: Application): BufferoosDatabase {
19 | return Room.databaseBuilder(
20 | application.applicationContext,
21 | BufferoosDatabase::class.java, "bufferoos.db")
22 | .build()
23 | }
24 |
25 | @Provides
26 | @JvmStatic
27 | @Singleton
28 | fun provideBufferooCache(): BufferooCache {
29 | return mock()
30 | }
31 |
32 | @Provides
33 | @JvmStatic
34 | @Singleton
35 | fun providePreferencesHelper(): PreferencesHelper {
36 | return mock()
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/injection/module/TestDataModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import dagger.Binds
5 | import dagger.Module
6 | import dagger.Provides
7 | import org.buffer.android.boilerplate.data.executor.JobExecutor
8 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
9 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | abstract class TestDataModule {
14 |
15 | @Module
16 | companion object {
17 |
18 | @Provides
19 | @JvmStatic
20 | @Singleton
21 | fun provideBufferooRepository(): BufferooRepository {
22 | return mock()
23 | }
24 | }
25 |
26 | @Binds
27 | abstract fun bindThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor
28 | }
29 |
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/injection/module/TestRemoteModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import com.nhaarman.mockito_kotlin.mock
4 | import dagger.Module
5 | import dagger.Provides
6 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
7 | import org.buffer.android.boilerplate.remote.BufferooService
8 | import javax.inject.Singleton
9 |
10 | @Module
11 | object TestRemoteModule {
12 |
13 | @Provides
14 | @JvmStatic
15 | @Singleton
16 | fun provideBufferooRemote(): BufferooRemote {
17 | return mock()
18 | }
19 |
20 | @Provides
21 | @JvmStatic
22 | @Singleton
23 | fun provideBufferooService(): BufferooService {
24 | return mock()
25 | }
26 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/util/BufferooFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.util
2 |
3 | import org.buffer.android.boilerplate.domain.model.Bufferoo
4 | import org.buffer.android.boilerplate.presentation.model.BufferooView
5 |
6 | /**
7 | * Factory class for Bufferoo related instances
8 | */
9 | object BufferooFactory {
10 |
11 | fun makeBufferooView(): BufferooView {
12 | return BufferooView(DataFactory.randomUuid(), DataFactory.randomUuid(),
13 | DataFactory.randomUuid())
14 | }
15 |
16 | fun makeBufferooList(count: Int): List {
17 | val bufferoos = mutableListOf()
18 | repeat(count) {
19 | bufferoos.add(BufferooFactory.makeBufferooModel())
20 | }
21 | return bufferoos
22 | }
23 |
24 | fun makeBufferooModel(): Bufferoo {
25 | return Bufferoo(DataFactory.randomLong(), DataFactory.randomUuid(),
26 | DataFactory.randomUuid(), DataFactory.randomUuid())
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/mobile-ui/src/androidTest/java/org/buffer/android/boilerplate/ui/test/util/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.test.util
2 |
3 | import java.util.concurrent.ThreadLocalRandom
4 |
5 | /**
6 | * Factory class for data instances
7 | */
8 | object DataFactory {
9 |
10 | fun randomUuid(): String {
11 | return java.util.UUID.randomUUID().toString()
12 | }
13 |
14 | fun randomInt(): Int {
15 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
16 | }
17 |
18 | fun randomLong(): Long {
19 | return randomInt().toLong()
20 | }
21 |
22 | fun randomBoolean(): Boolean {
23 | return Math.random() < 0.5
24 | }
25 |
26 | fun makeStringList(count: Int): List {
27 | val items: MutableList = mutableListOf()
28 | repeat(count) {
29 | items.add(randomUuid())
30 | }
31 | return items
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.arch.lifecycle.Observer
4 | import android.arch.lifecycle.ViewModelProviders
5 | import android.os.Bundle
6 | import android.support.v7.app.AppCompatActivity
7 | import android.support.v7.widget.LinearLayoutManager
8 | import android.view.View
9 | import dagger.android.AndroidInjection
10 | import kotlinx.android.synthetic.main.activity_browse.*
11 | import org.buffer.android.boilerplate.presentation.ViewModelFactory
12 | import org.buffer.android.boilerplate.presentation.browse.BrowseBufferoosViewModel
13 | import org.buffer.android.boilerplate.presentation.data.ResourceState
14 | import org.buffer.android.boilerplate.presentation.model.BufferooView
15 | import org.buffer.android.boilerplate.ui.R
16 | import org.buffer.android.boilerplate.ui.mapper.BufferooMapper
17 | import org.buffer.android.boilerplate.ui.widget.empty.EmptyListener
18 | import org.buffer.android.boilerplate.ui.widget.error.ErrorListener
19 | import javax.inject.Inject
20 |
21 | class BrowseActivity : AppCompatActivity() {
22 |
23 | @Inject lateinit var browseAdapter: BrowseAdapter
24 | @Inject lateinit var mapper: BufferooMapper
25 | @Inject lateinit var viewModelFactory: ViewModelFactory
26 | private lateinit var browseBufferoosViewModel: BrowseBufferoosViewModel
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | setContentView(R.layout.activity_browse)
31 | AndroidInjection.inject(this)
32 |
33 | browseBufferoosViewModel = ViewModelProviders.of(this, viewModelFactory)
34 | .get(BrowseBufferoosViewModel::class.java)
35 |
36 | setupBrowseRecycler()
37 | setupViewListeners()
38 | }
39 |
40 | override fun onStart() {
41 | super.onStart()
42 | browseBufferoosViewModel.getBufferoos().observe(this, Observer {
43 | if (it != null) this.handleDataState(it.status, it.data, it.message)
44 | })
45 | }
46 |
47 | private fun setupBrowseRecycler() {
48 | recycler_browse.layoutManager = LinearLayoutManager(this)
49 | recycler_browse.adapter = browseAdapter
50 | }
51 |
52 | private fun handleDataState(resourceState: ResourceState, data: List?,
53 | message: String?) {
54 | when (resourceState) {
55 | ResourceState.LOADING -> setupScreenForLoadingState()
56 | ResourceState.SUCCESS -> setupScreenForSuccess(data)
57 | ResourceState.ERROR -> setupScreenForError(message)
58 | }
59 | }
60 |
61 | private fun setupScreenForLoadingState() {
62 | progress.visibility = View.VISIBLE
63 | recycler_browse.visibility = View.GONE
64 | view_empty.visibility = View.GONE
65 | view_error.visibility = View.GONE
66 | }
67 |
68 | private fun setupScreenForSuccess(data: List?) {
69 | view_error.visibility = View.GONE
70 | progress.visibility = View.GONE
71 | if (data != null && data.isNotEmpty()) {
72 | updateListView(data)
73 | recycler_browse.visibility = View.VISIBLE
74 | } else {
75 | view_empty.visibility = View.VISIBLE
76 | }
77 | }
78 |
79 | private fun updateListView(bufferoos: List) {
80 | browseAdapter.bufferoos = bufferoos.map { mapper.mapToViewModel(it) }
81 | browseAdapter.notifyDataSetChanged()
82 | }
83 |
84 | private fun setupScreenForError(message: String?) {
85 | progress.visibility = View.GONE
86 | recycler_browse.visibility = View.GONE
87 | view_empty.visibility = View.GONE
88 | view_error.visibility = View.VISIBLE
89 | }
90 |
91 | private fun setupViewListeners() {
92 | view_empty.emptyListener = emptyListener
93 | view_error.errorListener = errorListener
94 | }
95 |
96 | private val emptyListener = object : EmptyListener {
97 | override fun onCheckAgainClicked() {
98 | browseBufferoosViewModel.fetchBufferoos()
99 | }
100 | }
101 |
102 | private val errorListener = object : ErrorListener {
103 | override fun onTryAgainClicked() {
104 | browseBufferoosViewModel.fetchBufferoos()
105 | }
106 | }
107 |
108 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.*
9 | import javax.inject.Singleton
10 |
11 | @Singleton
12 | @Component(modules = arrayOf(
13 | ApplicationModule::class,
14 | AndroidSupportInjectionModule::class,
15 | CacheModule::class,
16 | DataModule::class,
17 | DomainModule::class,
18 | PresentationModule::class,
19 | RemoteModule::class,
20 | UiModule::class)
21 | )
22 | interface ApplicationComponent {
23 |
24 | @Component.Builder
25 | interface Builder {
26 | @BindsInstance
27 | fun application(application: Application): Builder
28 |
29 | fun build(): ApplicationComponent
30 | }
31 |
32 | fun inject(app: BufferooApplication)
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/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.arch.lifecycle.ViewModelProvider
5 | import android.arch.persistence.room.Room
6 | import android.content.Context
7 | import dagger.Binds
8 | import dagger.Module
9 | import dagger.Provides
10 | import org.buffer.android.boilerplate.cache.BufferooCacheImpl
11 | import org.buffer.android.boilerplate.cache.PreferencesHelper
12 | import org.buffer.android.boilerplate.cache.db.BufferoosDatabase
13 | import org.buffer.android.boilerplate.cache.mapper.BufferooEntityMapper
14 | import org.buffer.android.boilerplate.data.BufferooDataRepository
15 | import org.buffer.android.boilerplate.data.executor.JobExecutor
16 | import org.buffer.android.boilerplate.data.mapper.BufferooMapper
17 | import org.buffer.android.boilerplate.data.repository.BufferooCache
18 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
19 | import org.buffer.android.boilerplate.data.source.BufferooDataStoreFactory
20 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
21 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
22 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
23 | import org.buffer.android.boilerplate.remote.BufferooRemoteImpl
24 | import org.buffer.android.boilerplate.remote.BufferooService
25 | import org.buffer.android.boilerplate.remote.BufferooServiceFactory
26 | import org.buffer.android.boilerplate.ui.BuildConfig
27 | import org.buffer.android.boilerplate.ui.UiThread
28 | import org.buffer.android.boilerplate.ui.injection.scopes.PerApplication
29 |
30 | /**
31 | * Module used to provide dependencies at an application-level.
32 | */
33 | @Module
34 | abstract class ApplicationModule {
35 |
36 | @Binds
37 | abstract fun bindContext(application: Application): Context
38 | }
39 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/CacheModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import android.app.Application
4 | import android.arch.persistence.room.Room
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.Provides
8 | import org.buffer.android.boilerplate.cache.BufferooCacheImpl
9 | import org.buffer.android.boilerplate.cache.db.BufferoosDatabase
10 | import org.buffer.android.boilerplate.data.repository.BufferooCache
11 |
12 | /**
13 | * Module that provides all dependencies from the cache package/layer.
14 | */
15 | @Module
16 | abstract class CacheModule {
17 |
18 | /**
19 | * This companion object annotated as a module is necessary in order to provide dependencies
20 | * statically in case the wrapping module is an abstract class (to use binding)
21 | */
22 | @Module
23 | companion object {
24 |
25 | @Provides
26 | @JvmStatic
27 | fun provideBufferoosDatabase(application: Application): BufferoosDatabase {
28 | return Room.databaseBuilder(
29 | application.applicationContext,
30 | BufferoosDatabase::class.java, "bufferoos.db")
31 | .build()
32 | }
33 | }
34 |
35 | @Binds
36 | abstract fun bindBufferooCache(bufferooCacheImpl: BufferooCacheImpl): BufferooCache
37 | }
38 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/DataModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import org.buffer.android.boilerplate.data.BufferooDataRepository
6 | import org.buffer.android.boilerplate.data.executor.JobExecutor
7 | import org.buffer.android.boilerplate.domain.executor.ThreadExecutor
8 | import org.buffer.android.boilerplate.domain.repository.BufferooRepository
9 |
10 | @Module
11 | abstract class DataModule {
12 |
13 | @Binds
14 | abstract fun bindBufferooRepository(bufferooDataRepository: BufferooDataRepository): BufferooRepository
15 |
16 | @Binds
17 | abstract fun bindThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor
18 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/DomainModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Module
4 |
5 | /**
6 | * Module that provides all dependencies from the domain package/layer.
7 | */
8 | @Module
9 | abstract class DomainModule
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import android.arch.lifecycle.ViewModel
4 | import android.arch.lifecycle.ViewModelProvider
5 | import dagger.Binds
6 | import dagger.MapKey
7 | import dagger.Module
8 | import dagger.multibindings.IntoMap
9 | import org.buffer.android.boilerplate.presentation.ViewModelFactory
10 | import org.buffer.android.boilerplate.presentation.browse.BrowseBufferoosViewModel
11 | import kotlin.reflect.KClass
12 |
13 | /**
14 | * Annotation class to identify view models by classname.
15 | */
16 | @MustBeDocumented
17 | @Target(AnnotationTarget.FUNCTION)
18 | @Retention(AnnotationRetention.RUNTIME)
19 | @MapKey
20 | annotation class ViewModelKey(val value: KClass)
21 |
22 | /**
23 | * Module that provides all dependencies from the presentation package/layer.
24 | */
25 | @Module
26 | abstract class PresentationModule {
27 | @Binds
28 | @IntoMap
29 | @ViewModelKey(BrowseBufferoosViewModel::class)
30 | abstract fun bindBrowseBufferoosViewModel(viewModel: BrowseBufferoosViewModel): ViewModel
31 |
32 | @Binds
33 | abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/RemoteModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.Provides
6 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
7 | import org.buffer.android.boilerplate.remote.BufferooRemoteImpl
8 | import org.buffer.android.boilerplate.remote.BufferooService
9 | import org.buffer.android.boilerplate.remote.BufferooServiceFactory
10 | import org.buffer.android.boilerplate.ui.BuildConfig
11 |
12 | /**
13 | * Module that provides all dependencies from the repository package/layer.
14 | */
15 | @Module
16 | abstract class RemoteModule {
17 |
18 | /**
19 | * This companion object annotated as a module is necessary in order to provide dependencies
20 | * statically in case the wrapping module is an abstract class (to use binding)
21 | */
22 | @Module
23 | companion object {
24 | @Provides
25 | @JvmStatic
26 | fun provideBufferooService(): BufferooService {
27 | return BufferooServiceFactory.makeBuffeoorService(BuildConfig.DEBUG)
28 | }
29 | }
30 |
31 | @Binds
32 | abstract fun bindBufferooRemote(bufferooRemoteImpl: BufferooRemoteImpl): BufferooRemote
33 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/injection/module/UiModule.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.injection.module
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.android.ContributesAndroidInjector
6 | import org.buffer.android.boilerplate.domain.executor.PostExecutionThread
7 | import org.buffer.android.boilerplate.ui.UiThread
8 | import org.buffer.android.boilerplate.ui.browse.BrowseActivity
9 |
10 | /**
11 | * Module that provides all dependencies from the mobile-ui package/layer and injects all activities.
12 | */
13 | @Module
14 | abstract class UiModule {
15 |
16 | @Binds
17 | abstract fun bindPostExecutionThread(uiThread: UiThread): PostExecutionThread
18 |
19 | @ContributesAndroidInjector
20 | abstract fun contributeMainActivity(): BrowseActivity
21 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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/java/org/buffer/android/boilerplate/ui/widget/empty/EmptyListener.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.widget.empty
2 |
3 | interface EmptyListener {
4 |
5 | fun onCheckAgainClicked()
6 |
7 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/widget/empty/EmptyView.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.widget.empty
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.widget.RelativeLayout
7 | import kotlinx.android.synthetic.main.view_empty.view.*
8 | import org.buffer.android.boilerplate.ui.R
9 |
10 | /**
11 | * Widget used to display an empty state to the user
12 | */
13 | class EmptyView: RelativeLayout {
14 |
15 | var emptyListener: EmptyListener? = null
16 |
17 | constructor(context: Context) : super(context) {
18 | init()
19 | }
20 |
21 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
22 | init()
23 | }
24 |
25 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
26 | super(context, attrs, defStyleAttr) {
27 | init()
28 | }
29 |
30 | private fun init() {
31 | LayoutInflater.from(context).inflate(R.layout.view_empty, this)
32 | button_check_again.setOnClickListener { emptyListener?.onCheckAgainClicked() }
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/widget/error/ErrorListener.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.widget.error
2 |
3 | interface ErrorListener {
4 |
5 | fun onTryAgainClicked()
6 |
7 | }
--------------------------------------------------------------------------------
/mobile-ui/src/main/java/org/buffer/android/boilerplate/ui/widget/error/ErrorView.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.ui.widget.error
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.widget.RelativeLayout
7 | import kotlinx.android.synthetic.main.view_error.view.*
8 | import org.buffer.android.boilerplate.ui.R
9 |
10 | /**
11 | * Widget used to display an empty state to the user
12 | */
13 | class ErrorView : RelativeLayout {
14 |
15 | var errorListener: ErrorListener? = null
16 |
17 | constructor(context: Context) : super(context) {
18 | init()
19 | }
20 |
21 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
22 | init()
23 | }
24 |
25 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
26 | super(context, attrs, defStyleAttr) {
27 | init()
28 | }
29 |
30 | private fun init() {
31 | LayoutInflater.from(context).inflate(R.layout.view_error, this)
32 | button_try_again.setOnClickListener { errorListener?.onTryAgainClicked() }
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/activity_browse.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/item_bufferoo.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
25 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/view_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/layout/view_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bufferapp/clean-architecture-components-boilerplate/09f746223d7c852ac5ac26fbd9118e4237b55a9b/mobile-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/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/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Clean Arch Boilerplate
3 |
4 | Oops, there are no results to display
5 | Oops, there was an error fetching the data
6 | Check again
7 | Try again
8 |
9 |
--------------------------------------------------------------------------------
/mobile-ui/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.domain.model.Bufferoo
4 | import org.buffer.android.boilerplate.presentation.model.BufferooView
5 |
6 | /**
7 | * Factory class for Bufferoo related instances
8 | */
9 | object BufferooFactory {
10 |
11 | fun makeBufferooView(): BufferooView {
12 | return BufferooView(DataFactory.randomUuid(), DataFactory.randomUuid(),
13 | DataFactory.randomUuid())
14 | }
15 |
16 | fun makeBufferooList(count: Int): List {
17 | val bufferoos = mutableListOf()
18 | repeat(count) {
19 | bufferoos.add(BufferooFactory.makeBufferooModel())
20 | }
21 | return bufferoos
22 | }
23 |
24 | fun makeBufferooModel(): Bufferoo {
25 | return Bufferoo(DataFactory.randomLong(), DataFactory.randomUuid(),
26 | DataFactory.randomUuid(), DataFactory.randomUuid())
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/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 | object DataFactory {
9 |
10 | fun randomUuid(): String {
11 | return java.util.UUID.randomUUID().toString()
12 | }
13 |
14 | fun randomInt(): Int {
15 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
16 | }
17 |
18 | fun randomLong(): Long {
19 | return randomInt().toLong()
20 | }
21 |
22 | fun randomBoolean(): Boolean {
23 | return Math.random() < 0.5
24 | }
25 |
26 | fun makeStringList(count: Int): List {
27 | val items: MutableList = mutableListOf()
28 | repeat(count) {
29 | items.add(randomUuid())
30 | }
31 | return items
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 |
5 | android {
6 | def globalConfiguration = rootProject.extensions.getByName("ext")
7 |
8 | compileSdkVersion globalConfiguration["androidCompileSdkVersion"]
9 | buildToolsVersion globalConfiguration["androidBuildToolsVersion"]
10 |
11 | defaultConfig {
12 | minSdkVersion globalConfiguration["androidMinSdkVersion"]
13 | targetSdkVersion globalConfiguration["androidTargetSdkVersion"]
14 | multiDexEnabled = true
15 | }
16 |
17 | dexOptions {
18 | preDexLibraries = false
19 | dexInProcess = false
20 | javaMaxHeapSize "4g"
21 | }
22 |
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_7
25 | targetCompatibility JavaVersion.VERSION_1_7
26 | }
27 |
28 | packagingOptions {
29 | exclude 'LICENSE.txt'
30 | exclude 'META-INF/DEPENDENCIES'
31 | exclude 'META-INF/ASL2.0'
32 | exclude 'META-INF/NOTICE'
33 | exclude 'META-INF/LICENSE'
34 | }
35 |
36 | lintOptions {
37 | quiet true
38 | abortOnError false
39 | ignoreWarnings true
40 | disable 'InvalidPackage' //Some libraries have issues with this.
41 | disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta.
42 | disable 'IconDensities' //For testing purpose. This is safe to remove.
43 | disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove.
44 | }
45 |
46 | }
47 |
48 | kapt {
49 | correctErrorTypes = true
50 | generateStubs = true
51 | }
52 |
53 | dependencies {
54 | def presentationDependencies = rootProject.ext.presentationDependencies
55 | def presentationTestDependencies = rootProject.ext.presentationTestDependencies
56 |
57 | compile presentationDependencies.javaxAnnotation
58 |
59 | compile presentationDependencies.kotlin
60 | compile presentationDependencies.javaxInject
61 | compile presentationDependencies.rxKotlin
62 | compile presentationDependencies.archRuntime
63 | compile presentationDependencies.archExtensions
64 | kapt presentationDependencies.archCompiler
65 |
66 | testImplementation presentationTestDependencies.junit
67 | testImplementation presentationTestDependencies.mockito
68 | testImplementation presentationTestDependencies.assertj
69 | testImplementation presentationTestDependencies.robolectric
70 | testImplementation "android.arch.core:core-testing:1.0.0-alpha9-1"
71 |
72 | compile project(':domain')
73 | }
--------------------------------------------------------------------------------
/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation
2 |
3 | import android.arch.lifecycle.ViewModel
4 | import android.arch.lifecycle.ViewModelProvider
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class ViewModelFactory
11 | @Inject constructor(private val creators: MutableMap, Provider>)
12 | : ViewModelProvider.Factory {
13 |
14 | @Suppress("UNCHECKED_CAST")
15 | override fun create(modelClass: Class): T {
16 | var creator: Provider? = creators[modelClass]
17 | if (creator == null) {
18 | for ((key, value) in creators.entries) {
19 | if (modelClass.isAssignableFrom(key)) {
20 | creator = value
21 | break
22 | }
23 | }
24 | }
25 | if (creator == null) {
26 | throw IllegalArgumentException("unknown model class " + modelClass)
27 | }
28 | try {
29 | return creator.get() as T
30 | } catch (e: Exception) {
31 | throw RuntimeException(e)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosViewModel.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.browse
2 |
3 | import android.arch.lifecycle.LiveData
4 | import android.arch.lifecycle.MutableLiveData
5 | import android.arch.lifecycle.ViewModel
6 | import io.reactivex.subscribers.DisposableSubscriber
7 | import org.buffer.android.boilerplate.domain.interactor.browse.GetBufferoos
8 | import org.buffer.android.boilerplate.domain.model.Bufferoo
9 | import org.buffer.android.boilerplate.presentation.data.Resource
10 | import org.buffer.android.boilerplate.presentation.data.ResourceState
11 | import org.buffer.android.boilerplate.presentation.mapper.BufferooMapper
12 | import org.buffer.android.boilerplate.presentation.model.BufferooView
13 | import javax.inject.Inject
14 |
15 | open class BrowseBufferoosViewModel @Inject internal constructor(
16 | private val getBufferoos: GetBufferoos,
17 | private val bufferooMapper: BufferooMapper) : ViewModel() {
18 |
19 | private val bufferoosLiveData: MutableLiveData>> =
20 | MutableLiveData()
21 |
22 | init {
23 | fetchBufferoos()
24 | }
25 |
26 | override fun onCleared() {
27 | getBufferoos.dispose()
28 | super.onCleared()
29 | }
30 |
31 | fun getBufferoos(): LiveData>> {
32 | return bufferoosLiveData
33 | }
34 |
35 | fun fetchBufferoos() {
36 | bufferoosLiveData.postValue(Resource(ResourceState.LOADING, null, null))
37 | return getBufferoos.execute(BufferooSubscriber())
38 | }
39 |
40 | inner class BufferooSubscriber: DisposableSubscriber>() {
41 |
42 | override fun onComplete() { }
43 |
44 | override fun onNext(t: List) {
45 | bufferoosLiveData.postValue(Resource(ResourceState.SUCCESS,
46 | t.map { bufferooMapper.mapToView(it) }, null))
47 | }
48 |
49 | override fun onError(exception: Throwable) {
50 | bufferoosLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
51 | }
52 |
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/data/Resource.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.data
2 |
3 | open class Resource constructor(val status: ResourceState, val data: T?, val message: String?) {
4 |
5 | fun success(data: T): Resource {
6 | return Resource(ResourceState.SUCCESS, data, null)
7 | }
8 |
9 | fun error(message: String, data: T?): Resource {
10 | return Resource(ResourceState.ERROR, null, message)
11 | }
12 |
13 | fun loading(): Resource {
14 | return Resource(ResourceState.LOADING, null, null)
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/org/buffer/android/boilerplate/presentation/data/ResourceState.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.data
2 |
3 | /**
4 | * Represents the state in which a [Resource] is currently in
5 | */
6 | enum class ResourceState {
7 | LOADING, SUCCESS, ERROR
8 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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)
--------------------------------------------------------------------------------
/presentation/src/test/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.browse
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule
4 | import com.nhaarman.mockito_kotlin.*
5 | import io.reactivex.subscribers.DisposableSubscriber
6 | import org.buffer.android.boilerplate.domain.interactor.browse.GetBufferoos
7 | import org.buffer.android.boilerplate.domain.model.Bufferoo
8 | import org.buffer.android.boilerplate.presentation.data.ResourceState
9 | import org.buffer.android.boilerplate.presentation.mapper.BufferooMapper
10 | import org.buffer.android.boilerplate.presentation.model.BufferooView
11 | import org.buffer.android.boilerplate.presentation.test.factory.BufferooFactory
12 | import org.buffer.android.boilerplate.presentation.test.factory.DataFactory
13 | import org.junit.Before
14 | import org.junit.Rule
15 | import org.junit.Test
16 | import org.junit.runner.RunWith
17 | import org.junit.runners.JUnit4
18 | import org.mockito.Captor
19 | import org.mockito.Mock
20 |
21 | @RunWith(JUnit4::class)
22 | class BrowseBufferoosViewModelTest {
23 |
24 | @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule()
25 |
26 | @Mock lateinit var getBufferoos: GetBufferoos
27 | @Mock lateinit var bufferooMapper: BufferooMapper
28 |
29 | @Captor
30 | private lateinit var captor: KArgumentCaptor>>
31 |
32 | private lateinit var bufferoosViewModel: BrowseBufferoosViewModel
33 |
34 | @Before
35 | fun setUp() {
36 | captor = argumentCaptor>>()
37 | getBufferoos = mock()
38 | bufferooMapper = mock()
39 | bufferoosViewModel = BrowseBufferoosViewModel(getBufferoos, bufferooMapper)
40 | }
41 |
42 | @Test
43 | fun getBufferoosExecutesUseCase() {
44 | bufferoosViewModel.getBufferoos()
45 |
46 | verify(getBufferoos, times(1)).execute(any(), anyOrNull())
47 | }
48 |
49 | //
50 | @Test
51 | fun getBufferoosReturnsSuccess() {
52 | val list = BufferooFactory.makeBufferooList(2)
53 | val viewList = BufferooFactory.makeBufferooViewList(2)
54 | stubBufferooMapperMapToView(viewList[0], list[0])
55 | stubBufferooMapperMapToView(viewList[1], list[1])
56 |
57 | bufferoosViewModel.getBufferoos()
58 |
59 | verify(getBufferoos).execute(captor.capture(), eq(null))
60 | captor.firstValue.onNext(list)
61 |
62 | assert(bufferoosViewModel.getBufferoos().value?.status == ResourceState.SUCCESS)
63 | }
64 |
65 | @Test
66 | fun getBufferoosReturnsDataOnSuccess() {
67 | val list = BufferooFactory.makeBufferooList(2)
68 | val viewList = BufferooFactory.makeBufferooViewList(2)
69 |
70 | stubBufferooMapperMapToView(viewList[0], list[0])
71 | stubBufferooMapperMapToView(viewList[1], list[1])
72 |
73 | bufferoosViewModel.getBufferoos()
74 |
75 | verify(getBufferoos).execute(captor.capture(), eq(null))
76 | captor.firstValue.onNext(list)
77 |
78 | assert(bufferoosViewModel.getBufferoos().value?.data == viewList)
79 | }
80 |
81 | @Test
82 | fun getBufferoosReturnsNoMessageOnSuccess() {
83 | val list = BufferooFactory.makeBufferooList(2)
84 | val viewList = BufferooFactory.makeBufferooViewList(2)
85 |
86 | stubBufferooMapperMapToView(viewList[0], list[0])
87 | stubBufferooMapperMapToView(viewList[1], list[1])
88 |
89 | bufferoosViewModel.getBufferoos()
90 |
91 | verify(getBufferoos).execute(captor.capture(), eq(null))
92 | captor.firstValue.onNext(list)
93 |
94 | assert(bufferoosViewModel.getBufferoos().value?.message == null)
95 | }
96 | //
97 |
98 | //
99 | @Test
100 | fun getBufferoosReturnsError() {
101 | bufferoosViewModel.getBufferoos()
102 |
103 | verify(getBufferoos).execute(captor.capture(), eq(null))
104 | captor.firstValue.onError(RuntimeException())
105 |
106 | assert(bufferoosViewModel.getBufferoos().value?.status == ResourceState.ERROR)
107 | }
108 |
109 | @Test
110 | fun getBufferoosFailsAndContainsNoData() {
111 | bufferoosViewModel.getBufferoos()
112 |
113 | verify(getBufferoos).execute(captor.capture(), eq(null))
114 | captor.firstValue.onError(RuntimeException())
115 |
116 | assert(bufferoosViewModel.getBufferoos().value?.data == null)
117 | }
118 |
119 | @Test
120 | fun getBufferoosFailsAndContainsMessage() {
121 | val errorMessage = DataFactory.randomUuid()
122 | bufferoosViewModel.getBufferoos()
123 |
124 | verify(getBufferoos).execute(captor.capture(), eq(null))
125 | captor.firstValue.onError(RuntimeException(errorMessage))
126 |
127 | assert(bufferoosViewModel.getBufferoos().value?.message == errorMessage)
128 | }
129 | //
130 |
131 | //
132 | @Test
133 | fun getBufferoosReturnsLoading() {
134 | bufferoosViewModel.getBufferoos()
135 |
136 | assert(bufferoosViewModel.getBufferoos().value?.status == ResourceState.LOADING)
137 | }
138 |
139 | @Test
140 | fun getBufferoosContainsNoDataWhenLoading() {
141 | bufferoosViewModel.getBufferoos()
142 |
143 | assert(bufferoosViewModel.getBufferoos().value?.data == null)
144 | }
145 |
146 | @Test
147 | fun getBufferoosContainsNoMessageWhenLoading() {
148 | bufferoosViewModel.getBufferoos()
149 |
150 | assert(bufferoosViewModel.getBufferoos().value?.data == null)
151 | }
152 | //
153 |
154 | private fun stubBufferooMapperMapToView(bufferooView: BufferooView,
155 | bufferoo: Bufferoo) {
156 | whenever(bufferooMapper.mapToView(bufferoo))
157 | .thenReturn(bufferooView)
158 | }
159 |
160 | }
--------------------------------------------------------------------------------
/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.model.BufferooView
5 | import org.buffer.android.boilerplate.presentation.test.factory.DataFactory.Factory.randomLong
6 | import org.buffer.android.boilerplate.presentation.test.factory.DataFactory.Factory.randomUuid
7 |
8 | /**
9 | * Factory class for Bufferoo related instances
10 | */
11 | class BufferooFactory {
12 |
13 | companion object Factory {
14 |
15 | fun makeBufferooList(count: Int): List {
16 | val bufferoos = mutableListOf()
17 | repeat(count) {
18 | bufferoos.add(makeBufferooModel())
19 | }
20 | return bufferoos
21 | }
22 |
23 | fun makeBufferooModel(): Bufferoo {
24 | return Bufferoo(randomLong(), randomUuid(), randomUuid(), randomUuid())
25 | }
26 |
27 | fun makeBufferooViewList(count: Int): List {
28 | val bufferoos = mutableListOf()
29 | repeat(count) {
30 | bufferoos.add(makeBufferooView())
31 | }
32 | return bufferoos
33 | }
34 |
35 | fun makeBufferooView(): BufferooView {
36 | return BufferooView(randomUuid(), randomUuid(), randomUuid())
37 | }
38 |
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/org/buffer/android/boilerplate/presentation/test/factory/DataFactory.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.presentation.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 randomInt(): Int {
13 | return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
14 | }
15 |
16 | fun randomLong(): Long {
17 | return randomInt().toLong()
18 | }
19 |
20 | fun randomUuid(): String {
21 | return java.util.UUID.randomUUID().toString()
22 | }
23 |
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/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 Components Boilerplate
4 |
5 |
6 | Note: This is a fork of our original [Clean Architecture Boilerplate](https://github.com/bufferapp/android-clean-architecture-boilerplate), except in this repo we have switched out the MVP approach found in the presentation layer to now use ViewModels from the Android Architecture Components Library.
7 | The caching layer now also uses Room.
8 |
9 |
10 | 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:
11 |
12 | 1. To experiment with modularisation
13 | 2. To experiment with the Android Architecture Components
14 | 3. 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/)
15 | 4. To use as a starting point in future projects where clean architecture feels appropriate
16 |
17 | It is written 100% in Kotlin with both UI and Unit tests - we will also be keeping this up-to-date as libraries change!
18 |
19 | ### Disclaimer
20 |
21 | 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.
22 |
23 | Clean Architecture will not be appropriate for every project, so it is down to you to decide whether or not it fits your needs 🙂
24 |
25 | ## Languages, libraries and tools used
26 |
27 | * [Kotlin](https://kotlinlang.org/)
28 | * [Room](https://developer.android.com/topic/libraries/architecture/room.html)
29 | * [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html)
30 | * Android Support Libraries
31 | * [RxJava2](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0)
32 | * [Dagger 2 (2.11)](https://github.com/google/dagger)
33 | * [Glide](https://github.com/bumptech/glide)
34 | * [Retrofit](http://square.github.io/retrofit/)
35 | * [OkHttp](http://square.github.io/okhttp/)
36 | * [Gson](https://github.com/google/gson)
37 | * [Timber](https://github.com/JakeWharton/timber)
38 | * [Mockito](http://site.mockito.org/)
39 | * [Espresso](https://developer.android.com/training/testing/espresso/index.html)
40 | * [Robolectric](http://robolectric.org/)
41 |
42 | ## Requirements
43 |
44 | * JDK 1.8
45 | * [Android SDK](https://developer.android.com/studio/index.html)
46 | * Android O ([API 26](https://developer.android.com/preview/api-overview.html))
47 | * Latest Android SDK Tools and build tools.
48 |
49 | ## Architecture
50 |
51 | The architecture of the project follows the principles of Clean Architecture. Here's how the sample project implements it:
52 |
53 | 
54 |
55 | The sample app when run will show you a simple list of all the Bufferoos (Buffer team members!).
56 |
57 |
58 |
59 |
60 | Let's look at each of the architecture layers and the role each one plays :)
61 |
62 | 
63 |
64 | ### User Interface
65 |
66 | 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 [BrowseBufferoosViewModel](https://github.com/bufferapp/clean-architecture-components-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosViewModel.kt) to retrieve data.
67 |
68 | ### Presentation
69 |
70 | This layer's responsibility 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 dependence on the Android Framework, it is a pure Kotlin module. Each ViewModel class that is created implements the ViewModel class found within the Architecture components library. This ViewModel can then be used by the UI layer to communicate with UseCases and retrieve data. The [BrowseBufferoosViewModel](https://github.com/bufferapp/clean-architecture-components-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/browse/BrowseBufferoosViewModel.kt) returns an instance of a [Resource](https://github.com/bufferapp/clean-architecture-components-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/data/Resource.kt) which contains data that can be used by the UI - this includes the [ResourceState](https://github.com/bufferapp/clean-architecture-components-boilerplate/blob/master/presentation/src/main/java/org/buffer/android/boilerplate/presentation/data/ResourceState.kt), data to be used by the UI and a message if required (for error states).
71 |
72 | The ViewModels use an instance of a [FlowableUseCase](https://github.com/bufferapp/clean-architecture-components-boilerplate/blob/master/domain/src/main/java/org/buffer/android/boilerplate/domain/interactor/FlowableUseCase.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.
73 |
74 | The ViewModel 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).
75 |
76 | ### Domain
77 |
78 | 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/ObservableUseCase.kt) base class - therefore we can reference it from outer layers and avoid a direct reference to a specific implementation.
79 |
80 | 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.
81 |
82 | 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.
83 |
84 | 
85 |
86 | ### Data
87 |
88 | 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.
89 |
90 | 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.
91 |
92 | 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.
93 |
94 | ### Remote
95 |
96 | 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.
97 |
98 | 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.
99 |
100 | ### Cache
101 |
102 | The Cache layer handles all communication with the local database which is used to cache data.
103 |
104 | 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.
105 |
106 | ## Conclusion
107 |
108 | 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 🙂
109 |
110 | 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 every 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 project that you are working on 🙌🏻
111 |
112 | ## Thanks
113 |
114 | A special thanks to the authors involved with these two repositories, they were a great resource during our learning!
115 |
116 | - https://github.com/android10/Android-CleanArchitecture
117 |
118 | - https://github.com/googlesamples/android-architecture
119 |
--------------------------------------------------------------------------------
/remote/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *iml
3 | *.iml
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import io.reactivex.Flowable
4 | import io.reactivex.Observable
5 | import org.buffer.android.boilerplate.data.model.BufferooEntity
6 | import org.buffer.android.boilerplate.data.repository.BufferooRemote
7 | import org.buffer.android.boilerplate.remote.mapper.BufferooEntityMapper
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Remote implementation for retrieving Bufferoo instances. This class implements the
12 | * [BufferooRemote] from the Data layer as it is that layers responsibility for defining the
13 | * operations in which data store implementation layers can carry out.
14 | */
15 | class BufferooRemoteImpl @Inject constructor(private val bufferooService: BufferooService,
16 | private val entityMapper: BufferooEntityMapper):
17 | BufferooRemote {
18 |
19 | /**
20 | * Retrieve a list of [BufferooEntity] instances from the [BufferooService].
21 | */
22 | override fun getBufferoos(): Flowable> {
23 | return bufferooService.getBufferoos()
24 | .map { it.team }
25 | .map {
26 | val entities = mutableListOf()
27 | it.forEach { entities.add(entityMapper.mapFromRemote(it)) }
28 | entities
29 | }
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/remote/src/main/java/org/buffer/android/boilerplate/remote/BufferooService.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.boilerplate.remote
2 |
3 | import io.reactivex.Flowable
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(): Flowable
14 |
15 | class BufferooResponse {
16 | lateinit var team: List
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.id, type.name, type.title, type.avatar)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 id: Long, val name: String, val title: String, val avatar: String)
--------------------------------------------------------------------------------
/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.Flowable
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(Flowable.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(Flowable.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(observable:
52 | Flowable) {
53 | whenever(bufferooService.getBufferoos())
54 | .thenReturn(observable)
55 | }
56 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.BufferooService
4 | import org.buffer.android.boilerplate.remote.model.BufferooModel
5 | import org.buffer.android.boilerplate.remote.test.factory.DataFactory.Factory.randomLong
6 | import org.buffer.android.boilerplate.remote.test.factory.DataFactory.Factory.randomUuid
7 |
8 | /**
9 | * Factory class for Bufferoo related instances
10 | */
11 | class BufferooFactory {
12 |
13 | companion object Factory {
14 |
15 | fun makeBufferooResponse(): BufferooService.BufferooResponse {
16 | val bufferooResponse = BufferooService.BufferooResponse()
17 | bufferooResponse.team = makeBufferooModelList(5)
18 | return bufferooResponse
19 | }
20 |
21 | fun makeBufferooModelList(count: Int): List {
22 | val bufferooEntities = mutableListOf()
23 | repeat(count) {
24 | bufferooEntities.add(makeBufferooModel())
25 | }
26 | return bufferooEntities
27 | }
28 |
29 | fun makeBufferooModel(): BufferooModel {
30 | return BufferooModel(randomLong(), randomUuid(), randomUuid(), randomUuid())
31 | }
32 |
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':cache', ':remote', ':data', ':domain', ':presentation', ':mobile-ui'
2 |
--------------------------------------------------------------------------------