diff --git a/.circleci/config.yml b/.circleci/config.yml
index edbe35c..e4542dd 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -76,12 +76,60 @@ jobs:
- store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
path: features/detail/build/test-results
- ## Repository
+ ## Abstractions
- run:
- name: Test Repository
+ name: Test Abstractions
command: |
- fastlane test_repository
- ./gradlew repository:jacocoTestReport
+ fastlane test_abstractions
+ ./gradlew abstractions:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: repository/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: repository/build/test-results
+
+ ## Definitions
+ - run:
+ name: Test Definitions
+ command: |
+ fastlane test_definitions
+ ./gradlew data:definitions:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: repository/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: repository/build/test-results
+
+ ## Interactors
+ - run:
+ name: Test Interactors
+ command: |
+ fastlane test_interactors
+ ./gradlew data:interactors:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: repository/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: repository/build/test-results
+
+ ## Network
+ - run:
+ name: Test Network
+ command: |
+ fastlane test_network
+ ./gradlew data:network:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: repository/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: repository/build/test-results
+
+ ## Persistence
+ - run:
+ name: Test Persistence
+ command: |
+ fastlane test_persistence
+ ./gradlew data:persistence:jacocoTestReport
bash <(curl -s https://codecov.io/bash)
- store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
path: repository/build/reports/tests
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 0972801..13de4f6 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -55,8 +55,32 @@ jobs:
./gradlew features:detail:jacocoTestReport
bash <(curl -s https://codecov.io/bash)
- - name: Test Repository
+ - name: Test Abstractions
run: |
- fastlane test_repository
- ./gradlew repository:jacocoTestReport
+ fastlane test_abstractions
+ ./gradlew abstractions:jacocoTestReport
bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Definitions
+ run: |
+ fastlane test_definitions
+ ./gradlew data:definitions:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Interactors
+ run: |
+ fastlane test_interactors
+ ./gradlew data:interactors:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Network
+ run: |
+ fastlane test_network
+ ./gradlew data:network:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Persistence
+ run: |
+ fastlane test_persistence
+ ./gradlew data:persistence:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3a8ee31..4cdb0a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
/.idea
.DS_Store
/build
+**/build
/fastlane/README.md
/fastlane/report.xml
/captures
diff --git a/Gemfile.lock b/Gemfile.lock
index b9db40d..192c06b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,7 +5,7 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
- babosa (1.0.2)
+ babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
@@ -18,7 +18,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.66.0)
+ excon (0.67.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
- fastlane (2.131.0)
+ fastlane (2.133.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -37,9 +37,9 @@ GEM
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
- faraday (~> 0.9)
+ faraday (< 0.16.0)
faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (~> 0.9)
+ faraday_middleware (< 0.16.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
@@ -52,7 +52,7 @@ GEM
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
- rubyzip (>= 1.2.2, < 2.0.0)
+ rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
@@ -98,7 +98,7 @@ GEM
memoist (0.16.0)
mime-types (3.3)
mime-types-data (~> 3.2015)
- mime-types-data (3.2019.0904)
+ mime-types-data (3.2019.1009)
mini_magick (4.9.5)
multi_json (1.13.1)
multi_xml (0.6.0)
@@ -114,7 +114,7 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
- rubyzip (1.2.4)
+ rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
diff --git a/core/.gitignore b/abstractions/.gitignore
similarity index 100%
rename from core/.gitignore
rename to abstractions/.gitignore
diff --git a/abstractions/build.gradle b/abstractions/build.gradle
new file mode 100644
index 0000000..86354c0
--- /dev/null
+++ b/abstractions/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: "de.mannodermaus.android-junit5"
+
+apply from: "$rootProject.projectDir/scripts/module.gradle"
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation libraries.kotlin
+
+ testImplementation testLibraries.jUnitApi
+ testImplementation testLibraries.mockk
+ testImplementation testLibraries.kluent
+
+ testRuntimeOnly testLibraries.jUnitEngine
+}
diff --git a/repository/proguard-rules.pro b/abstractions/consumer-rules.pro
similarity index 100%
rename from repository/proguard-rules.pro
rename to abstractions/consumer-rules.pro
diff --git a/abstractions/proguard-rules.pro b/abstractions/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/abstractions/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/abstractions/src/main/AndroidManifest.xml b/abstractions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c706bc8
--- /dev/null
+++ b/abstractions/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt b/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt
new file mode 100644
index 0000000..6d9668e
--- /dev/null
+++ b/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt
@@ -0,0 +1,3 @@
+package com.melih.abstractions.data
+
+interface ViewEntity
diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt
new file mode 100644
index 0000000..030a02e
--- /dev/null
+++ b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt
@@ -0,0 +1,6 @@
+package com.melih.abstractions.deliverable
+
+abstract class Reason : Throwable() {
+
+ abstract val messageRes: Int
+}
diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt
similarity index 70%
rename from repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt
rename to abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt
index e34eea7..f81e27e 100644
--- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt
+++ b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt
@@ -1,12 +1,9 @@
-package com.melih.repository.interactors.base
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+package com.melih.abstractions.deliverable
/**
- * Result class that wraps any [Success], [Failure] or [State] that can be generated by any derivation of [BaseInteractor]
+ * Result class that wraps any [Success], [Failure] or [State]
*/
-@UseExperimental(ExperimentalCoroutinesApi::class)
sealed class Result
//region Subclasses
@@ -22,7 +19,11 @@ sealed class State : Result() {
//region Extensions
-inline fun Result.handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) {
+inline fun Result.handle(
+ stateBlock: (State) -> Unit,
+ failureBlock: (Reason) -> Unit,
+ successBlock: (T) -> Unit
+) {
when (this) {
is Success -> successBlock(successData)
is Failure -> failureBlock(errorData)
diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt
new file mode 100644
index 0000000..aac38d2
--- /dev/null
+++ b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt
@@ -0,0 +1,8 @@
+package com.melih.abstractions.mapper
+
+import com.melih.abstractions.data.ViewEntity
+
+abstract class Mapper {
+
+ abstract fun convert(t: T): R
+}
diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt b/abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt
similarity index 80%
rename from repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt
rename to abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt
index cc770df..a21f935 100644
--- a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt
+++ b/abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt
@@ -1,6 +1,10 @@
-package com.melih.repository.interactors.base
+package com.melih.abstractions
-import com.melih.repository.R
+import com.melih.abstractions.deliverable.Failure
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.State
+import com.melih.abstractions.deliverable.Success
+import com.melih.abstractions.deliverable.handle
import io.mockk.called
import io.mockk.spyk
import io.mockk.verify
@@ -8,12 +12,17 @@ import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldEqualTo
import org.junit.jupiter.api.Test
+
class ResultTest {
private val number = 10
private val success = Success(number)
- private val failure = Failure(GenericError())
+ private val failure = Failure(object : Reason() {
+ override val messageRes: Int
+ get() = 10
+
+ })
private val state = State.Loading()
private val emptyStateBlock = spyk({ _: State -> })
@@ -37,8 +46,7 @@ class ResultTest {
@Test
fun `Failure should only invoke failureBlock with correct error`() {
val actualFailureBlock = spyk({ reason: Reason ->
- reason shouldBeInstanceOf GenericError::class
- (reason as GenericError).messageRes shouldEqualTo R.string.reason_generic
+ reason.messageRes shouldEqualTo 10
Unit
})
diff --git a/app/build.gradle b/app/build.gradle
index 4eb083f..4d1af18 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply from: "$rootProject.projectDir/scripts/default_android_config.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
apply from: "$rootProject.projectDir/scripts/sources.gradle"
android {
@@ -31,10 +32,14 @@ dependencies {
androidTestImplementation testLibraries.espresso
- // These libraries required by dagger to create dependency graph, but not by app
- compileOnly project(':repository')
+ // These libraries required by dagger to create dependency graph and application compilation, but not used by app
+
+ compileOnly project(':abstractions')
+ compileOnly project(':data:interactors')
+ compileOnly project(':data:network')
+ compileOnly project(':data:definitions')
+
compileOnly libraries.retrofit
- compileOnly libraries.room
compileOnly libraries.paging
compileOnly libraries.swipeRefreshLayout
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index da2a358..ce0ec64 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -21,8 +21,5 @@
@com.squareup.moshi.ToJson ;
}
--keepnames @kotlin.Metadata class com.myapp.packagename.model.**
--keep class com.myapp.packagnename.model.** { *; }
-
# Keeping entities intact
--keep class com.melih.repository.entities.** { *; }
+-keep class com.melih.definitions.entities.** { *; }
diff --git a/build.gradle b/build.gradle
index 232dd0a..d709464 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.50'
- ext.nav_version = '2.2.0-alpha01'
+ ext.nav_version = '2.2.0-beta01'
repositories {
google()
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18"
classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0"
@@ -20,9 +20,9 @@ buildscript {
}
plugins {
- id "io.gitlab.arturbosch.detekt" version "1.0.0"
- id "org.jetbrains.dokka" version "0.9.18"
- id "jacoco"
+ id 'io.gitlab.arturbosch.detekt' version '1.1.1'
+ id 'org.jetbrains.dokka' version '0.9.18'
+ id 'jacoco'
}
allprojects {
@@ -97,7 +97,9 @@ task projectDependencyGraph {
rootProjects.remove(dependency)
def graphKey = new Tuple2(project, dependency)
- def traits = dependencies.computeIfAbsent(graphKey) { new ArrayList() }
+ def traits = dependencies.computeIfAbsent(graphKey) {
+ new ArrayList()
+ }
if (config.name.toLowerCase().endsWith('implementation')) {
traits.add('style=dotted')
diff --git a/core/build.gradle b/core/build.gradle
index ef8cb83..0d9ae40 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply from: "$rootProject.projectDir/scripts/default_android_config.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
apply from: "$rootProject.projectDir/scripts/sources.gradle"
android {
@@ -14,8 +15,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':repository')
-
+ implementation libraries.coroutines
implementation libraries.fragment
implementation libraries.paging
implementation libraries.lifecycle
diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt
index 53e1d0a..368a89e 100644
--- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt
+++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt
@@ -9,8 +9,8 @@ import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
+import com.melih.abstractions.deliverable.Reason
import com.melih.core.R
-import com.melih.repository.interactors.base.Reason
/**
* Parent of all fragments.
diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt
index ff1ff69..a004f0e 100644
--- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt
+++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt
@@ -4,12 +4,13 @@ import androidx.annotation.CallSuper
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.PageKeyedDataSource
-import com.melih.repository.interactors.base.Reason
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.State
-import com.melih.repository.interactors.base.onFailure
-import com.melih.repository.interactors.base.onState
-import com.melih.repository.interactors.base.onSuccess
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.deliverable.State
+import com.melih.abstractions.deliverable.onFailure
+import com.melih.abstractions.deliverable.onState
+import com.melih.abstractions.deliverable.onSuccess
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,11 +36,11 @@ const val INITIAL_PAGE = 0
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
-abstract class BasePagingDataSource : PageKeyedDataSource() {
+abstract class BasePagingDataSource : PageKeyedDataSource() {
//region Abstractions
- abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s)
+ abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s)
//endregion
//region Properties
@@ -63,7 +64,10 @@ abstract class BasePagingDataSource : PageKeyedDataSource() {
//region Functions
- override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback
+ ) {
// Looping through channel as we'll receive any state, error or data here
loadDataForPage(INITIAL_PAGE)
.onEach { result ->
@@ -81,7 +85,7 @@ abstract class BasePagingDataSource : PageKeyedDataSource() {
.launchIn(coroutineScope)
}
- override fun loadAfter(params: LoadParams, callback: LoadCallback) {
+ override fun loadAfter(params: LoadParams, callback: LoadCallback) {
// Key for which page to load is in params
val page = params.key
@@ -104,7 +108,7 @@ abstract class BasePagingDataSource : PageKeyedDataSource() {
/**
* This loads previous pages, we don't have a use for it yet, so it's a no-op override
*/
- override fun loadBefore(params: LoadParams, callback: LoadCallback) {
+ override fun loadBefore(params: LoadParams, callback: LoadCallback) {
// no-op
}
diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt
index 1a51f1e..ddd12b6 100644
--- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt
+++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt
@@ -3,6 +3,7 @@ package com.melih.core.base.paging
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
+import com.melih.abstractions.data.ViewEntity
/**
* Base [factory][DataSource.Factory] class for any [dataSource][DataSource]s in project.
@@ -14,7 +15,7 @@ import androidx.paging.DataSource
*
* Purpose of this transmission is to encapuslate [basePagingDataSource][BasePagingDataSource].
*/
-abstract class BasePagingFactory : DataSource.Factory() {
+abstract class BasePagingFactory : DataSource.Factory() {
//region Abstractions
diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt
index 413a602..7a0caad 100644
--- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt
+++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt
@@ -5,9 +5,10 @@ import androidx.lifecycle.Transformations.switchMap
import androidx.lifecycle.ViewModel
import androidx.paging.PagedList
import androidx.paging.toLiveData
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.State
import com.melih.core.base.paging.BasePagingFactory
-import com.melih.repository.interactors.base.Reason
-import com.melih.repository.interactors.base.State
/**
* Base [ViewModel] for view models that will use [PagedList].
@@ -18,7 +19,7 @@ import com.melih.repository.interactors.base.State
*
* If paging won't be used, use [BaseViewModel] instead.
*/
-abstract class BasePagingViewModel : ViewModel() {
+abstract class BasePagingViewModel : ViewModel() {
//region Abstractions
diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt
index 6e7c440..525a772 100644
--- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt
+++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt
@@ -4,8 +4,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.melih.repository.interactors.base.Reason
-import com.melih.repository.interactors.base.State
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.State
import kotlinx.coroutines.launch
/**
diff --git a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt b/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt
deleted file mode 100644
index c57840f..0000000
--- a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.melih.core.extensions
-
-import android.view.MenuItem
-import androidx.appcompat.widget.SearchView
-import com.melih.core.utils.ClearFocusQueryTextListener
-
-/**
- * Shorthand for [contains] with ignoreCase set [true]
- */
-fun CharSequence.containsIgnoreCase(other: CharSequence) = contains(other, true)
-
-/**
- * Adds [ClearFocusQueryTextListener] as [SearchView.OnQueryTextListener]
- */
-fun SearchView.setOnQueryChangedListener(block: (String?) -> Unit) = setOnQueryTextListener(ClearFocusQueryTextListener(this, block))
-
-/**
- * Shortening set menu item expands / collapses
- */
-fun MenuItem.onExpandOrCollapse(onExpand: () -> Unit, onCollapse: () -> Unit) {
- setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
- override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
- onCollapse()
- return true
- }
-
- override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
- onExpand()
- return true
- }
- })
-}
diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt
index 5750ad1..700bbfb 100644
--- a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt
+++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt
@@ -3,14 +3,15 @@
package com.melih.core.paging
import androidx.paging.PageKeyedDataSource
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Failure
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.deliverable.State
+import com.melih.abstractions.deliverable.Success
import com.melih.core.BaseTestWithMainThread
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.testObserve
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.GenericError
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.State
-import com.melih.repository.interactors.base.Success
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
@@ -28,7 +29,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
val failureSource = spyk(TestFailureSource())
val data = 10
- val errorMessage = "Generic Error"
+ val errorMessageResId = 1313
@Nested
inner class BasePagingSource {
@@ -37,10 +38,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
inner class LoadInitial {
@Test
-
fun `should update state accordingly`() {
val params = mockk>(relaxed = true)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
runBlocking {
@@ -54,10 +54,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
-
fun `should update error Error accordingly`() {
val params = PageKeyedDataSource.LoadInitialParams(10, false)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
runBlocking {
@@ -65,7 +64,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
failureSource.loadInitial(params, callback)
failureSource.reasonData.testObserve {
- it shouldBeInstanceOf GenericError::class
+ it shouldBeInstanceOf TestFailureReason::class
}
}
}
@@ -75,10 +74,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
inner class LoadAfter {
@Test
-
fun `should update state accordingly`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
runBlocking {
@@ -92,10 +90,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
-
fun `should update error Error accordingly`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
runBlocking {
@@ -103,17 +100,16 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
failureSource.loadAfter(params, callback)
failureSource.reasonData.testObserve {
- it shouldBeInstanceOf GenericError::class
+ it shouldBeInstanceOf TestFailureReason::class
}
}
}
}
@Test
-
fun `should use loadDataForPage in loadInitial and transform emmited value`() {
val params = mockk>(relaxed = true)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
// Fake loading
source.loadInitial(params, callback)
@@ -126,10 +122,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
-
fun `should use loadDataForPage in loadAfter and transform emmited value`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
- val callback = mockk>(relaxed = true)
+ val callback = mockk>(relaxed = true)
// Fake loading
source.loadAfter(params, callback)
@@ -142,25 +137,27 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
}
- inner class TestSource : BasePagingDataSource() {
-
+ inner class TestSource : BasePagingDataSource() {
val result = flow {
emit(State.Loading())
- emit(Success(listOf(data)))
+ emit(Success(listOf(TestViewEntity(data))))
}
-
- override fun loadDataForPage(page: Int): Flow>> = result
+ override fun loadDataForPage(page: Int): Flow>> = result
}
- inner class TestFailureSource : BasePagingDataSource() {
+ inner class TestFailureSource : BasePagingDataSource() {
val result = flow {
emit(State.Loading())
- emit(Failure(GenericError()))
+ emit(Failure(TestFailureReason(errorMessageResId)))
}
- override fun loadDataForPage(page: Int): Flow>> = result
+ override fun loadDataForPage(page: Int): Flow>> = result
}
-}
\ No newline at end of file
+
+ inner class TestViewEntity(data: Int) : ViewEntity
+
+ inner class TestFailureReason(override val messageRes: Int) : Reason()
+}
diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt
index 5829189..cfb6145 100644
--- a/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt
+++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt
@@ -1,5 +1,6 @@
package com.melih.core.paging
+import com.melih.abstractions.data.ViewEntity
import com.melih.core.BaseTestWithMainThread
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.base.paging.BasePagingFactory
@@ -25,9 +26,10 @@ class BasePagingFactoryTest : BaseTestWithMainThread() {
}
}
- inner class TestFactory : BasePagingFactory() {
-
- override fun createSource(): BasePagingDataSource = mockk(relaxed = true)
+ inner class TestFactory : BasePagingFactory() {
+ override fun createSource(): BasePagingDataSource = mockk(relaxed = true)
}
+
+ inner class TestViewEntity : ViewEntity
}
diff --git a/data/definitions/build.gradle b/data/definitions/build.gradle
new file mode 100644
index 0000000..b234e17
--- /dev/null
+++ b/data/definitions/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+apply from: "$rootProject.projectDir/scripts/module.gradle"
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation project(':abstractions')
+
+ implementation libraries.room
+ implementation libraries.moshiKotlin
+
+ kapt annotationProcessors.roomCompiler
+ kapt annotationProcessors.moshi
+}
diff --git a/data/definitions/consumer-rules.pro b/data/definitions/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/data/definitions/proguard-rules.pro b/data/definitions/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/data/definitions/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/data/definitions/src/main/AndroidManifest.xml b/data/definitions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8363ce0
--- /dev/null
+++ b/data/definitions/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/repository/src/main/kotlin/com/melih/repository/Constants.kt b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt
similarity index 74%
rename from repository/src/main/kotlin/com/melih/repository/Constants.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt
index 5d5e2dc..533f176 100644
--- a/repository/src/main/kotlin/com/melih/repository/Constants.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt
@@ -1,4 +1,4 @@
-package com.melih.repository
+package com.melih.definitions
internal const val DEFAULT_NAME = "Default name"
internal const val EMPTY_STRING = ""
diff --git a/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt b/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt
new file mode 100644
index 0000000..289304f
--- /dev/null
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt
@@ -0,0 +1,23 @@
+package com.melih.definitions
+
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.entities.LaunchEntity
+
+/**
+ * Contract for sources to seperate business logic from build and return type
+ */
+interface Source {
+
+ //region Abstractions
+
+ suspend fun getNextLaunches(
+ count: Int,
+ page: Int,
+ mapper: Mapper
+ ): Result>
+
+ suspend fun getLaunchById(id: Long, mapper: Mapper): Result
+ //endregion
+}
diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt
similarity index 87%
rename from repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt
index 2a7bdbd..43779e8 100644
--- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt
@@ -1,8 +1,8 @@
-package com.melih.repository.entities
+package com.melih.definitions.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
-import com.melih.repository.DEFAULT_NAME
+import com.melih.definitions.DEFAULT_NAME
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt
similarity index 86%
rename from repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt
index eaa55df..f6f3762 100644
--- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt
@@ -1,4 +1,4 @@
-package com.melih.repository.entities
+package com.melih.definitions.entities
import com.squareup.moshi.JsonClass
diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt
similarity index 87%
rename from repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt
index 652c4d3..af217ef 100644
--- a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt
@@ -1,7 +1,7 @@
-package com.melih.repository.entities
+package com.melih.definitions.entities
import androidx.room.ColumnInfo
-import com.melih.repository.DEFAULT_NAME
+import com.melih.definitions.DEFAULT_NAME
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
diff --git a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt
similarity index 73%
rename from repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt
index 971ec00..a8d1141 100644
--- a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt
@@ -1,8 +1,8 @@
-package com.melih.repository.entities
+package com.melih.definitions.entities
import androidx.room.ColumnInfo
-import com.melih.repository.DEFAULT_NAME
-import com.melih.repository.EMPTY_STRING
+import com.melih.definitions.DEFAULT_NAME
+import com.melih.definitions.EMPTY_STRING
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
diff --git a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt
similarity index 78%
rename from repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt
rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt
index 7fd8c3d..1b3f01e 100644
--- a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt
@@ -1,8 +1,8 @@
-package com.melih.repository.entities
+package com.melih.definitions.entities
import androidx.room.ColumnInfo
-import com.melih.repository.DEFAULT_NAME
-import com.melih.repository.EMPTY_STRING
+import com.melih.definitions.DEFAULT_NAME
+import com.melih.definitions.EMPTY_STRING
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
diff --git a/data/interactors/build.gradle b/data/interactors/build.gradle
new file mode 100644
index 0000000..b09d7d2
--- /dev/null
+++ b/data/interactors/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+apply from: "$rootProject.projectDir/scripts/module.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation project(':data:definitions')
+ implementation project(':data:network')
+ implementation project(':data:persistence')
+
+ implementation libraries.coroutines
+ implementation libraries.retrofit
+
+ testImplementation testLibraries.coroutinesCore
+ testImplementation testLibraries.coroutinesTest
+
+ compileOnly libraries.room
+}
diff --git a/data/interactors/consumer-rules.pro b/data/interactors/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/data/interactors/proguard-rules.pro b/data/interactors/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/data/interactors/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/data/interactors/src/main/AndroidManifest.xml b/data/interactors/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..86a5aa4
--- /dev/null
+++ b/data/interactors/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt
new file mode 100644
index 0000000..50fef98
--- /dev/null
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt
@@ -0,0 +1,40 @@
+package com.melih.interactors
+
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.interactors.base.BaseInteractor
+import com.melih.interactors.base.InteractorParameters
+import com.melih.interactors.sources.LaunchesSource
+import kotlinx.coroutines.flow.FlowCollector
+import javax.inject.Inject
+
+/**
+ * Gets next given number of launches
+ */
+class GetLaunchDetails @Inject constructor(
+ private val mapper: @JvmSuppressWildcards Mapper
+) : BaseInteractor() {
+
+ //region Properties
+
+ @Inject
+ internal lateinit var launchesSource: LaunchesSource
+ //endregion
+
+ //region Functions
+
+ override suspend fun FlowCollector>.run(params: Params) {
+ emit(launchesSource.getLaunchById(params.id, mapper))
+ }
+ //endregion
+
+
+ //region Parameters
+
+ data class Params(
+ val id: Long
+ ) : InteractorParameters
+ //endregion
+}
diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt
new file mode 100644
index 0000000..0f3e601
--- /dev/null
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt
@@ -0,0 +1,44 @@
+package com.melih.interactors
+
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.interactors.base.BaseInteractor
+import com.melih.interactors.base.InteractorParameters
+import com.melih.interactors.sources.LaunchesSource
+import kotlinx.coroutines.flow.FlowCollector
+import javax.inject.Inject
+
+const val DEFAULT_LAUNCHES_AMOUNT = 15
+
+/**
+ * Gets next given number of launches
+ */
+class GetLaunches @Inject constructor(
+ private val mapper: @JvmSuppressWildcards Mapper
+) : BaseInteractor, GetLaunches.Params>() {
+
+ //region Properties
+
+ @Inject
+ internal lateinit var launchesSource: LaunchesSource
+ //endregion
+
+ //region Functions
+
+ override suspend fun FlowCollector>>.run(params: Params) {
+
+ // Start network fetch - we're not handling state here to ommit them
+ emit(
+ launchesSource
+ .getNextLaunches(params.count, params.page, mapper)
+ )
+ }
+ //endregion
+
+ data class Params(
+ val count: Int = DEFAULT_LAUNCHES_AMOUNT,
+ val page: Int
+ ) : InteractorParameters
+}
diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt
similarity index 88%
rename from repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt
rename to data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt
index bb7605c..ffcca7f 100644
--- a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt
@@ -1,5 +1,7 @@
-package com.melih.repository.interactors.base
+package com.melih.interactors.base
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.deliverable.State
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt b/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt
new file mode 100644
index 0000000..3242f88
--- /dev/null
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt
@@ -0,0 +1,17 @@
+package com.melih.interactors.error
+
+import androidx.annotation.StringRes
+import com.melih.abstractions.deliverable.Reason
+import com.melih.interactors.R
+
+sealed class InteractionErrorReason(@StringRes override val messageRes: Int) : Reason()
+
+class GenericError(@StringRes override val messageRes: Int = R.string.reason_generic) : InteractionErrorReason(messageRes)
+
+sealed class NetworkError(override val messageRes: Int) : InteractionErrorReason(messageRes)
+class ConnectionError : NetworkError(R.string.reason_network)
+class EmptyResultError : NetworkError(R.string.reason_empty_body)
+class ResponseError : NetworkError(R.string.reason_response)
+class TimeoutError : NetworkError(R.string.reason_timeout)
+
+class PersistenceEmptyError : InteractionErrorReason(R.string.reason_persistance_empty)
diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt
new file mode 100644
index 0000000..ad73cce
--- /dev/null
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt
@@ -0,0 +1,163 @@
+package com.melih.interactors.sources
+
+import android.content.Context
+import android.net.NetworkInfo
+import com.melih.abstractions.data.ViewEntity
+import com.melih.abstractions.deliverable.Failure
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.deliverable.Success
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.Source
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.interactors.DEFAULT_LAUNCHES_AMOUNT
+import com.melih.interactors.error.ConnectionError
+import com.melih.interactors.error.EmptyResultError
+import com.melih.interactors.error.NetworkError
+import com.melih.interactors.error.PersistenceEmptyError
+import com.melih.interactors.error.ResponseError
+import com.melih.interactors.error.TimeoutError
+import com.melih.network.ApiImpl
+import com.melih.persistence.LaunchesDatabase
+import retrofit2.Response
+import java.io.IOException
+import javax.inject.Inject
+import javax.inject.Provider
+
+private const val DEFAULT_IMAGE_SIZE = 480
+
+internal class LaunchesSource @Inject constructor(
+ ctx: Context,
+ private val apiImpl: ApiImpl,
+ private val networkInfoProvider: Provider
+) : Source {
+
+ //region Properties
+
+ private val launchesDatabase = LaunchesDatabase.getInstance(ctx)
+
+ private val isNetworkConnected: Boolean
+ get() {
+ val networkInfo = networkInfoProvider.get()
+ return networkInfo != null && networkInfo.isConnected
+ }
+ //endregion
+
+ //region Functions
+
+ override suspend fun getNextLaunches(
+ count: Int,
+ page: Int, mapper: Mapper
+ ): Result> {
+ val networkResponse = safeExecute({
+ apiImpl.getNextLaunches(count, page * DEFAULT_LAUNCHES_AMOUNT)
+ }) { entity ->
+ entity.launches
+ .map(::transformRocketImageUrl)
+ .saveLaunches()
+ .map(mapper::convert)
+ }
+
+ return if (networkResponse is NetworkError) {
+ launchesDatabase
+ .launchesDao
+ .getLaunches(count, page)
+ .takeUnless { it.isNullOrEmpty() }
+ ?.run {
+ Success(map(mapper::convert))
+ } ?: Failure(PersistenceEmptyError())
+ } else {
+ networkResponse
+ }
+ }
+
+ override suspend fun getLaunchById(
+ id: Long,
+ mapper: Mapper
+ ): Result {
+ return launchesDatabase
+ .launchesDao
+ .getLaunchById(id)
+ .takeIf { it != null }
+ ?.run {
+ Success(mapper.convert(this))
+ } ?: loadLaunchFromNetwork(id, mapper)
+ }
+
+ private suspend fun loadLaunchFromNetwork(
+ id: Long,
+ mapper: Mapper
+ ): Result =
+ safeExecute({
+ apiImpl.getLaunchById(id)
+ }) {
+ mapper.convert(
+ transformRocketImageUrl(it)
+ .saveLaunch()
+ )
+ }
+
+ private suspend fun List.saveLaunches() = run {
+ launchesDatabase.launchesDao.saveLaunches(this)
+ this
+ }
+
+ private suspend fun LaunchEntity.saveLaunch() = run {
+ launchesDatabase.launchesDao.saveLaunch(this)
+ this
+ }
+
+ private inline fun safeExecute(
+ block: () -> Response,
+ transform: (T) -> R
+ ) =
+ if (isNetworkConnected) {
+ try {
+ block().extractResponseBody(transform)
+ } catch (e: IOException) {
+ Failure(TimeoutError())
+ }
+ } else {
+ Failure(ConnectionError())
+ }
+
+ private inline fun Response.extractResponseBody(transform: (T) -> R) =
+ if (isSuccessful) {
+ body()?.let {
+ Success(transform(it))
+ } ?: Failure(EmptyResultError())
+ } else {
+ Failure(ResponseError())
+ }
+
+ private fun transformRocketImageUrl(launch: LaunchEntity) =
+ if (!launch.rocket.imageURL.isNotBlank()) {
+ launch.copy(
+ rocket = launch.rocket.copy(
+ imageURL = transformImageUrl(
+ launch.rocket.imageURL,
+ launch.rocket.imageSizes
+ )
+ )
+ )
+ } else {
+ launch
+ }
+
+ private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) =
+ try {
+ val urlSplit = imageUrl.split("_")
+ val url = urlSplit[0]
+ val format = urlSplit[1].split(".")[1]
+
+ val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) {
+ supportedSizes.last { it < DEFAULT_IMAGE_SIZE }
+ } else {
+ DEFAULT_IMAGE_SIZE
+ }
+
+ "${url}_$requestedSize.$format"
+ } catch (e: Exception) {
+ imageUrl
+ }
+ //endregion
+}
diff --git a/repository/src/main/res/values/strings.xml b/data/interactors/src/main/res/values/strings.xml
similarity index 81%
rename from repository/src/main/res/values/strings.xml
rename to data/interactors/src/main/res/values/strings.xml
index 92cdbc9..13e42df 100644
--- a/repository/src/main/res/values/strings.xml
+++ b/data/interactors/src/main/res/values/strings.xml
@@ -1,9 +1,8 @@
+ Something went wrong
+ There are no saved launches
Network error
Response is empty
- Something went wrong
Woops, seems we got a server error
Server timed out
- There are no saved launches
- Seems there are no data and network
diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt
similarity index 91%
rename from repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt
rename to data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt
index 35d922c..5505f1a 100644
--- a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt
+++ b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt
@@ -1,5 +1,8 @@
-package com.melih.repository.interactors.base
+package com.melih.interactors.base
+import com.melih.abstractions.deliverable.Result
+import com.melih.abstractions.deliverable.State
+import com.melih.abstractions.deliverable.Success
import io.mockk.coVerify
import io.mockk.spyk
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -10,7 +13,7 @@ import kotlinx.coroutines.runBlocking
import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldEqualTo
import org.junit.jupiter.api.Test
-import java.util.*
+import java.util.ArrayDeque
@UseExperimental(ExperimentalCoroutinesApi::class)
class BaseInteractorTest {
diff --git a/data/network/build.gradle b/data/network/build.gradle
new file mode 100644
index 0000000..cc02d5c
--- /dev/null
+++ b/data/network/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+apply from: "$rootProject.projectDir/scripts/module.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation project(':data:definitions')
+
+ implementation libraries.okHttpLogger
+ implementation libraries.moshiKotlin
+ implementation libraries.coroutines
+ implementation libraries.retrofit
+
+ testImplementation testLibraries.coroutinesCore
+ testImplementation testLibraries.coroutinesTest
+}
diff --git a/data/network/consumer-rules.pro b/data/network/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/data/network/proguard-rules.pro b/data/network/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/data/network/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/data/network/src/main/AndroidManifest.xml b/data/network/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..32d8ca6
--- /dev/null
+++ b/data/network/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/repository/src/main/kotlin/com/melih/repository/network/Api.kt b/data/network/src/main/kotlin/com/melih/network/api/Api.kt
similarity index 78%
rename from repository/src/main/kotlin/com/melih/repository/network/Api.kt
rename to data/network/src/main/kotlin/com/melih/network/api/Api.kt
index 2783559..bef517d 100644
--- a/repository/src/main/kotlin/com/melih/repository/network/Api.kt
+++ b/data/network/src/main/kotlin/com/melih/network/api/Api.kt
@@ -1,7 +1,7 @@
-package com.melih.repository.network
+package com.melih.network
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.entities.LaunchesEntity
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.definitions.entities.LaunchesEntity
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
diff --git a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt b/data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt
similarity index 88%
rename from repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt
rename to data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt
index c56ffb8..ff195c6 100644
--- a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt
+++ b/data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt
@@ -1,7 +1,7 @@
-package com.melih.repository.network
+package com.melih.network
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.entities.LaunchesEntity
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.definitions.entities.LaunchesEntity
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.OkHttpClient
@@ -14,7 +14,7 @@ import javax.inject.Inject
internal const val TIMEOUT_DURATION = 7L
-internal class ApiImpl @Inject constructor() : Api {
+class ApiImpl @Inject constructor() : Api {
//region Properties
diff --git a/repository/build.gradle b/data/persistence/build.gradle
similarity index 82%
rename from repository/build.gradle
rename to data/persistence/build.gradle
index 843f46d..fcc536e 100644
--- a/repository/build.gradle
+++ b/data/persistence/build.gradle
@@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply from: "$rootProject.projectDir/scripts/module.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
android {
defaultConfig {
@@ -17,15 +18,13 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation libraries.coroutines
- implementation libraries.liveDataKTX
- implementation libraries.retrofit
- implementation libraries.room
+ implementation project(':data:definitions')
+
implementation libraries.moshiKotlin
- implementation libraries.okHttpLogger
+ implementation libraries.coroutines
+ implementation libraries.room
kapt annotationProcessors.roomCompiler
- kapt annotationProcessors.moshi
testImplementation testLibraries.coroutinesCore
testImplementation testLibraries.coroutinesTest
diff --git a/data/persistence/consumer-rules.pro b/data/persistence/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/data/persistence/proguard-rules.pro b/data/persistence/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/data/persistence/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/data/persistence/src/main/AndroidManifest.xml b/data/persistence/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4d3d76b
--- /dev/null
+++ b/data/persistence/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt b/data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt
similarity index 65%
rename from repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt
index 636dbab..da7a83c 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt
@@ -1,15 +1,15 @@
-package com.melih.repository.persistence
+package com.melih.persistence
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.persistence.converters.LocationConverter
-import com.melih.repository.persistence.converters.MissionConverter
-import com.melih.repository.persistence.converters.RocketConverter
-import com.melih.repository.persistence.dao.LaunchesDao
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.persistence.converters.LocationConverter
+import com.melih.persistence.converters.MissionConverter
+import com.melih.persistence.converters.RocketConverter
+import com.melih.persistence.dao.LaunchesDao
const val DB_NAME = "LaunchesDB"
@@ -26,7 +26,7 @@ const val DB_NAME = "LaunchesDB"
RocketConverter::class,
MissionConverter::class
)
-internal abstract class LaunchesDatabase : RoomDatabase() {
+abstract class LaunchesDatabase : RoomDatabase() {
//region Companion
@@ -48,6 +48,6 @@ internal abstract class LaunchesDatabase : RoomDatabase() {
//region Abstractions
- internal abstract val launchesDao: LaunchesDao
+ abstract val launchesDao: LaunchesDao
//endregion
}
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt
similarity index 93%
rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt
index cc9d698..a0bb43f 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt
@@ -1,4 +1,4 @@
-package com.melih.repository.persistence.converters
+package com.melih.persistence.converters
import androidx.room.TypeConverter
import com.squareup.moshi.JsonAdapter
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt
similarity index 93%
rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt
index 7bdc189..df5fe73 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt
@@ -1,4 +1,4 @@
-package com.melih.repository.persistence.converters
+package com.melih.persistence.converters
import androidx.room.TypeConverter
import com.squareup.moshi.JsonAdapter
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt
similarity index 63%
rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt
index 399efb9..a4d375d 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt
@@ -1,7 +1,7 @@
-package com.melih.repository.persistence.converters
+package com.melih.persistence.converters
-import com.melih.repository.entities.LocationEntity
-import com.melih.repository.entities.LocationEntityJsonAdapter
+import com.melih.definitions.entities.LocationEntity
+import com.melih.definitions.entities.LocationEntityJsonAdapter
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt
similarity index 81%
rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt
index 1d25e9a..6a524e4 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt
@@ -1,6 +1,6 @@
-package com.melih.repository.persistence.converters
+package com.melih.persistence.converters
-import com.melih.repository.entities.MissionEntity
+import com.melih.definitions.entities.MissionEntity
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt
similarity index 63%
rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt
index 66b3606..d98102b 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt
@@ -1,7 +1,7 @@
-package com.melih.repository.persistence.converters
+package com.melih.persistence.converters
-import com.melih.repository.entities.RocketEntity
-import com.melih.repository.entities.RocketEntityJsonAdapter
+import com.melih.definitions.entities.RocketEntity
+import com.melih.definitions.entities.RocketEntityJsonAdapter
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt
similarity index 86%
rename from repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt
rename to data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt
index 2490cdd..7d14666 100644
--- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt
@@ -1,16 +1,16 @@
-package com.melih.repository.persistence.dao
+package com.melih.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
-import com.melih.repository.entities.LaunchEntity
+import com.melih.definitions.entities.LaunchEntity
/**
* DAO for list of [launches][LaunchEntity]
*/
@Dao
-internal abstract class LaunchesDao {
+abstract class LaunchesDao {
//region Queries
diff --git a/docs/module_graph.png b/docs/module_graph.png
index 7add4b6..eab7666 100644
Binary files a/docs/module_graph.png and b/docs/module_graph.png differ
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index ce5b3e9..43c06ce 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -50,9 +50,29 @@ platform :android do
run_detail_tests()
end
- desc "Runs tests in repository module"
- lane :test_repository do
- run_repository_tests()
+ desc "Runs tests in abstraction module"
+ lane :test_abstractions do
+ run_abstractions_tests()
+ end
+
+ desc "Runs tests in definitions module"
+ lane :test_definitions do
+ run_definitions_tests()
+ end
+
+ desc "Runs tests in interactors module"
+ lane :test_interactors do
+ run_interactors_tests()
+ end
+
+ desc "Runs tests in network module"
+ lane :test_network do
+ run_network_tests()
+ end
+
+ desc "Runs tests in persistence module"
+ lane :test_persistence do
+ run_persistence_tests()
end
# ================ Gradle tasks ================
@@ -81,7 +101,23 @@ platform :android do
gradle(task: "features:detail:test --continue")
end
- def run_repository_tests
- gradle(task: "repository:test --continue")
+ def run_abstractions_tests
+ gradle(task: "abstractions:test --continue")
+ end
+
+ def run_definitions_tests
+ gradle(task: "data:definitions:test --continue")
+ end
+
+ def run_interactors_tests
+ gradle(task: "data:interactors:test --continue")
+ end
+
+ def run_network_tests
+ gradle(task: "data:network:test --continue")
+ end
+
+ def run_persistence_tests
+ gradle(task: "data:persistence:test --continue")
end
end
diff --git a/features/detail/build.gradle b/features/detail/build.gradle
index 43a02bd..cea8317 100644
--- a/features/detail/build.gradle
+++ b/features/detail/build.gradle
@@ -14,7 +14,5 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':repository')
-
testImplementation testLibraries.coroutinesTest
}
diff --git a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt
new file mode 100644
index 0000000..e6676f4
--- /dev/null
+++ b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt
@@ -0,0 +1,10 @@
+package com.melih.launches.data
+
+import com.melih.abstractions.data.ViewEntity
+
+data class LaunchDetailItem(
+ val id: Long,
+ val imageUrl: String,
+ val rocketName: String,
+ val missionDescription: String
+) : ViewEntity
diff --git a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt
new file mode 100644
index 0000000..32f61c0
--- /dev/null
+++ b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt
@@ -0,0 +1,18 @@
+package com.melih.launches.data
+
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.entities.LaunchEntity
+import javax.inject.Inject
+
+class LaunchDetailMapper @Inject constructor() : Mapper() {
+
+ override fun convert(launchEntity: LaunchEntity) =
+ with(launchEntity) {
+ LaunchDetailItem(
+ id,
+ rocket.imageURL,
+ rocket.name,
+ if (!missions.isNullOrEmpty()) missions[0].description else ""
+ )
+ }
+}
diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt
index c4a58a2..946e852 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt
@@ -2,11 +2,15 @@ package com.melih.detail.di.modules
import androidx.lifecycle.ViewModel
import androidx.navigation.fragment.navArgs
+import com.melih.abstractions.mapper.Mapper
import com.melih.core.di.keys.ViewModelKey
+import com.melih.definitions.entities.LaunchEntity
import com.melih.detail.ui.DetailFragment
import com.melih.detail.ui.DetailFragmentArgs
import com.melih.detail.ui.DetailViewModel
-import com.melih.repository.interactors.GetLaunchDetails
+import com.melih.interactors.GetLaunchDetails
+import com.melih.launches.data.LaunchDetailItem
+import com.melih.launches.data.LaunchDetailMapper
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -21,6 +25,9 @@ abstract class DetailFragmentModule {
@IntoMap
@ViewModelKey(DetailViewModel::class)
abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel
+
+ @Binds
+ abstract fun detailMapper(mapper: LaunchDetailMapper): Mapper
//endregion
@Module
diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt
index f63ee29..8897dbc 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt
@@ -1,34 +1,31 @@
package com.melih.detail.ui
import androidx.lifecycle.Transformations.map
+import com.melih.abstractions.deliverable.handle
import com.melih.core.base.viewmodel.BaseViewModel
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.GetLaunchDetails
-import com.melih.repository.interactors.base.handle
+import com.melih.interactors.GetLaunchDetails
+import com.melih.launches.data.LaunchDetailItem
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import javax.inject.Inject
class DetailViewModel @Inject constructor(
- private val getLaunchDetails: GetLaunchDetails,
+ private val getLaunchDetails: GetLaunchDetails,
private val getLaunchDetailsParams: GetLaunchDetails.Params
-) : BaseViewModel() {
+) : BaseViewModel() {
//region Properties
val rocketName = map(successData) {
- it.rocket.name
+ it.rocketName
}
val description = map(successData) {
- if (it.missions.isEmpty()) {
- ""
- } else {
- it.missions[0].description
- }
+ it.missionDescription
}
val imageUrl = map(successData) {
- it.rocket.imageURL
+ it.imageUrl
}
//endregion
diff --git a/features/detail/src/main/res/layout/fragment_detail.xml b/features/detail/src/main/res/layout/fragment_detail.xml
index dde2a8f..e58cded 100644
--- a/features/detail/src/main/res/layout/fragment_detail.xml
+++ b/features/detail/src/main/res/layout/fragment_detail.xml
@@ -1,69 +1,69 @@
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
diff --git a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt
index 69ad7c9..d4cc572 100644
--- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt
+++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt
@@ -1,7 +1,8 @@
package com.melih.detail
import com.melih.detail.ui.DetailViewModel
-import com.melih.repository.interactors.GetLaunchDetails
+import com.melih.interactors.GetLaunchDetails
+import com.melih.launches.data.LaunchDetailItem
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
@@ -19,7 +20,7 @@ import org.junit.jupiter.api.Test
@UseExperimental(ExperimentalCoroutinesApi::class)
class DetailViewModelTest : BaseTestWithMainThread() {
- private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true)
+ private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true)
private val getLaunchDetailsParams = GetLaunchDetails.Params(1013)
private val viewModel = spyk(DetailViewModel(getLaunchDetails, getLaunchDetailsParams))
diff --git a/features/launches/build.gradle b/features/launches/build.gradle
index 78a3446..740d414 100644
--- a/features/launches/build.gradle
+++ b/features/launches/build.gradle
@@ -7,8 +7,6 @@ apply from: "$rootProject.projectDir/scripts/feature_module.gradle"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':repository')
-
implementation libraries.paging
implementation libraries.swipeRefreshLayout
diff --git a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt
new file mode 100644
index 0000000..8d50cb4
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt
@@ -0,0 +1,10 @@
+package com.melih.launches.data
+
+import com.melih.abstractions.data.ViewEntity
+
+data class LaunchItem(
+ val id: Long,
+ val imageUrl: String,
+ val rocketName: String,
+ val missionDescription: String
+) : ViewEntity
diff --git a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt
new file mode 100644
index 0000000..77aadd1
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt
@@ -0,0 +1,18 @@
+package com.melih.launches.data
+
+import com.melih.abstractions.mapper.Mapper
+import com.melih.definitions.entities.LaunchEntity
+import javax.inject.Inject
+
+class LaunchMapper @Inject constructor() : Mapper() {
+
+ override fun convert(launchEntity: LaunchEntity) =
+ with(launchEntity) {
+ LaunchItem(
+ id,
+ rocket.imageURL,
+ rocket.name,
+ if (!missions.isNullOrEmpty()) missions[0].description else ""
+ )
+ }
+}
diff --git a/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
index 207eaf3..8841558 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
@@ -2,10 +2,14 @@ package com.melih.launches.di.modules
import androidx.lifecycle.ViewModel
import androidx.paging.Config
+import com.melih.abstractions.mapper.Mapper
import com.melih.core.di.keys.ViewModelKey
+import com.melih.definitions.entities.LaunchEntity
+import com.melih.interactors.DEFAULT_LAUNCHES_AMOUNT
+import com.melih.interactors.GetLaunches
+import com.melih.launches.data.LaunchItem
+import com.melih.launches.data.LaunchMapper
import com.melih.launches.ui.vm.LaunchesViewModel
-import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT
-import com.melih.repository.interactors.GetLaunches
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -14,12 +18,15 @@ import dagger.multibindings.IntoMap
@Module
abstract class LaunchesFragmentModule {
- //region ViewModels
+ //region Binds
@Binds
@IntoMap
@ViewModelKey(LaunchesViewModel::class)
- abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel
+ abstract fun launchesViewModel(listViewModel: LaunchesViewModel): ViewModel
+
+ @Binds
+ abstract fun launchMapper(mapper: LaunchMapper): Mapper
//endregion
@Module
diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt
index e29d29c..e5e7a85 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt
@@ -4,18 +4,18 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import com.melih.abstractions.deliverable.State
import com.melih.core.actions.openDetail
import com.melih.core.base.lifecycle.BaseDaggerFragment
import com.melih.core.extensions.observe
+import com.melih.interactors.error.PersistenceEmptyError
import com.melih.launches.R
-import com.melih.launches.databinding.ListBinding
+import com.melih.launches.data.LaunchItem
+import com.melih.launches.databinding.LaunchesBinding
import com.melih.launches.ui.adapters.LaunchesAdapter
import com.melih.launches.ui.vm.LaunchesViewModel
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.PersistenceEmpty
-import com.melih.repository.interactors.base.State
-class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener {
+class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener {
//region Properties
@@ -67,7 +67,7 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O
// Observing error to show toast with retry action
observe(viewModel.errorData) {
- if (it !is PersistenceEmpty) {
+ if (it !is PersistenceEmptyError) {
showSnackbarWithAction(it) {
viewModel.retry()
}
@@ -79,7 +79,7 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O
}
}
- private fun onItemSelected(item: LaunchEntity) {
+ private fun onItemSelected(item: LaunchItem) {
openDetail(item.id)
}
diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt
index d9e88ec..7f8f423 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt
@@ -5,10 +5,10 @@ import android.view.ViewGroup
import com.melih.core.base.recycler.BasePagingListAdapter
import com.melih.core.base.recycler.BaseViewHolder
import com.melih.core.extensions.createDiffCallback
+import com.melih.launches.data.LaunchItem
import com.melih.launches.databinding.LaunchRowBinding
-import com.melih.repository.entities.LaunchEntity
-class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter(
+class LaunchesAdapter(itemClickListener: (LaunchItem) -> Unit) : BasePagingListAdapter(
createDiffCallback { oldItem, newItem -> oldItem.id == newItem.id },
itemClickListener
) {
@@ -19,21 +19,18 @@ class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingLis
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
- ): BaseViewHolder =
+ ): BaseViewHolder =
LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false))
//endregion
}
-class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) {
+class LaunchesViewHolder(private val binding: LaunchRowBinding) :
+ BaseViewHolder(binding) {
//region Functions
- override fun bind(item: LaunchEntity) {
+ override fun bind(item: LaunchItem) {
binding.entity = item
-
- val missions = item.missions
- binding.tvDescription.text = if (!missions.isNullOrEmpty()) missions[0].description else ""
-
binding.executePendingBindings()
}
//endregion
diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt
index 6dc3056..7e1424b 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt
@@ -1,25 +1,23 @@
package com.melih.launches.ui.paging
import com.melih.core.base.paging.BasePagingDataSource
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.GetLaunches
-import com.melih.repository.interactors.base.Result
+import com.melih.interactors.GetLaunches
+import com.melih.launches.data.LaunchItem
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
/**
* Uses [GetLaunches] to get data for pagination
*/
class LaunchesPagingSource @Inject constructor(
- private val getLaunches: GetLaunches,
+ private val getLaunches: GetLaunches,
private val getLaunchesParams: GetLaunches.Params
-) : BasePagingDataSource() {
+) : BasePagingDataSource() {
//region Functions
@UseExperimental(ExperimentalCoroutinesApi::class)
- override fun loadDataForPage(page: Int): Flow>> =
+ override fun loadDataForPage(page: Int) =
getLaunches(
getLaunchesParams.copy(
page = page
diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
index 98b3485..7a6cfcf 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
@@ -2,16 +2,16 @@ package com.melih.launches.ui.paging
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.base.paging.BasePagingFactory
-import com.melih.repository.entities.LaunchEntity
+import com.melih.launches.data.LaunchItem
import javax.inject.Inject
import javax.inject.Provider
class LaunchesPagingSourceFactory @Inject constructor(
private val sourceProvider: Provider
-) : BasePagingFactory() {
+) : BasePagingFactory() {
//region Functions
- override fun createSource(): BasePagingDataSource = sourceProvider.get()
+ override fun createSource(): BasePagingDataSource = sourceProvider.get()
//endregion
}
diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt
index 39133a7..d026a54 100644
--- a/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt
@@ -3,18 +3,18 @@ package com.melih.launches.ui.vm
import androidx.paging.PagedList
import com.melih.core.base.paging.BasePagingFactory
import com.melih.core.base.viewmodel.BasePagingViewModel
+import com.melih.launches.data.LaunchItem
import com.melih.launches.ui.paging.LaunchesPagingSourceFactory
-import com.melih.repository.entities.LaunchEntity
import javax.inject.Inject
class LaunchesViewModel @Inject constructor(
private val launchesPagingSourceFactory: LaunchesPagingSourceFactory,
private val launchesPagingConfig: PagedList.Config
-) : BasePagingViewModel() {
+) : BasePagingViewModel() {
//region Properties
- override val factory: BasePagingFactory
+ override val factory: BasePagingFactory
get() = launchesPagingSourceFactory
override val config: PagedList.Config
diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml
index f9f368d..967e940 100644
--- a/features/launches/src/main/res/layout/fragment_launches.xml
+++ b/features/launches/src/main/res/layout/fragment_launches.xml
@@ -4,7 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
-
+
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
diff --git a/repository/.gitignore b/repository/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/repository/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/repository/src/main/AndroidManifest.xml b/repository/src/main/AndroidManifest.xml
deleted file mode 100644
index efd4c29..0000000
--- a/repository/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/repository/src/main/kotlin/com/melih/repository/Repository.kt b/repository/src/main/kotlin/com/melih/repository/Repository.kt
deleted file mode 100644
index af1e3d8..0000000
--- a/repository/src/main/kotlin/com/melih/repository/Repository.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.melih.repository
-
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.Result
-
-/**
- * Contract for sources to seperate low level business logic from build and return type
- */
-abstract class Repository {
-
- //region Abstractions
-
- internal abstract suspend fun getNextLaunches(count: Int, page: Int): Result>
- internal abstract suspend fun getLaunchById(id: Long): Result
- //endregion
-}
diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt
deleted file mode 100644
index 5f9de0d..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.melih.repository.interactors
-
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.BaseInteractor
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.InteractorParameters
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.sources.NetworkSource
-import com.melih.repository.sources.PersistenceSource
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.FlowCollector
-import javax.inject.Inject
-
-/**
- * Gets next given number of launches
- */
-@UseExperimental(ExperimentalCoroutinesApi::class)
-class GetLaunchDetails @Inject constructor() : BaseInteractor() {
-
- //region Properties
-
- @field:Inject
- internal lateinit var networkSource: NetworkSource
-
- @field:Inject
- internal lateinit var persistenceSource: PersistenceSource
- //endregion
-
- //region Functions
-
- override suspend fun FlowCollector>.run(params: Params) {
- val result = persistenceSource.getLaunchById(params.id)
-
- if (result !is Success) {
- when (val response = networkSource.getLaunchById(params.id)) {
- // Save result and return again from persistence
- is Success -> {
- persistenceSource.saveLaunch(response.successData)
- emit(persistenceSource.getLaunchById(params.id))
- }
-
- // Redirect failure as it is
- is Failure -> emit(response)
- }
- } else {
- emit(result)
- }
- }
- //endregion
-
- data class Params(
- val id: Long
- ) : InteractorParameters
-}
diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt
deleted file mode 100644
index 775d8c9..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.melih.repository.interactors
-
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.BaseInteractor
-import com.melih.repository.interactors.base.InteractorParameters
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.sources.NetworkSource
-import com.melih.repository.sources.PersistenceSource
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.FlowCollector
-import javax.inject.Inject
-
-const val DEFAULT_LAUNCHES_AMOUNT = 15
-
-/**
- * Gets next given number of launches
- */
-@UseExperimental(ExperimentalCoroutinesApi::class)
-class GetLaunches @Inject constructor() : BaseInteractor, GetLaunches.Params>() {
-
- //region Properties
-
- @field:Inject
- internal lateinit var networkSource: NetworkSource
-
- @field:Inject
- internal lateinit var persistenceSource: PersistenceSource
- //endregion
-
- //region Functions
-
- override suspend fun FlowCollector>>.run(params: Params) {
-
- // Start network fetch - we're not handling state here to ommit them
- networkSource
- .getNextLaunches(params.count, params.page)
- .also {
- if (it is Success) {
- persistenceSource.saveLaunches(it.successData)
- emit(persistenceSource.getNextLaunches(params.count, params.page))
- } else {
- emit(it)
- emit(persistenceSource.getNextLaunches(params.count, params.page))
- }
- }
- }
- //endregion
-
- data class Params(
- val count: Int = DEFAULT_LAUNCHES_AMOUNT,
- val page: Int
- ) : InteractorParameters
-}
diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt
deleted file mode 100644
index fa14e9a..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.melih.repository.interactors.base
-
-import androidx.annotation.StringRes
-import com.melih.repository.R
-
-
-/**
- * [Failure] reasons
- */
-sealed class Reason(@StringRes val messageRes: Int)
-
-//region Subclasses
-
-class NetworkError : Reason(R.string.reason_network)
-class EmptyResultError : Reason(R.string.reason_empty_body)
-class GenericError : Reason(R.string.reason_generic)
-class ResponseError : Reason(R.string.reason_response)
-class TimeoutError : Reason(R.string.reason_timeout)
-class PersistenceEmpty : Reason(R.string.reason_persistance_empty)
-class NoNetworkPersistenceEmpty : Reason(R.string.reason_no_network_persistance_empty)
-//endregion
diff --git a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt
deleted file mode 100644
index 785e0a2..0000000
--- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.melih.repository.sources
-
-import android.net.NetworkInfo
-import com.melih.repository.Repository
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT
-import com.melih.repository.interactors.base.EmptyResultError
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.NetworkError
-import com.melih.repository.interactors.base.Reason
-import com.melih.repository.interactors.base.ResponseError
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.interactors.base.TimeoutError
-import com.melih.repository.network.ApiImpl
-import retrofit2.Response
-import java.io.IOException
-import javax.inject.Inject
-import javax.inject.Provider
-
-private const val DEFAULT_IMAGE_SIZE = 480
-
-/**
- * NetworkSource for fetching results using api and wrapping them as contracted in [repository][Repository],
- * returning either [failure][Failure] with proper [reason][Reason] or [success][Success] with data
- */
-internal class NetworkSource @Inject constructor(
- private val apiImpl: ApiImpl,
- private val networkInfoProvider: Provider
-) : Repository() {
-
- //region Properties
-
- private val isNetworkConnected: Boolean
- get() {
- val networkInfo = networkInfoProvider.get()
- return networkInfo != null && networkInfo.isConnected
- }
- //endregion
-
- //region Functions
-
- override suspend fun getNextLaunches(count: Int, page: Int): Result> =
- safeExecute({
- apiImpl.getNextLaunches(count, page * DEFAULT_LAUNCHES_AMOUNT)
- }) { entity ->
- entity.launches.map { launch ->
- if (!launch.rocket.imageURL.isNotBlank()) {
- launch.copy(
- rocket = launch.rocket.copy(
- imageURL = transformImageUrl(
- launch.rocket.imageURL,
- launch.rocket.imageSizes
- )
- )
- )
- } else {
- launch
- }
- }
- }
-
- override suspend fun getLaunchById(id: Long): Result =
- safeExecute({
- apiImpl.getLaunchById(id)
- }) {
- if (!it.rocket.imageURL.isNotBlank()) {
- it.copy(
- rocket = it.rocket.copy(
- imageURL = transformImageUrl(it.rocket.imageURL, it.rocket.imageSizes)
- )
- )
- } else {
- it
- }
- }
-
- private inline fun safeExecute(
- block: () -> Response,
- transform: (T) -> R
- ) =
- if (isNetworkConnected) {
- try {
- block().extractResponseBody(transform)
- } catch (e: IOException) {
- Failure(TimeoutError())
- }
- } else {
- Failure(NetworkError())
- }
-
- private inline fun Response.extractResponseBody(transform: (T) -> R) =
- if (isSuccessful) {
- body()?.let {
- Success(transform(it))
- } ?: Failure(EmptyResultError())
- } else {
- Failure(ResponseError())
- }
-
- private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) =
- try {
- val urlSplit = imageUrl.split("_")
- val url = urlSplit[0]
- val format = urlSplit[1].split(".")[1]
-
- val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) {
- supportedSizes.last { it < DEFAULT_IMAGE_SIZE }
- } else {
- DEFAULT_IMAGE_SIZE
- }
-
- "${url}_$requestedSize.$format"
- } catch (e: Exception) {
- imageUrl
- }
- //endregion
-}
diff --git a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt
deleted file mode 100644
index c49881d..0000000
--- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.melih.repository.sources
-
-import android.content.Context
-import com.melih.repository.Repository
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.PersistenceEmpty
-import com.melih.repository.interactors.base.Result
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.persistence.LaunchesDatabase
-import javax.inject.Inject
-
-/**
- * Persistance source using Room database to save / read objects for SST - offline usage
- */
-internal class PersistenceSource @Inject constructor(
- ctx: Context
-) : Repository() {
-
- //region Functions
-
- private val launchesDatabase = LaunchesDatabase.getInstance(ctx)
-
- override suspend fun getNextLaunches(count: Int, page: Int): Result> =
- launchesDatabase
- .launchesDao
- .getLaunches(count, page)
- .takeIf { it.isNotEmpty() }
- ?.run {
- Success(this)
- } ?: Failure(PersistenceEmpty())
-
- override suspend fun getLaunchById(id: Long): Result =
- launchesDatabase
- .launchesDao
- .getLaunchById(id)
- .takeIf { it != null }
- ?.run {
- Success(this)
- } ?: Failure(PersistenceEmpty())
-
- internal suspend fun saveLaunches(launches: List) {
- launchesDatabase.launchesDao.saveLaunches(launches)
- }
-
- internal suspend fun saveLaunch(launch: LaunchEntity) {
- launchesDatabase.launchesDao.saveLaunch(launch)
- }
- //endregion
-}
diff --git a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt
deleted file mode 100644
index 343aac6..0000000
--- a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.melih.repository.sources
-
-import android.net.NetworkInfo
-import com.melih.repository.R
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.entities.LaunchesEntity
-import com.melih.repository.interactors.base.EmptyResultError
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.NetworkError
-import com.melih.repository.interactors.base.ResponseError
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.interactors.base.onFailure
-import com.melih.repository.interactors.base.onSuccess
-import com.melih.repository.network.ApiImpl
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.spyk
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runBlockingTest
-import org.amshove.kluent.shouldBeInstanceOf
-import org.amshove.kluent.shouldEqualTo
-import org.junit.jupiter.api.Nested
-import org.junit.jupiter.api.Test
-import javax.inject.Provider
-
-@UseExperimental(ExperimentalCoroutinesApi::class)
-class NetworkSourceTest {
-
- private val apiImpl = mockk(relaxed = true)
- private val networkInfoProvider = mockk>(relaxed = true) {
- every { get() } returns mockk(relaxed = true)
- }
-
- private val source = spyk(NetworkSource(apiImpl, networkInfoProvider))
-
- @Nested
- inner class GetNextLaunches {
-
- @Test
- fun `should return network error when internet is not connected`() {
- every { networkInfoProvider.get().isConnected } returns false
-
- runBlockingTest {
- val result = source.getNextLaunches(1, 0)
-
- result shouldBeInstanceOf Failure::class
- result.onFailure {
- it shouldBeInstanceOf NetworkError::class
- }
- }
- }
-
- @Test
- fun `should return response error when it is not successful`() {
- every { networkInfoProvider.get().isConnected } returns true
- coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns false
-
- runBlockingTest {
- val result = source.getNextLaunches(1, 0)
-
- result shouldBeInstanceOf Failure::class
- result.onFailure {
- it shouldBeInstanceOf ResponseError::class
- (it as ResponseError).messageRes shouldEqualTo R.string.reason_response
- }
- }
- }
-
- @Test
- fun `should return empty result error when body is null`() {
- every { networkInfoProvider.get().isConnected } returns true
- coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns true
- coEvery { apiImpl.getNextLaunches(any(), any()).body() } returns null
-
- runBlockingTest {
- val result = source.getNextLaunches(1, 0)
-
- result shouldBeInstanceOf Failure::class
- result.onFailure {
- it shouldBeInstanceOf EmptyResultError::class
- }
- }
- }
-
- @Test
- fun `should return success with data if execution is successful`() {
- every { networkInfoProvider.get().isConnected } returns true
- coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns true
- coEvery { apiImpl.getNextLaunches(any(), any()).body() } returns LaunchesEntity(launches = listOf(LaunchEntity(id = 1013)))
-
- runBlockingTest {
- val result = source.getNextLaunches(1, 0)
-
- result shouldBeInstanceOf Success::class
- result.onSuccess {
- it shouldBeInstanceOf List::class
- it.size shouldEqualTo 1
- it[0].id shouldEqualTo 1013
- }
- }
- }
- }
-}
diff --git a/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt
deleted file mode 100644
index e40fa14..0000000
--- a/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.melih.repository.sources
-
-import android.content.Context
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.Failure
-import com.melih.repository.interactors.base.PersistenceEmpty
-import com.melih.repository.interactors.base.Success
-import com.melih.repository.interactors.base.onFailure
-import com.melih.repository.interactors.base.onSuccess
-import com.melih.repository.persistence.LaunchesDatabase
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.mockkObject
-import io.mockk.spyk
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import org.amshove.kluent.shouldBe
-import org.amshove.kluent.shouldBeInstanceOf
-import org.amshove.kluent.shouldEqualTo
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Nested
-import org.junit.jupiter.api.Test
-
-class PersistanceSourceTest {
-
- private val ctx = mockk(relaxed = true)
- private val dbImplementation = mockk(relaxed = true)
- private val source = spyk(PersistenceSource(ctx))
-
- private val scope = CoroutineScope(Dispatchers.IO)
-
- @BeforeEach
- fun setup() {
- mockkObject(LaunchesDatabase)
- every { LaunchesDatabase.getInstance(ctx) } returns dbImplementation
- }
-
- @Nested
- inner class GetNextLaunches {
-
- @Test
- fun `should return persistance empty error when db is empty`() {
- coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns emptyList()
-
- scope.launch {
- val result = source.getNextLaunches(10, 0)
-
- result shouldBeInstanceOf Failure::class
- result.onFailure {
- it shouldBeInstanceOf PersistenceEmpty::class
- }
- }
- }
-
- @Test
- fun `should return success with data if db is not empty`() {
- coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns listOf(LaunchEntity(id = 1013))
-
- scope.launch {
- val result = source.getNextLaunches(10, 0)
-
- result shouldBeInstanceOf Success::class
- result.onSuccess {
- it.isEmpty() shouldBe false
- it.size shouldEqualTo 1
- it[0].id shouldEqualTo 1013
- }
- }
- }
- }
-}
diff --git a/scripts/detekt.gradle b/scripts/cq/detekt.gradle
similarity index 100%
rename from scripts/detekt.gradle
rename to scripts/cq/detekt.gradle
diff --git a/scripts/dokka.gradle b/scripts/cq/dokka.gradle
similarity index 100%
rename from scripts/dokka.gradle
rename to scripts/cq/dokka.gradle
diff --git a/scripts/cq/jacoco.gradle b/scripts/cq/jacoco.gradle
new file mode 100644
index 0000000..0493b26
--- /dev/null
+++ b/scripts/cq/jacoco.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'jacoco'
+
+jacoco {
+ toolVersion = "0.8.1"
+ reportsDir = file("$rootProject.projectDir/reports/jacoco/$project.name")
+}
+
+task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {
+ group = "Reporting"
+ description = "Generate Jacoco coverage reports for Debug build"
+
+ reports {
+ xml.enabled = true
+ html.enabled = true
+ }
+
+ // what to exclude from coverage report
+ // UI, "noise", generated classes, platform classes, etc.
+ def excludes = [
+ '**/R.class',
+ '**/R$*.class',
+ '**/*$ViewInjector*.*',
+ '**/BuildConfig.*',
+ '**/Manifest*.*',
+ '**/*Test*.*',
+ 'android/**/*.*',
+ '**/*Fragment.*',
+ '**/*Activity.*'
+ ]
+
+ // generated classes
+ getClassDirectories().setFrom(
+ fileTree(
+ dir: "$buildDir/intermediates/classes/debug",
+ excludes: excludes
+ ) + fileTree(
+ dir: "$buildDir/tmp/kotlin-classes/debug",
+ excludes: excludes
+ )
+ )
+
+ // sources
+ getSourceDirectories().setFrom(
+ files([
+ android.sourceSets.main.java.srcDirs,
+ "src/main/kotlin"
+ ])
+ )
+
+ getExecutionData().setFrom(
+ files("$buildDir/jacoco/testDebugUnitTest.exec")
+ )
+}
\ No newline at end of file
diff --git a/scripts/default_android_config.gradle b/scripts/default_android_config.gradle
index 4546de2..c81a51b 100644
--- a/scripts/default_android_config.gradle
+++ b/scripts/default_android_config.gradle
@@ -1,4 +1,6 @@
-apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
+apply from: "$rootProject.projectDir/scripts/cq/detekt.gradle"
+apply from: "$rootProject.projectDir/scripts/cq/dokka.gradle"
+apply from: "$rootProject.projectDir/scripts/cq/jacoco.gradle"
android {
compileSdkVersion versions.compileSdkVersion
diff --git a/scripts/default_dependencies.gradle b/scripts/default_dependencies.gradle
index b9ee1c9..9e6003a 100644
--- a/scripts/default_dependencies.gradle
+++ b/scripts/default_dependencies.gradle
@@ -1,11 +1,10 @@
apply plugin: "de.mannodermaus.android-junit5"
-apply from: "$rootProject.projectDir/scripts/detekt.gradle"
-apply from: "$rootProject.projectDir/scripts/dokka.gradle"
-
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':abstractions')
+
implementation libraries.kotlin
implementation libraries.dagger
implementation libraries.timber
@@ -18,57 +17,3 @@ dependencies {
testRuntimeOnly testLibraries.jUnitEngine
}
-
-apply plugin: 'jacoco'
-
-jacoco {
- toolVersion = "0.8.1"
- reportsDir = file("$rootProject.projectDir/reports/jacoco/$project.name")
-}
-
-task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {
- group = "Reporting"
- description = "Generate Jacoco coverage reports for Debug build"
-
- reports {
- xml.enabled = true
- html.enabled = true
- }
-
- // what to exclude from coverage report
- // UI, "noise", generated classes, platform classes, etc.
- def excludes = [
- '**/R.class',
- '**/R$*.class',
- '**/*$ViewInjector*.*',
- '**/BuildConfig.*',
- '**/Manifest*.*',
- '**/*Test*.*',
- 'android/**/*.*',
- '**/*Fragment.*',
- '**/*Activity.*'
- ]
-
- // generated classes
- getClassDirectories().setFrom(
- fileTree(
- dir: "$buildDir/intermediates/classes/debug",
- excludes: excludes
- ) + fileTree(
- dir: "$buildDir/tmp/kotlin-classes/debug",
- excludes: excludes
- )
- )
-
- // sources
- getSourceDirectories().setFrom(
- files([
- android.sourceSets.main.java.srcDirs,
- "src/main/kotlin"
- ])
- )
-
- getExecutionData().setFrom(
- files("$buildDir/jacoco/testDebugUnitTest.exec")
- )
-}
diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle
index 21ff897..49b7aed 100644
--- a/scripts/dependencies.gradle
+++ b/scripts/dependencies.gradle
@@ -5,28 +5,28 @@ ext {
compileSdkVersion : 29,
targetSdkVersion : 29,
buildToolsVersion : "29.0.2",
- appCompatVersion : "1.1.0-rc01",
- lifecycleVersion : "2.2.0-alpha03",
- fragmentVersion : "1.2.0-alpha02",
- workManagerVersion : "2.3.0-alpha01",
- constraintLayoutVesion : "2.0.0-beta2",
+ appCompatVersion : "1.1.0",
+ lifecycleVersion : "2.2.0-alpha05",
+ fragmentVersion : "1.2.0-rc01",
+ workManagerVersion : "2.3.0-alpha03",
+ constraintLayoutVesion : "2.0.0-beta3",
cardViewVersion : "1.0.0",
- recyclerViewVersion : "1.1.0-beta03",
+ recyclerViewVersion : "1.1.0-rc01",
pagingVersion : "2.1.0",
- viewPagerVersion : "1.0.0-beta03",
+ viewPagerVersion : "1.0.0-rc01",
materialVersion : "1.1.0-alpha09",
- swipeRefreshLayoutVersion: "1.1.0-alpha02",
+ swipeRefreshLayoutVersion: "1.1.0-alpha03",
collectionVersion : "1.1.0",
- roomVersion : "2.2.0-rc01",
- daggerVersion : "2.24",
- okHttpVersion : "4.0.1",
- retrofitVersion : "2.6.1",
+ roomVersion : "2.2.0",
+ daggerVersion : "2.25.2",
+ okHttpVersion : "4.2.1",
+ retrofitVersion : "2.6.2",
picassoVersion : "2.71828",
moshiVersion : "1.8.0",
- coroutinesVersion : "1.3.0-RC2",
- leakCanaryVersion : "2.0-beta-2",
+ coroutinesVersion : "1.3.2",
+ leakCanaryVersion : "2.0-beta-3",
timberVersion : "4.7.1",
- jUnitVersion : "5.5.1",
+ jUnitVersion : "5.5.2",
espressoVersion : "3.2.0",
mockkVersion : "1.9.3",
kluentVersion : "1.53",
diff --git a/scripts/feature_module.gradle b/scripts/feature_module.gradle
index 981efc5..89eae3a 100644
--- a/scripts/feature_module.gradle
+++ b/scripts/feature_module.gradle
@@ -1,5 +1,7 @@
apply plugin: "androidx.navigation.safeargs"
+
apply from: "$rootProject.projectDir/scripts/module.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
android {
dataBinding {
@@ -9,6 +11,8 @@ android {
dependencies {
implementation project(':core')
+ implementation project(':data:interactors')
+ implementation project(':data:definitions')
implementation libraries.fragment
implementation libraries.lifecycle
diff --git a/settings.gradle b/settings.gradle
index 7a766a4..ad1f600 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,11 @@
-include ':app', ':repository', ':core', ':features:launches', ':features:detail'
-rootProject.name = 'Rocket Science'
+rootProject.name = 'RocketScience'
+
+include ':abstractions',
+ ':core',
+ 'app',
+ ':features:launches',
+ ':features:detail',
+ ':data:interactors',
+ ':data:network',
+ ':data:persistence',
+ ':data:definitions'