diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4a7b433..e4542dd 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -26,19 +26,114 @@ jobs:
command: fastlane detekt
- store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
path: reports/detekt
- - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
- path: reports
# Tests
+ ## App
+ - run:
+ name: Test App
+ command: |
+ fastlane test_app
+ ./gradlew app:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: app/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: app/build/test-results
+
+ ## Core
+ - run:
+ name: Test Core
+ command: |
+ fastlane test_core
+ ./gradlew core:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: core/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: core/build/test-results
+
+ ## Launches
+ - run:
+ name: Test Launches
+ command: |
+ fastlane test_launches
+ ./gradlew features:launches:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: features/launches/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: features/launches/build/test-results
+
+ ## Detail
+ - run:
+ name: Test Detail
+ command: |
+ fastlane test_detail
+ ./gradlew features:detail:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+ - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
+ path: features/detail/build/reports/tests
+ - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
+ path: features/detail/build/test-results
+
+ ## Abstractions
+ - run:
+ name: Test Abstractions
+ command: |
+ 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: Tests
+ name: Test Persistence
command: |
- fastlane test_all
- ./gradlew jacocoTestReport
+ 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: build/reports/tests
+ path: repository/build/reports/tests
- store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
- path: reports
+ path: repository/build/test-results
# See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644
index 0000000..a6ec318
--- /dev/null
+++ b/.github/workflows/android.yml
@@ -0,0 +1,86 @@
+name: Android CI
+
+on: [push, pull_request]
+
+jobs:
+
+ code_quality:
+
+ runs-on: macOS-latest
+
+ steps:
+ - uses: actions/checkout@v1
+
+ - name: Setup
+ run: |
+ gem install bundler
+ bundle install
+
+ - name: Code Quality ( Detekt )
+ run: fastlane detekt
+
+ test:
+
+ runs-on: macOS-latest
+
+ steps:
+ - uses: actions/checkout@v1
+
+ - name: Setup
+ run: |
+ gem install bundler
+ bundle install
+
+ - name: Test App
+ run: |
+ bundle exec fastlane test_app
+ ./gradlew app:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Core
+ run: |
+ bundle exec fastlane test_core
+ ./gradlew core:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Launches
+ run: |
+ bundle exec fastlane test_launches
+ ./gradlew features:launches:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Detail
+ run: |
+ bundle exec fastlane test_detail
+ ./gradlew features:detail:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Abstractions
+ run: |
+ bundle exec fastlane test_abstractions
+ ./gradlew abstractions:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Definitions
+ run: |
+ bundle exec fastlane test_definitions
+ ./gradlew data:definitions:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Interactors
+ run: |
+ bundle exec fastlane test_interactors
+ ./gradlew data:interactors:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Network
+ run: |
+ bundle exec fastlane test_network
+ ./gradlew data:network:jacocoTestReport
+ bash <(curl -s https://codecov.io/bash)
+
+ - name: Test Persistence
+ run: |
+ bundle exec 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 90bb429..4cdb0a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@
/.idea
.DS_Store
/build
+**/build
+/fastlane/README.md
+/fastlane/report.xml
/captures
.externalNativeBuild
/projectFilesBackup
@@ -11,3 +14,6 @@
# Project reports
/reports
+
+#jacoco.exec
+jacoco.exec
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..40a7662
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,159 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.2)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ atomos (0.1.3)
+ babosa (1.0.3)
+ claide (1.0.3)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander-fastlane (4.4.6)
+ highline (~> 1.7.2)
+ declarative (0.0.10)
+ declarative-option (0.1.0)
+ digest-crc (0.4.1)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.5)
+ emoji_regex (1.0.1)
+ excon (0.71.1)
+ faraday (0.17.3)
+ multipart-post (>= 1.2, < 3)
+ faraday-cookie_jar (0.0.6)
+ faraday (>= 0.7.4)
+ http-cookie (~> 1.0.0)
+ faraday_middleware (0.13.1)
+ faraday (>= 0.7.4, < 1.0)
+ fastimage (2.1.7)
+ fastlane (2.139.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.3, < 3.0.0)
+ babosa (>= 1.0.2, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander-fastlane (>= 4.4.6, < 5.0.0)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 2.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 0.17)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 0.13.1)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-api-client (>= 0.29.2, < 0.37.0)
+ google-cloud-storage (>= 1.15.0, < 2.0.0)
+ highline (>= 1.7.2, < 2.0.0)
+ json (< 3.0.0)
+ jwt (~> 2.1.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multi_xml (~> 0.5)
+ multipart-post (~> 2.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ public_suffix (~> 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)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ gh_inspector (1.1.3)
+ google-api-client (0.36.3)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (~> 0.9)
+ httpclient (>= 2.8.1, < 3.0)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ signet (~> 0.12)
+ google-cloud-core (1.4.1)
+ google-cloud-env (~> 1.0)
+ google-cloud-env (1.3.0)
+ faraday (~> 0.11)
+ google-cloud-storage (1.25.0)
+ addressable (~> 2.5)
+ digest-crc (~> 0.4)
+ google-api-client (~> 0.33)
+ google-cloud-core (~> 1.2)
+ googleauth (~> 0.9)
+ mini_mime (~> 1.0)
+ googleauth (0.10.0)
+ faraday (~> 0.12)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (~> 0.12)
+ highline (1.7.10)
+ http-cookie (1.0.3)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ json (2.3.0)
+ jwt (2.1.0)
+ memoist (0.16.2)
+ mini_magick (4.9.5)
+ mini_mime (1.0.2)
+ multi_json (1.14.1)
+ multi_xml (0.6.0)
+ multipart-post (2.0.0)
+ nanaimo (0.2.6)
+ naturally (2.2.0)
+ os (1.0.1)
+ plist (3.5.0)
+ public_suffix (2.0.5)
+ representable (3.0.4)
+ declarative (< 0.1.0)
+ declarative-option (< 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rouge (2.0.7)
+ rubyzip (1.3.0)
+ security (0.1.3)
+ signet (0.12.0)
+ addressable (~> 2.3)
+ faraday (~> 0.9)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.7)
+ CFPropertyList
+ naturally
+ slack-notifier (2.3.2)
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ tty-cursor (0.7.0)
+ tty-screen (0.7.0)
+ tty-spinner (0.9.2)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.7.6)
+ unicode-display_width (1.6.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.14.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.2.6)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.0)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ fastlane
+
+BUNDLED WITH
+ 2.0.1
diff --git a/README.md b/README.md
index e700c93..f4bd56d 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-# Rocket Science
+# Android-Kotlin-Modulerized-CleanArchitecture
-[](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [](https://codecov.io/gh/melihaksoy/RocketScience) [](https://circleci.com/gh/melihaksoy/RocketScience/tree/master) [](https://github.com/KotlinBy/awesome-kotlin)
+[](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/actions) [](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [](https://codecov.io/gh/melihaksoy/RocketScience) [](https://github.com/KotlinBy/awesome-kotlin)
-RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android.
+This is a prototype application tries to serve an example for modularization & clean architecture in Android.
While there are many blogs about good practices, it's hard to come by a complete example that merges these different popular topics & approaches.
-RocketScience takes popular approaches and libraries to create an example on how to actually bind these components with each other.
+This project takes popular approaches and libraries to create an example on how to actually bind these components with each other.
I'll soon be writing small series about roadmap, challanges, alternatives and try - fails I've encountered during development in [Medium](https://medium.com/@aksoymelihcan).
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 64%
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 a5684c7..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,15 +1,12 @@
-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
+//region Subclasses
class Success(val successData: T) : Result()
class Failure(val errorData: Reason) : Result()
@@ -18,11 +15,15 @@ sealed class State : Result() {
class Loading : State()
class Loaded : State()
}
-// endregion
+//endregion
-// region Extensions
+//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)
@@ -50,4 +51,4 @@ inline fun Result.onState(stateBlock: (State) -> Unit): Result {
return this
}
-// endregion
+//endregion
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..0eba505
--- /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
+
+interface Mapper {
+
+ 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 abf4201..8dd64c3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,8 +3,8 @@ 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"
-apply from: "$rootProject.projectDir/scripts/flavors.gradle"
android {
defaultConfig {
@@ -14,8 +14,8 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
- dataBinding {
- enabled = true
+ buildFeatures{
+ dataBinding = true
}
}
@@ -26,15 +26,23 @@ dependencies {
implementation project(':features:launches')
implementation project(':features:detail')
- implementation libraries.coroutines
implementation libraries.navigation
debugImplementation libraries.leakCanary
androidTestImplementation testLibraries.espresso
- // These libraries required by dagger to create dependency graph, but not by app
+ // 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
+
+ // Need for proper renders in xml previews
+ compileOnly libraries.constraintLayout
}
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/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7e6e52f..6013c48 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,8 +1,10 @@
-
+
+ tools:ignore="GoogleAppIndexingWarning">
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/kotlin/com/melih/rocketscience/App.kt b/app/src/main/kotlin/com/melih/rocketscience/App.kt
index b2d8e13..803856c 100644
--- a/app/src/main/kotlin/com/melih/rocketscience/App.kt
+++ b/app/src/main/kotlin/com/melih/rocketscience/App.kt
@@ -1,10 +1,12 @@
package com.melih.rocketscience
import com.melih.core.di.DaggerCoreComponent
+import com.melih.launches.data.LaunchDetailItem
import com.melih.rocketscience.di.DaggerAppComponent
import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import timber.log.Timber
+import javax.inject.Inject
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector =
@@ -14,7 +16,6 @@ class App : DaggerApplication() {
.create(this)
)
-
override fun onCreate() {
super.onCreate()
diff --git a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt
new file mode 100644
index 0000000..696b4dd
--- /dev/null
+++ b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt
@@ -0,0 +1,29 @@
+package com.melih.rocketscience
+
+import android.os.Bundle
+import androidx.appcompat.widget.Toolbar
+import androidx.navigation.NavController
+import androidx.navigation.findNavController
+import androidx.navigation.ui.NavigationUI
+import dagger.android.support.DaggerAppCompatActivity
+
+class MainActivity : DaggerAppCompatActivity() {
+
+ private lateinit var navController: NavController
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ navController = findNavController(R.id.nav_host_fragment)
+ NavigationUI.setupWithNavController(findViewById(R.id.toolbar), navController)
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ if (!NavigationUI.navigateUp(navController, null)) {
+ onBackPressed()
+ }
+
+ return true
+ }
+}
diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt
index ffa55c8..ebbc8a3 100644
--- a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt
+++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt
@@ -1,8 +1,6 @@
package com.melih.rocketscience.di
import com.melih.core.di.CoreComponent
-import com.melih.detail.di.DetailFeatureModule
-import com.melih.list.di.LaunchesFeatureModule
import com.melih.rocketscience.App
import dagger.Component
import dagger.android.AndroidInjectionModule
@@ -10,9 +8,10 @@ import dagger.android.AndroidInjector
@AppScope
@Component(
- modules = [AndroidInjectionModule::class,
- LaunchesFeatureModule::class,
- DetailFeatureModule::class],
+ modules = [
+ AndroidInjectionModule::class,
+ AppModule::class
+ ],
dependencies = [CoreComponent::class]
)
diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt
new file mode 100644
index 0000000..0d0630b
--- /dev/null
+++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt
@@ -0,0 +1,21 @@
+package com.melih.rocketscience.di
+
+import com.melih.detail.di.DetailContributor
+import com.melih.launches.data.LaunchDetailItem
+import com.melih.launches.di.LaunchesContributor
+import com.melih.rocketscience.MainActivity
+import dagger.Module
+import dagger.Provides
+import dagger.android.ContributesAndroidInjector
+
+@Module
+abstract class AppModule {
+
+
+ @ContributesAndroidInjector(
+ modules = [
+ LaunchesContributor::class,
+ DetailContributor::class]
+ )
+ abstract fun mainActivity(): MainActivity
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..51075f4
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/navigation/nav_main.xml b/app/src/main/res/navigation/nav_main.xml
new file mode 100644
index 0000000..c390eb6
--- /dev/null
+++ b/app/src/main/res/navigation/nav_main.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 1314a64..52cec02 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,18 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.41'
- ext.nav_version = '2.2.0-alpha01'
+ ext.kotlin_version = '1.3.72'
+ ext.nav_version = '2.3.0'
repositories {
google()
jcenter()
-
+ maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
+ maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0-rc03'
+ classpath 'com.android.tools.build:gradle:4.2.0-alpha03'
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.0.0"
+ classpath "de.mannodermaus.gradle.plugins:android-junit5:1.6.0.1-SNAPSHOT"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -20,23 +20,19 @@ 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.10.0'
+ id 'org.jetbrains.dokka' version '0.10.1'
+ id 'jacoco'
}
allprojects {
repositories {
google()
jcenter()
-
+ maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
}
}
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
-
task removeReports(type: Delete) {
delete fileTree(rootProject.projectDir.path + "/reports") {
include '**/*.*'
@@ -97,7 +93,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 adb92c2..325677e 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -3,31 +3,30 @@ 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"
-apply from: "$rootProject.projectDir/scripts/flavors.gradle"
android {
- dataBinding {
- enabled = true
+
+ buildFeatures{
+ dataBinding = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- api project(":repository")
-
+ implementation libraries.coroutines
implementation libraries.fragment
implementation libraries.paging
implementation libraries.lifecycle
implementation libraries.liveDataKTX
implementation libraries.navigation
implementation libraries.picasso
+ implementation libraries.material
testImplementation testLibraries.jUnitApi
testImplementation testLibraries.mockk
testImplementation testLibraries.kluent
testImplementation testLibraries.coroutinesTest
-
- compileOnly libraries.room
}
diff --git a/core/jacoco.exec b/core/jacoco.exec
deleted file mode 100644
index 45a3fec..0000000
Binary files a/core/jacoco.exec and /dev/null differ
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 7a7f527..e3436a3 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
-
+
-
+
diff --git a/core/src/main/kotlin/com/melih/core/actions/Actions.kt b/core/src/main/kotlin/com/melih/core/actions/Actions.kt
index 8fbd473..ca813c9 100644
--- a/core/src/main/kotlin/com/melih/core/actions/Actions.kt
+++ b/core/src/main/kotlin/com/melih/core/actions/Actions.kt
@@ -1,16 +1,12 @@
package com.melih.core.actions
-import android.content.Intent
-
-const val EXTRA_LAUNCH_ID = "extras:detail:launchid"
+import android.net.Uri
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.NavHostFragment
+import com.melih.core.R
/**
* Navigation actions for navigation between feature activities
*/
-object Actions {
-
- fun openDetailFor(id: Long) =
- Intent("action.dashboard.open")
- .putExtra(EXTRA_LAUNCH_ID, id)
-
-}
+fun Fragment.openDetail(id: Long) =
+ NavHostFragment.findNavController(this).navigate(Uri.parse(getString(R.string.detail_uri, id)))
diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt
deleted file mode 100644
index ba672c4..0000000
--- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.melih.core.base.lifecycle
-
-import android.os.Bundle
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.databinding.DataBindingUtil
-import androidx.databinding.ViewDataBinding
-import androidx.navigation.fragment.NavHostFragment
-import androidx.navigation.ui.NavigationUI
-import dagger.android.support.DaggerAppCompatActivity
-
-const val NAV_HOST_FRAGMENT_TAG = "nav_host_fragment_tag"
-
-/**
- * Base class of all Activity classes
- */
-abstract class BaseActivity : DaggerAppCompatActivity() {
-
- protected lateinit var binding: T
- protected lateinit var navHostFragment: NavHostFragment
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- binding = DataBindingUtil.setContentView(this, getLayoutId())
- binding.lifecycleOwner = this
-
- if (savedInstanceState == null) {
- navHostFragment = createNavHostFragment()
-
- supportFragmentManager
- .beginTransaction()
- .add(addNavHostTo(), navHostFragment, NAV_HOST_FRAGMENT_TAG)
- .commitNow()
- } else {
- navHostFragment = supportFragmentManager
- .findFragmentByTag(NAV_HOST_FRAGMENT_TAG) as NavHostFragment
- }
- }
-
- override fun onSupportNavigateUp(): Boolean {
- if (!NavigationUI.navigateUp(navHostFragment.navController, null)) {
- onBackPressed()
- }
-
- return true
- }
-
- @LayoutRes
- abstract fun getLayoutId(): Int
-
- abstract fun createNavHostFragment(): NavHostFragment
-
- @IdRes
- abstract fun addNavHostTo(): Int
-}
diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt
index 0ca715f..23014aa 100644
--- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt
+++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt
@@ -8,13 +8,14 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import dagger.android.support.AndroidSupportInjection
import javax.inject.Inject
+import kotlin.properties.Delegates
/**
* Parent of fragments which has injections. Aim is to seperate [BaseFragment] functionality for fragments which
* won't need any injection.
*
- * Note that fragments that extends from [BaseDaggerFragment] should contribute android injector.
+ * Note that fragments that extends from [BaseDaggerFragment] should contribute their injector.
*
* This class provides [viewModelFactory] which serves as factory for view models
* in the project. It's injected by map of view models that this app is serving. Check [ViewModelFactory]
@@ -22,22 +23,22 @@ import javax.inject.Inject
*/
abstract class BaseDaggerFragment : BaseFragment(), HasAndroidInjector {
- // region Properties
+ //region Properties
- @get:Inject
- internal var androidInjector: DispatchingAndroidInjector? = null
+ @Inject
+ protected lateinit var androidInjector: DispatchingAndroidInjector
@Inject
- lateinit var viewModelFactory: ViewModelFactory
- // endregion
+ protected lateinit var viewModelFactory: ViewModelFactory
+ //endregion
- // region Functions
+ //region Functions
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
- override fun androidInjector(): AndroidInjector? = androidInjector
- // endregion
+ override fun androidInjector(): AndroidInjector = androidInjector
+ //endregion
}
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 4224e72..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
@@ -8,10 +8,9 @@ import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
-import androidx.navigation.NavController
-import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.snackbar.Snackbar
-import com.melih.repository.interactors.base.Reason
+import com.melih.abstractions.deliverable.Reason
+import com.melih.core.R
/**
* Parent of all fragments.
@@ -21,20 +20,24 @@ import com.melih.repository.interactors.base.Reason
*/
abstract class BaseFragment : Fragment() {
- // region Properties
+ //region Abstractions
+
+ @LayoutRes
+ abstract fun getLayoutId(): Int
+ //endregion
+
+ //region Properties
- protected lateinit var navController: NavController
protected lateinit var binding: T
- // endregion
+ //endregion
- // region Functions
+ //region Functions
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- navController = NavHostFragment.findNavController(this)
binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
binding.lifecycleOwner = this
return binding.root
@@ -45,12 +48,9 @@ abstract class BaseFragment : Fragment() {
binding.root,
resources.getString(reason.messageRes),
Snackbar.LENGTH_INDEFINITE
- ).setAction(com.melih.core.R.string.retry) {
+ ).setAction(R.string.retry) {
block()
}.show()
}
-
- @LayoutRes
- abstract fun getLayoutId(): Int
- // endregion
+ //endregion
}
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 47b6126..9cd5fa7 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
@@ -34,15 +35,15 @@ const val INITIAL_PAGE = 0
* It's cancelled automatically when source factory [invalidates][invalidate] the source.
*/
-@UseExperimental(ExperimentalCoroutinesApi::class)
-abstract class BasePagingDataSource : PageKeyedDataSource() {
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BasePagingDataSource : PageKeyedDataSource() {
- // region Abstractions
+ //region Abstractions
- abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s)
- // endregion
+ abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s)
+ //endregion
- // region Properties
+ //region Properties
private val _stateData = MutableLiveData()
private val _reasonData = MutableLiveData()
@@ -59,11 +60,14 @@ abstract class BasePagingDataSource : PageKeyedDataSource() {
*/
val reasonData: LiveData
get() = _reasonData
- // endregion
+ //endregion
- // region Functions
+ //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
}
@@ -136,5 +140,5 @@ abstract class BasePagingDataSource : PageKeyedDataSource() {
coroutineScope.cancel()
super.invalidate()
}
- // endregion
+ //endregion
}
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 e948600..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,22 +15,22 @@ 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
+ //region Abstractions
abstract fun createSource(): BasePagingDataSource
- // endregion
+ //endregion
- // region Properties
+ //region Properties
private val _currentSource = MutableLiveData>()
val currentSource: LiveData>
get() = _currentSource
- // endregion
+ //endregion
- // region Functions
+ //region Functions
override fun create(): DataSource = createSource().apply { _currentSource.postValue(this) }
@@ -38,5 +39,5 @@ abstract class BasePagingFactory : DataSource.Factory() {
* by calling [BasePagingDataSource.invalidate]
*/
fun invalidateDataSource() = currentSource.value?.invalidate()
- // endregion
+ //endregion
}
diff --git a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt
index cfe44bc..3c9cdb4 100644
--- a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt
+++ b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt
@@ -15,6 +15,8 @@ abstract class BasePagingListAdapter(
private val clickListener: (T) -> Unit
) : PagedListAdapter>(callback) {
+ //region Abstractions
+
/**
* This method will be called to create view holder to obfuscate layout inflation creation / process
*
@@ -27,6 +29,9 @@ abstract class BasePagingListAdapter(
parent: ViewGroup,
viewType: Int
): BaseViewHolder
+ //endregion
+
+ //region Functions
/**
* [createViewHolder] will provide holders, no need to override this
@@ -51,6 +56,7 @@ abstract class BasePagingListAdapter(
holder.bind(item)
}
}
+ //endregion
}
/**
@@ -58,6 +64,8 @@ abstract class BasePagingListAdapter(
*/
abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
+ //region Functions
+
/**
* Items are delivered to [bind] via [BaseListAdapter.onBindViewHolder]
*
@@ -65,4 +73,5 @@ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHo
* @param position position from adapter
*/
abstract fun bind(item: T)
+ //endregion
}
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 57da05b..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
@@ -1,13 +1,14 @@
package com.melih.core.base.viewmodel
import androidx.lifecycle.LiveData
-import androidx.lifecycle.Transformations
+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,21 +19,21 @@ 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
+ //region Abstractions
abstract val factory: BasePagingFactory
abstract val config: PagedList.Config
- // endregion
+ //endregion
- // region Properties
+ //region Properties
/**
* Observe [stateData] to get notified of state of data
*/
val stateData: LiveData by lazy {
- Transformations.switchMap(factory.currentSource) {
+ switchMap(factory.currentSource) {
it.stateData
}
}
@@ -41,7 +42,7 @@ abstract class BasePagingViewModel : ViewModel() {
* Observe [errorData] to get notified if an error occurs
*/
val errorData: LiveData by lazy {
- Transformations.switchMap(factory.currentSource) {
+ switchMap(factory.currentSource) {
it.reasonData
}
}
@@ -52,9 +53,9 @@ abstract class BasePagingViewModel : ViewModel() {
val pagedList: LiveData> by lazy {
factory.toLiveData(config)
}
- // endregion
+ //endregion
- // region Functions
+ //region Functions
fun refresh() {
factory.currentSource.value?.invalidate()
@@ -66,5 +67,5 @@ abstract class BasePagingViewModel : ViewModel() {
fun retry() {
factory.currentSource.value?.invalidate()
}
- // endregion
+ //endregion
}
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 2e3246b..4a5be25 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
@@ -3,10 +3,8 @@ package com.melih.core.base.viewmodel
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 kotlinx.coroutines.launch
+import com.melih.abstractions.deliverable.Reason
+import com.melih.abstractions.deliverable.State
/**
* Base [ViewModel] for view models that will process data.
@@ -15,18 +13,7 @@ import kotlinx.coroutines.launch
*/
abstract class BaseViewModel : ViewModel() {
- // region Abstractions
-
- abstract suspend fun loadData()
- // endregion
-
- init {
- viewModelScope.launch {
- loadData()
- }
- }
-
- // region Properties
+ //region Properties
private val _successData = MutableLiveData()
private val _stateData = MutableLiveData()
@@ -49,9 +36,9 @@ abstract class BaseViewModel : ViewModel() {
*/
val errorData: LiveData
get() = _errorData
- // endregion
+ //endregion
- // region Functions
+ //region Functions
/**
* Default success handler which assigns given [data] to [successData]
@@ -79,23 +66,5 @@ abstract class BaseViewModel : ViewModel() {
protected fun handleFailure(reason: Reason) {
_errorData.value = reason
}
-
- /**
- * Reload data
- */
- fun refresh() {
- viewModelScope.launch {
- loadData()
- }
- }
-
- /**
- * Retry loading data, incase there's difference between refresh and retry, should go here
- */
- fun retry() {
- viewModelScope.launch {
- loadData()
- }
- }
- // endregion
+ //endregion
}
diff --git a/core/src/main/kotlin/com/melih/core/di/CoreModule.kt b/core/src/main/kotlin/com/melih/core/di/CoreModule.kt
index bc8aab2..fe9bbf5 100644
--- a/core/src/main/kotlin/com/melih/core/di/CoreModule.kt
+++ b/core/src/main/kotlin/com/melih/core/di/CoreModule.kt
@@ -14,6 +14,10 @@ class CoreModule {
fun proivdeAppContext(app: Application): Context = app.applicationContext
@Provides
- fun provideNetworkInfo(app: Application): NetworkInfo? =
- (app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
+ fun provideNetworkInfo(app: Application): NetworkInfo? {
+ val connectivityManager =
+ app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ return connectivityManager.activeNetworkInfo
+ }
+
}
diff --git a/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt
new file mode 100644
index 0000000..55bca7a
--- /dev/null
+++ b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt
@@ -0,0 +1,27 @@
+package com.melih.core.extensions
+
+import androidx.recyclerview.widget.DiffUtil
+
+/**
+ * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker.
+ * It uses [itemCheck] for both [DiffUtil.ItemCallback.areItemsTheSame] and [DiffUtil.ItemCallback.areContentsTheSame].
+ */
+inline fun createDiffCallback(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = createDiffCallback(itemCheck, itemCheck)
+
+/**
+ * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker
+ */
+inline fun createDiffCallback(
+ crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean,
+ crossinline contentCheck: (oldItem: T, newItem: T) -> Boolean
+) = object : DiffUtil.ItemCallback() {
+
+ //region Functions
+
+ override fun areItemsTheSame(oldItem: T, newItem: T): Boolean =
+ itemCheck(oldItem, newItem)
+
+ override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
+ contentCheck(oldItem, newItem)
+ //endregion
+}
diff --git a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt
index bf31e90..1396b42 100644
--- a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt
+++ b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt
@@ -3,9 +3,6 @@ package com.melih.core.extensions
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProviders
/**
* Reduces required boilerplate code to observe a live data
@@ -16,13 +13,3 @@ import androidx.lifecycle.ViewModelProviders
fun Fragment.observe(data: LiveData, block: (T) -> Unit) {
data.observe(this, Observer(block))
}
-
-/**
- * Method for getting viewModel from factory and run a block over it if required for easy access
- *
- * crossinline for unwanted returns
- */
-inline fun ViewModelProvider.Factory.createFor(
- fragment: Fragment,
- crossinline block: T.() -> Unit = {}
-): T = ViewModelProviders.of(fragment, this)[T::class.java].apply(block)
diff --git a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt
deleted file mode 100644
index 011fac9..0000000
--- a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.melih.core.extensions
-
-import androidx.recyclerview.widget.DiffUtil
-
-/**
- * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker
- */
-inline fun getDiffCallbackForType(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: T, newItem: T): Boolean =
- itemCheck(oldItem, newItem)
-
- override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
- itemCheck(oldItem, newItem)
-}
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/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index 270aec4..da95615 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -1,7 +1,11 @@
- #008577
- #00574B
- #D81B60
- #8F8F8F
+ #f9a825
+ #ffd95a
+ #c17900
+ #757575
+ #a4a4a4
+ #494949
+ #000000
+ #686868
diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml
index ab4df8e..7bf765c 100644
--- a/core/src/main/res/values/dimens.xml
+++ b/core/src/main/res/values/dimens.xml
@@ -1,5 +1,7 @@
8dp
+ 16dp
+ 4dp
11dp
\ No newline at end of file
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 22788f1..9268d45 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -1,15 +1,12 @@
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
- incididunt ut labore et dolore
- magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
- consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
- non proident, sunt in culpa qui officia
- deserunt mollit anim id est laborum
-
+
+
+]>
- Retry
+
+ Retry
-
- action.detail.open
+ ¶mLaunchId;
+ &deeplinkDetailPath;/{¶mLaunchId;}
+ &deeplinkDetailPath;/%1d
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index f7ae6d6..1d6d73c 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -1,35 +1,40 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt
index d8d68fb..92deea2 100644
--- a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt
+++ b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt
@@ -13,7 +13,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import kotlin.coroutines.suspendCoroutine
-@UseExperimental(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseTestWithMainThread {
private val dispatcher = TestCoroutineDispatcher()
diff --git a/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt b/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt
index b195388..4b527fa 100644
--- a/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt
+++ b/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt
@@ -11,7 +11,7 @@ class BaseViewModelTest : BaseTestWithMainThread() {
@Test
fun `refresh should invoke loadData`() {
val baseVm = spyk(TestViewModel())
- baseVm.refresh()
+ baseVm.loadData()
coVerify(exactly = 1) { baseVm.loadData() }
}
@@ -19,14 +19,14 @@ class BaseViewModelTest : BaseTestWithMainThread() {
@Test
fun `retry should invoke loadData`() {
val baseVm = spyk(TestViewModel())
- baseVm.retry()
+ baseVm.loadData()
coVerify(exactly = 1) { baseVm.loadData() }
}
}
class TestViewModel : BaseViewModel() {
- override suspend fun loadData() {
- // no - op
+ fun loadData() {
+
}
}
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..11e3515 100644
--- a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt
+++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt
@@ -1,16 +1,15 @@
-@file:UseExperimental(ExperimentalCoroutinesApi::class)
-
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 +27,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
val failureSource = spyk(TestFailureSource())
val data = 10
- val errorMessage = "Generic Error"
+ val errorMessageResId = 1313
@Nested
inner class BasePagingSource {
@@ -37,10 +36,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 +52,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 +62,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
failureSource.loadInitial(params, callback)
failureSource.reasonData.testObserve {
- it shouldBeInstanceOf GenericError::class
+ it shouldBeInstanceOf TestFailureReason::class
}
}
}
@@ -75,10 +72,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 +88,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 +98,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 +120,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 +135,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/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt
new file mode 100644
index 0000000..533f176
--- /dev/null
+++ b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt
@@ -0,0 +1,4 @@
+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 74%
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 e431a2d..d8a7a85 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
@@ -10,15 +12,15 @@ import kotlinx.coroutines.flow.flowOn
/**
* Base use case that wraps [suspending][suspend] [run] function with [flow][Flow] and returns it for later usage.
*/
-@UseExperimental(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseInteractor {
- // region Abstractions
+ //region Abstractions
protected abstract suspend fun FlowCollector>.run(params: P)
- // endregion
+ //endregion
- // region Functions
+ //region Functions
operator fun invoke(params: P) =
flow> {
@@ -26,7 +28,7 @@ abstract class BaseInteractor {
run(params)
emit(State.Loaded())
}.flowOn(Dispatchers.IO)
- // endregion
+ //endregion
}
/**
@@ -37,4 +39,4 @@ interface InteractorParameters
/**
* Symbolizes absence of parameters for an [interactor][BaseInteractor]
*/
-class None : Any(), InteractorParameters
+class None : InteractorParameters
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..7abce3c
--- /dev/null
+++ b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt
@@ -0,0 +1,160 @@
+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): String {
+ 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
+ }
+
+ return "${url}_$requestedSize.$format"
+ }
+ //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 90%
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..0799822 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,9 +13,8 @@ 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 {
val testInteractor = spyk(TestInteractor())
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 73%
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 0b410d8..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
@@ -12,6 +12,8 @@ import retrofit2.http.Query
*/
internal interface Api {
+ //region Get
+
@GET("launch/next/{count}")
suspend fun getNextLaunches(
@Path("count") count: Int,
@@ -22,4 +24,5 @@ internal interface Api {
suspend fun getLaunchById(
@Path("id") id: Long
): Response
+ //endregion
}
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 85%
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 85ca556..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,9 +14,9 @@ import javax.inject.Inject
internal const val TIMEOUT_DURATION = 7L
-internal class ApiImpl @Inject constructor() : Api {
+class ApiImpl @Inject constructor() : Api {
- // region Properties
+ //region Properties
private val service by lazy {
val moshi = Moshi.Builder()
@@ -39,7 +39,9 @@ internal class ApiImpl @Inject constructor() : Api {
.build()
.create(Api::class.java)
}
- // endregion
+ //endregion
+
+ //region Functions
override suspend fun getNextLaunches(
count: Int,
@@ -51,4 +53,5 @@ internal class ApiImpl @Inject constructor() : Api {
id: Long
): Response =
service.getLaunchById(id)
+ //endregion
}
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 project(':data:definitions')
+
+ implementation libraries.moshiKotlin
implementation libraries.coroutines
- implementation libraries.liveDataKTX
- implementation libraries.retrofit
implementation libraries.room
- implementation libraries.moshiKotlin
- implementation libraries.okHttpLogger
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 62%
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 4ab8fb7..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,9 @@ const val DB_NAME = "LaunchesDB"
RocketConverter::class,
MissionConverter::class
)
-internal abstract class LaunchesDatabase : RoomDatabase() {
+abstract class LaunchesDatabase : RoomDatabase() {
+
+ //region Companion
companion object {
@@ -42,6 +44,10 @@ internal abstract class LaunchesDatabase : RoomDatabase() {
}
}
+ //endregion
+
+ //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 78%
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 735d615..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
@@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
*/
abstract class BaseConverter {
+ //region Abstractions
+
+ abstract fun getAdapter(moshi: Moshi): JsonAdapter
+ //endregion
+
+ //region Properties
+
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
+ //endregion
- abstract fun getAdapter(moshi: Moshi): JsonAdapter
-
- // region Functions
+ //region Functions
@TypeConverter
fun convertFrom(item: T) =
@@ -25,5 +31,5 @@ abstract class BaseConverter {
@TypeConverter
fun convertTo(string: String) =
getAdapter(moshi).fromJson(string)
- // endregion
+ //endregion
}
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 79%
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 f304c0a..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
@@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
*/
abstract class BaseListConverter {
+ //region Abstractions
+
+ abstract fun getAdapter(moshi: Moshi): JsonAdapter>
+ //endregion
+
+ //region Properties
+
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
+ //endregion
- abstract fun getAdapter(moshi: Moshi): JsonAdapter>
-
- // region Functions
+ //region Functions
@TypeConverter
fun convertFrom(items: List) =
@@ -25,5 +31,5 @@ abstract class BaseListConverter {
@TypeConverter
fun convertTo(string: String): List? =
getAdapter(moshi).fromJson(string)
- // endregion
+ //endregion
}
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 6742ecb..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
@@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi
* Converts [location][LocationEntity]
*/
class LocationConverter : BaseConverter() {
+
override fun getAdapter(moshi: Moshi): JsonAdapter =
LocationEntityJsonAdapter(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 54%
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 de2e3b8..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
@@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi
* Converts [rocket][RocketEntity]
*/
class RocketConverter : BaseConverter() {
+
override fun getAdapter(moshi: Moshi): JsonAdapter =
- RocketEntityJsonAdapter(moshi)
+ RocketEntityJsonAdapter(moshi)
}
diff --git a/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt
new file mode 100644
index 0000000..36f366b
--- /dev/null
+++ b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt
@@ -0,0 +1,35 @@
+package com.melih.persistence.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.melih.definitions.entities.LaunchEntity
+
+/**
+ * DAO for list of [launches][LaunchEntity]
+ */
+@Dao
+interface LaunchesDao {
+
+ //region Queries
+
+ @Query("SELECT * FROM Launches ORDER BY launchStartTime DESC LIMIT :count OFFSET :page*:count")
+ suspend fun getLaunches(count: Int, page: Int): List
+
+ @Query("SELECT * FROM Launches WHERE id=:id LIMIT 1")
+ suspend fun getLaunchById(id: Long): LaunchEntity?
+
+ @Query("DELETE FROM Launches")
+ suspend fun nukeLaunches()
+ //endregion
+
+ //region Insertion
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun saveLaunches(launches: List)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun saveLaunch(launch: LaunchEntity)
+ //endregion
+}
diff --git a/default-detekt-config.yml b/default-detekt-config.yml
index 326fe3c..738f638 100644
--- a/default-detekt-config.yml
+++ b/default-detekt-config.yml
@@ -1,40 +1,21 @@
-autoCorrect: true
-
-test-pattern: # Configure exclusions for test sources
- active: true
- patterns: # Test file regexes
- - '.*/test/.*'
- - '.*/androidTest/.*'
- - '.*Test.kt'
- - '.*Spec.kt'
- - '.*Spek.kt'
- exclude-rule-sets:
- - 'comments'
- exclude-rules:
- - 'NamingRules'
- - 'WildcardImport'
- - 'MagicNumber'
- - 'MaxLineLength'
- - 'LateinitUsage'
- - 'StringLiteralDuplication'
- - 'SpreadOperator'
- - 'TooManyFunctions'
- - 'ForEachOnRange'
- - 'FunctionMaxLength'
- - 'TooGenericExceptionCaught'
- - 'InstanceOfCheckForException'
-
build:
maxIssues: 1
+ excludeCorrectable: false
weights:
-# complexity: 1
-# LongParameterList: 1
-# style: 1
-# comments: 0
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ''
processors:
active: true
exclude:
+ - 'DetektProgressListener'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ClassCountProcessor'
@@ -44,21 +25,25 @@ processors:
console-reports:
active: true
exclude:
- # - 'ProjectStatisticsReport'
- # - 'ComplexityReport'
- # - 'NotificationReport'
- # - 'FindingsReport'
- # - 'BuildFailureReport'
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ # - 'FindingsReport'
+ - 'FileBasedFindingsReport'
comments:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateFile: 'license.template'
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
EndOfSentenceFormat:
active: false
- endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$)
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
UndocumentedPublicClass:
active: false
searchInNestedClass: true
@@ -67,6 +52,8 @@ comments:
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false
+ UndocumentedPublicProperty:
+ active: false
complexity:
active: true
@@ -77,14 +64,17 @@ complexity:
active: false
threshold: 10
includeStaticDeclarations: false
+ includePrivateDeclarations: false
ComplexMethod:
active: true
- threshold: 10
+ threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull
LabeledExpression:
active: false
- ignoredLabels: ""
+ ignoredLabels: ''
LargeClass:
active: true
threshold: 600
@@ -93,8 +83,10 @@ complexity:
threshold: 60
LongParameterList:
active: true
- threshold: 6
+ functionThreshold: 6
+ constructorThreshold: 7
ignoreDefaultParameters: false
+ ignoreDataClasses: true
MethodOverloading:
active: false
threshold: 6
@@ -103,12 +95,14 @@ complexity:
threshold: 4
StringLiteralDuplication:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
@@ -118,11 +112,18 @@ complexity:
ignorePrivate: false
ignoreOverridden: false
+coroutines:
+ active: true
+ GlobalCoroutineUsage:
+ active: false
+ RedundantSuspendModifier:
+ active: false
+
empty-blocks:
active: true
EmptyCatchBlock:
active: true
- allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ allowedExceptionNameRegex: '^(_|(ignore|expected).*)'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
@@ -137,7 +138,7 @@ empty-blocks:
active: true
EmptyFunctionBlock:
active: true
- ignoreOverriddenFunctions: false
+ ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
@@ -146,6 +147,8 @@ empty-blocks:
active: true
EmptySecondaryConstructor:
active: true
+ EmptyTryBlock:
+ active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
@@ -158,6 +161,7 @@ exceptions:
methodNames: 'toString,hashCode,equals,finalize'
InstanceOfCheckForException:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
NotImplementedDeclaration:
active: false
PrintStackTrace:
@@ -166,9 +170,11 @@ exceptions:
active: false
ReturnFromFinally:
active: false
+ ignoreLabeled: false
SwallowedException:
active: false
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
+ allowedExceptionNameRegex: '^(_|(ignore|expected).*)'
ThrowingExceptionFromFinally:
active: false
ThrowingExceptionInMain:
@@ -179,53 +185,65 @@ exceptions:
ThrowingNewInstanceOfSameException:
active: false
TooGenericExceptionCaught:
- active: false
+ active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
exceptionNames:
- - ArrayIndexOutOfBoundsException
- - Error
- - Exception
- - IllegalMonitorStateException
- - NullPointerException
- - IndexOutOfBoundsException
- - RuntimeException
- - Throwable
- allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ - ArrayIndexOutOfBoundsException
+ - Error
+ - Exception
+ - IllegalMonitorStateException
+ - NullPointerException
+ - IndexOutOfBoundsException
+ - RuntimeException
+ - Throwable
+ allowedExceptionNameRegex: '^(_|(ignore|expected).*)'
TooGenericExceptionThrown:
active: true
exceptionNames:
- - Error
- - Exception
- - Throwable
- - RuntimeException
+ - Error
+ - Exception
+ - Throwable
+ - RuntimeException
formatting:
active: true
android: false
autoCorrect: true
+ AnnotationOnSeparateLine:
+ active: false
+ autoCorrect: true
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
+ EnumEntryNameCase:
+ active: false
+ autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
+ insertFinalNewLine: true
ImportOrdering:
active: false
+ autoCorrect: true
Indentation:
- active: true
+ active: false
autoCorrect: true
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
- maxLineLength: 150
+ maxLineLength: 173
ModifierOrdering:
active: true
autoCorrect: true
+ MultiLineIfElse:
+ active: true
+ autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
@@ -235,8 +253,9 @@ formatting:
NoEmptyClassBody:
active: true
autoCorrect: true
- NoItParamInMultilineLambda:
+ NoEmptyFirstLineInMethodBlock:
active: false
+ autoCorrect: true
NoLineBreakAfterElse:
active: true
autoCorrect: true
@@ -260,7 +279,6 @@ formatting:
autoCorrect: true
NoWildcardImports:
active: true
- autoCorrect: true
PackageName:
active: true
autoCorrect: true
@@ -277,6 +295,9 @@ formatting:
SpacingAroundCurly:
active: true
autoCorrect: true
+ SpacingAroundDot:
+ active: true
+ autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
@@ -297,60 +318,79 @@ naming:
active: true
ClassNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
classPattern: '[A-Z$][a-zA-Z0-9$]*'
ConstructorParameterNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
+ ignoreOverridden: true
EnumNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
forbiddenName: ''
FunctionMaxLength:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
minimumFunctionNameLength: 3
FunctionNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
- ignoreOverriddenFunctions: true
+ ignoreOverridden: true
+ InvalidPackageDeclaration:
+ active: false
+ rootPackage: ''
MatchingDeclarationName:
active: true
+ mustBeFirst: true
MemberNameEqualsClassName:
- active: false
- ignoreOverriddenFunction: true
+ active: true
+ ignoreOverridden: true
ObjectPropertyNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
- privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
maximumVariableNameLength: 64
VariableMinLength:
active: false
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
minimumVariableNameLength: 1
VariableNaming:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
@@ -359,46 +399,61 @@ naming:
performance:
active: true
ArrayPrimitive:
- active: false
+ active: true
ForEachOnRange:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
SpreadOperator:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
+ Deprecation:
+ active: false
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
- active: false
+ active: true
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
- InvalidRange:
+ HasPlatformType:
active: false
- IteratorHasNextCallsNextMethod:
+ ImplicitDefaultLocale:
active: false
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
IteratorNotThrowingNoSuchElementException:
- active: false
+ active: true
LateinitUsage:
active: false
- excludeAnnotatedProperties: ""
- ignoreOnClassesPattern: ""
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
+ excludeAnnotatedProperties: ''
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: false
+ MissingWhenCase:
+ active: true
+ RedundantElseInWhen:
+ active: true
UnconditionalJumpStatementInLoop:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
- active: false
+ active: true
UnsafeCast:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
- active: false
+ active: true
style:
active: true
@@ -407,10 +462,14 @@ style:
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
- EqualsNullCall:
+ DataClassShouldBeImmutable:
active: false
+ EqualsNullCall:
+ active: true
EqualsOnSignatureLine:
active: false
+ ExplicitCollectionElementAccessMethod:
+ active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
@@ -419,38 +478,54 @@ style:
ForbiddenComment:
active: true
values: 'TODO:,FIXME:,STOPSHIP:'
+ allowedPatterns: ''
ForbiddenImport:
active: false
- imports: ''
+ imports: []
+ forbiddenPatterns: ''
+ ForbiddenMethodCall:
+ active: false
+ methods: ''
+ ForbiddenPublicDataClass:
+ active: false
+ ignorePackages: '*.internal,*.internal.*'
ForbiddenVoid:
active: false
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
- active: false
+ active: true
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
+ excludeAnnotatedFunction: 'dagger.Provides'
+ LibraryCodeMustSpecifyReturnType:
+ active: true
LoopWithTooManyJumpStatements:
- active: false
+ active: true
maxJumpCount: 1
MagicNumber:
active: true
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
ignoreNumbers: '-1,0,1,2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
+ ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
+ ignoreRanges: false
MandatoryBracesIfStatements:
active: false
MaxLineLength:
active: true
- maxLineLength: 150
+ maxLineLength: 173
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
- active: false
+ active: true
ModifierOrder:
active: true
NestedClassesVisibility:
@@ -468,15 +543,18 @@ style:
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
- excludedFunctions: "equals"
+ excludedFunctions: 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
+ excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
@@ -492,12 +570,14 @@ style:
active: false
acceptableDecimalLength: 5
UnnecessaryAbstractClass:
+ active: true
+ excludeAnnotatedClasses: 'dagger.Module'
+ UnnecessaryAnnotationUseSiteTarget:
active: false
- excludeAnnotatedClasses: "dagger.Module"
UnnecessaryApply:
active: false
UnnecessaryInheritance:
- active: false
+ active: true
UnnecessaryLet:
active: false
UnnecessaryParentheses:
@@ -507,17 +587,29 @@ style:
UnusedImports:
active: false
UnusedPrivateClass:
- active: false
+ active: true
UnusedPrivateMember:
active: false
- allowedNames: "(_|ignored|expected|serialVersionUID)"
+ allowedNames: '(_|ignored|expected|serialVersionUID)'
+ UseArrayLiteralsInAnnotations:
+ active: false
+ UseCheckOrError:
+ active: false
UseDataClass:
active: false
- excludeAnnotatedClasses: ""
- UtilityClassWithPublicConstructor:
+ excludeAnnotatedClasses: ''
+ allowVars: false
+ UseIfInsteadOfWhen:
+ active: false
+ UseRequire:
active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
VarCouldBeVal:
active: false
WildcardImport:
active: true
- excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
+ excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
+ excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
\ No newline at end of file
diff --git a/docs/module_graph.png b/docs/module_graph.png
index 93bab79..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 d30e454..43c06ce 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -30,9 +30,49 @@ platform :android do
run_detekt()
end
- desc "Runs all tests in all modules"
- lane :test_all do
- run_all_tests()
+ desc "Runs tests in app module"
+ lane :test_app do
+ run_app_tests()
+ end
+
+ desc "Runs tests in core module"
+ lane :test_core do
+ run_core_tests()
+ end
+
+ desc "Runs tests in launches module"
+ lane :test_launches do
+ run_launches_tests()
+ end
+
+ desc "Runs tests in detail module"
+ lane :test_detail do
+ run_detail_tests()
+ end
+
+ 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 ================
@@ -45,7 +85,39 @@ platform :android do
gradle(task: "removeReports")
end
- def run_all_tests
- gradle(task: "clean test --continue")
+ def run_app_tests
+ gradle(task: "app:test --continue")
+ end
+
+ def run_core_tests
+ gradle(task: "core:test --continue")
+ end
+
+ def run_launches_tests
+ gradle(task: "features:launches:test --continue")
+ end
+
+ def run_detail_tests
+ gradle(task: "features:detail:test --continue")
+ end
+
+ 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 0b93486..cea8317 100644
--- a/features/detail/build.gradle
+++ b/features/detail/build.gradle
@@ -1,7 +1,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
-apply plugin: "androidx.navigation.safeargs"
apply from: "$rootProject.projectDir/scripts/feature_module.gradle"
diff --git a/features/detail/jacoco.exec b/features/detail/jacoco.exec
deleted file mode 100644
index b1922d8..0000000
Binary files a/features/detail/jacoco.exec and /dev/null differ
diff --git a/features/detail/src/main/AndroidManifest.xml b/features/detail/src/main/AndroidManifest.xml
index e7838c8..748078e 100644
--- a/features/detail/src/main/AndroidManifest.xml
+++ b/features/detail/src/main/AndroidManifest.xml
@@ -1,14 +1 @@
-
-
-
-
-
-
-
-
-
-
-
+
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..363e16d
--- /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/DetailContributor.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt
index f97d26a..ad1b7fa 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt
@@ -1,8 +1,8 @@
package com.melih.detail.di
import com.melih.detail.di.modules.DetailFragmentModule
+import com.melih.detail.di.scopes.DetailFragmentScope
import com.melih.detail.ui.DetailFragment
-import com.melih.list.di.scopes.DetailFragmentScope
import dagger.Module
import dagger.android.ContributesAndroidInjector
@@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector
@Module
abstract class DetailContributor {
- // region Contributes
+ //region Contributes
@ContributesAndroidInjector(
modules = [DetailFragmentModule::class]
)
@DetailFragmentScope
abstract fun detailFragment(): DetailFragment
- // endregion
+ //endregion
}
diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt
deleted file mode 100644
index 86b41bf..0000000
--- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.melih.detail.di
-
-import com.melih.detail.ui.DetailActivity
-import com.melih.list.di.scopes.DetailScope
-import dagger.Module
-import dagger.android.ContributesAndroidInjector
-
-/**
- * Contributes fragments & view models in this module
- */
-@Module
-abstract class DetailFeatureModule {
-
- // region Contributes
-
- @ContributesAndroidInjector(
- modules = [
- DetailContributor::class
- ]
- )
- @DetailScope
- abstract fun detailActivity(): DetailActivity
- // endregion
-}
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 314e70e..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
@@ -15,13 +19,16 @@ import dagger.multibindings.IntoMap
@Module
abstract class DetailFragmentModule {
- // region ViewModels
+ //region ViewModels
@Binds
@IntoMap
@ViewModelKey(DetailViewModel::class)
abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel
- // endregion
+
+ @Binds
+ abstract fun detailMapper(mapper: LaunchDetailMapper): Mapper
+ //endregion
@Module
companion object {
@@ -32,7 +39,7 @@ abstract class DetailFragmentModule {
@Provides
@JvmStatic
fun provideGetLaunchDetailParams(fragment: DetailFragment): GetLaunchDetails.Params {
- val args: DetailFragmentArgs by fragment.navArgs()
+ val args by fragment.navArgs()
return GetLaunchDetails.Params(args.launchId)
}
}
diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt
index c5a983b..61e38e2 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt
@@ -1,4 +1,4 @@
-package com.melih.list.di.scopes
+package com.melih.detail.di.scopes
import javax.inject.Scope
diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt
index e264fde..93d96b0 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt
@@ -1,4 +1,4 @@
-package com.melih.list.di.scopes
+package com.melih.detail.di.scopes
import javax.inject.Scope
diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt
deleted file mode 100644
index 0e7fbae..0000000
--- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.melih.detail.ui
-
-import android.os.Bundle
-import androidx.navigation.fragment.NavHostFragment
-import com.melih.core.actions.EXTRA_LAUNCH_ID
-import com.melih.core.base.lifecycle.BaseActivity
-import com.melih.detail.R
-import com.melih.detail.databinding.DetailActivityBinding
-
-const val INVALID_LAUNCH_ID = -1L
-
-class DetailActivity : BaseActivity() {
-
- // region Functions
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setSupportActionBar(binding.toolbar)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- supportActionBar?.setDisplayShowHomeEnabled(true)
- }
-
- override fun getLayoutId(): Int = R.layout.activity_detail
-
- override fun createNavHostFragment() =
- NavHostFragment.create(
- R.navigation.nav_detail,
- DetailFragmentArgs.Builder()
- .setLaunchId(intent?.extras?.getLong(EXTRA_LAUNCH_ID) ?: INVALID_LAUNCH_ID)
- .build()
- .toBundle()
- )
-
- override fun addNavHostTo(): Int = R.id.container
- // endregion
-}
diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt
index b9a9ca5..db6a8a3 100644
--- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt
+++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt
@@ -3,21 +3,20 @@ package com.melih.detail.ui
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.View
+import androidx.fragment.app.viewModels
import com.melih.core.base.lifecycle.BaseDaggerFragment
-import com.melih.core.extensions.createFor
import com.melih.core.extensions.observe
import com.melih.detail.R
import com.melih.detail.databinding.DetailBinding
class DetailFragment : BaseDaggerFragment() {
- // region Properties
+ //region Properties
- private val viewModel: DetailViewModel
- get() = viewModelFactory.createFor(this)
- // endregion
+ private val viewModel by viewModels { viewModelFactory }
+ //endregion
- // region Functions
+ //region Functions
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -28,11 +27,11 @@ class DetailFragment : BaseDaggerFragment() {
// Observing error to show toast with retry action
observe(viewModel.errorData) {
showSnackbarWithAction(it) {
- viewModel.retry()
+ viewModel.loadData()
}
}
}
override fun getLayoutId(): Int = R.layout.fragment_detail
- // endregion
+ //endregion
}
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 83c28dd..14b2f41 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,46 +1,52 @@
package com.melih.detail.ui
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.Transformations.map
+import androidx.lifecycle.viewModelScope
+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.flow.collect
+import kotlinx.coroutines.launch
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
+ //region Properties
- val rocketName = Transformations.map(successData) {
- it.rocket.name
+ val rocketName by lazy {
+ val nameData = map(successData) {
+ it.rocketName
+ }
+
+ loadData()
+
+ nameData
}
- val description = Transformations.map(successData) {
- if (it.missions.isEmpty()) {
- ""
- } else {
- it.missions[0].description
- }
+ val description = map(successData) {
+ it.missionDescription
}
- val imageUrl = Transformations.map(successData) {
- it.rocket.imageURL
+ val imageUrl = map(successData) {
+ it.imageUrl
}
- // endregion
+ //endregion
- // region Functions
+ //region Functions
/**
* Triggering interactor in view model scope
*/
- override suspend fun loadData() {
- getLaunchDetails(getLaunchDetailsParams).collect {
- it.handle(::handleState, ::handleFailure, ::handleSuccess)
+ fun loadData() {
+ viewModelScope.launch {
+ getLaunchDetails(getLaunchDetailsParams).collect {
+ it.handle(::handleState, ::handleFailure, ::handleSuccess)
+ }
}
}
- // endregion
+ //endregion
}
diff --git a/features/detail/src/main/res/layout/activity_detail.xml b/features/detail/src/main/res/layout/activity_detail.xml
deleted file mode 100644
index 89c60f2..0000000
--- a/features/detail/src/main/res/layout/activity_detail.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/features/detail/src/main/res/layout/fragment_detail.xml b/features/detail/src/main/res/layout/fragment_detail.xml
index abf4e90..e58cded 100644
--- a/features/detail/src/main/res/layout/fragment_detail.xml
+++ b/features/detail/src/main/res/layout/fragment_detail.xml
@@ -11,65 +11,59 @@
type="com.melih.detail.ui.DetailViewModel" />
-
-
+
-
+
-
-
-
-
-
+
+
diff --git a/features/detail/src/main/res/navigation/nav_detail.xml b/features/detail/src/main/res/navigation/nav_detail.xml
index 2f2f844..cbab98f 100644
--- a/features/detail/src/main/res/navigation/nav_detail.xml
+++ b/features/detail/src/main/res/navigation/nav_detail.xml
@@ -1,18 +1,22 @@
-
+
-
-
-
+
+
+
+
diff --git a/features/detail/src/main/res/values/strings.xml b/features/detail/src/main/res/values/strings.xml
index c9dc1ae..f4f6713 100644
--- a/features/detail/src/main/res/values/strings.xml
+++ b/features/detail/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
+
- Image of the rocket
+ Image of the rocket
diff --git a/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt b/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt
index 82056d4..135cb5e 100644
--- a/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt
+++ b/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
-@UseExperimental(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseTestWithMainThread {
protected val dispatcher = TestCoroutineDispatcher()
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..738d89c 100644
--- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt
+++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt
@@ -1,12 +1,18 @@
package com.melih.detail
+import androidx.lifecycle.viewModelScope
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.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import org.amshove.kluent.shouldEqualTo
import org.junit.jupiter.api.Test
@@ -16,25 +22,10 @@ import org.junit.jupiter.api.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
-@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))
-
- @Test
- fun `loadData should invoke getLauchDetails with provided params`() {
- dispatcher.runBlockingTest {
-
- val paramsSlot = slot()
-
- viewModel.loadData()
-
- // init should have called it already due to creation above
- verify(exactly = 1) { getLaunchDetails(capture(paramsSlot)) }
- paramsSlot.captured.id shouldEqualTo 1013
- }
- }
}
diff --git a/features/launches/build.gradle b/features/launches/build.gradle
index 1128480..740d414 100644
--- a/features/launches/build.gradle
+++ b/features/launches/build.gradle
@@ -8,6 +8,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.paging
+ implementation libraries.swipeRefreshLayout
testImplementation testLibraries.coroutinesTest
}
diff --git a/features/launches/jacoco.exec b/features/launches/jacoco.exec
deleted file mode 100644
index 65d2458..0000000
Binary files a/features/launches/jacoco.exec and /dev/null differ
diff --git a/features/launches/src/main/AndroidManifest.xml b/features/launches/src/main/AndroidManifest.xml
index 2323aa1..01b8e2a 100644
--- a/features/launches/src/main/AndroidManifest.xml
+++ b/features/launches/src/main/AndroidManifest.xml
@@ -1,14 +1 @@
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
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..4a24bee
--- /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/list/di/LaunchesContributor.kt b/features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt
similarity index 60%
rename from features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt
rename to features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt
index 9b1d947..a69725e 100644
--- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt
@@ -1,8 +1,8 @@
-package com.melih.list.di
+package com.melih.launches.di
-import com.melih.list.di.modules.LaunchesFragmentModule
-import com.melih.list.di.scopes.LaunchesFragmentScope
-import com.melih.list.ui.LaunchesFragment
+import com.melih.launches.di.modules.LaunchesFragmentModule
+import com.melih.launches.di.scopes.LaunchesFragmentScope
+import com.melih.launches.ui.LaunchesFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector
@Module
abstract class LaunchesContributor {
- // region Contributes
+ //region Contributes
@ContributesAndroidInjector(
modules = [LaunchesFragmentModule::class]
)
@LaunchesFragmentScope
abstract fun launchesFragment(): LaunchesFragment
- // endregion
+ //endregion
}
diff --git a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
similarity index 56%
rename from features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt
rename to features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
index eadf9b4..8841558 100644
--- a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt
@@ -1,11 +1,15 @@
-package com.melih.list.di.modules
+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.list.ui.vm.LaunchesViewModel
-import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT
-import com.melih.repository.interactors.GetLaunches
+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 dagger.Binds
import dagger.Module
import dagger.Provides
@@ -14,13 +18,16 @@ import dagger.multibindings.IntoMap
@Module
abstract class LaunchesFragmentModule {
- // region ViewModels
+ //region Binds
@Binds
@IntoMap
@ViewModelKey(LaunchesViewModel::class)
- abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel
- // endregion
+ abstract fun launchesViewModel(listViewModel: LaunchesViewModel): ViewModel
+
+ @Binds
+ abstract fun launchMapper(mapper: LaunchMapper): Mapper
+ //endregion
@Module
companion object {
diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt
similarity index 75%
rename from features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt
rename to features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt
index e5b898b..eb5898f 100644
--- a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt
@@ -1,4 +1,4 @@
-package com.melih.list.di.scopes
+package com.melih.launches.di.scopes
import javax.inject.Scope
diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt
similarity index 73%
rename from features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt
rename to features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt
index 31b4887..24baafb 100644
--- a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt
@@ -1,4 +1,4 @@
-package com.melih.list.di.scopes
+package com.melih.launches.di.scopes
import javax.inject.Scope
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
new file mode 100644
index 0000000..af60af3
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt
@@ -0,0 +1,93 @@
+package com.melih.launches.ui
+
+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.data.LaunchItem
+import com.melih.launches.databinding.LaunchesBinding
+import com.melih.launches.ui.adapters.LaunchesAdapter
+import com.melih.launches.ui.vm.LaunchesViewModel
+import javax.inject.Inject
+
+class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener {
+
+ //region Properties
+
+ private val viewModel by viewModels { viewModelFactory }
+
+ private val launchesAdapter = LaunchesAdapter(::onItemSelected)
+ //endregion
+
+ //region Lifecyle
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ //setHasOptionsMenu(true)
+
+ binding.rocketList.adapter = launchesAdapter
+ binding.swipeRefreshLayout.setOnRefreshListener(this)
+
+ observeDataChanges()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683
+ binding.swipeRefreshLayout.isEnabled = true
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683
+ binding.swipeRefreshLayout.isEnabled = false
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ binding.rocketList.adapter = null
+ }
+ //endregion
+
+ //region Functions
+
+ private fun observeDataChanges() {
+
+ // Observing state to show loading
+ observe(viewModel.stateData) {
+ binding.swipeRefreshLayout.isRefreshing = it is State.Loading
+ }
+
+ // Observing error to show toast with retry action
+ observe(viewModel.errorData) {
+ if (it !is PersistenceEmptyError) {
+ showSnackbarWithAction(it) {
+ viewModel.retry()
+ }
+ }
+ }
+
+ observe(viewModel.pagedList) {
+ launchesAdapter.submitList(it)
+ }
+ }
+
+ private fun onItemSelected(item: LaunchItem) {
+ openDetail(item.id)
+ }
+
+ override fun onRefresh() {
+ viewModel.refresh()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_launches
+ //endregion
+}
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
new file mode 100644
index 0000000..7f8f423
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt
@@ -0,0 +1,37 @@
+package com.melih.launches.ui.adapters
+
+import android.view.LayoutInflater
+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
+
+class LaunchesAdapter(itemClickListener: (LaunchItem) -> Unit) : BasePagingListAdapter(
+ createDiffCallback { oldItem, newItem -> oldItem.id == newItem.id },
+ itemClickListener
+) {
+
+ //region Functions
+
+ override fun createViewHolder(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ viewType: Int
+ ): BaseViewHolder =
+ LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false))
+ //endregion
+}
+
+class LaunchesViewHolder(private val binding: LaunchRowBinding) :
+ BaseViewHolder(binding) {
+
+ //region Functions
+
+ override fun bind(item: LaunchItem) {
+ binding.entity = item
+ 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
new file mode 100644
index 0000000..599165b
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt
@@ -0,0 +1,26 @@
+package com.melih.launches.ui.paging
+
+import com.melih.core.base.paging.BasePagingDataSource
+import com.melih.interactors.GetLaunches
+import com.melih.launches.data.LaunchItem
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Inject
+
+/**
+ * Uses [GetLaunches] to get data for pagination
+ */
+class LaunchesPagingSource @Inject constructor(
+ private val getLaunches: GetLaunches,
+ private val getLaunchesParams: GetLaunches.Params
+) : BasePagingDataSource() {
+
+ //region Functions
+
+ override fun loadDataForPage(page: Int) =
+ getLaunches(
+ getLaunchesParams.copy(
+ page = page
+ )
+ )
+ //endregion
+}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
similarity index 53%
rename from features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt
rename to features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
index d176076..7a6cfcf 100644
--- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt
@@ -1,14 +1,17 @@
-package com.melih.list.ui.paging
+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() {
- override fun createSource(): BasePagingDataSource = sourceProvider.get()
+ //region Functions
+
+ 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
new file mode 100644
index 0000000..d026a54
--- /dev/null
+++ b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt
@@ -0,0 +1,23 @@
+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 javax.inject.Inject
+
+class LaunchesViewModel @Inject constructor(
+ private val launchesPagingSourceFactory: LaunchesPagingSourceFactory,
+ private val launchesPagingConfig: PagedList.Config
+) : BasePagingViewModel() {
+
+ //region Properties
+
+ override val factory: BasePagingFactory
+ get() = launchesPagingSourceFactory
+
+ override val config: PagedList.Config
+ get() = launchesPagingConfig
+ //endregion
+}
diff --git a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt
deleted file mode 100644
index 68ae1be..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.melih.list.di
-
-import com.melih.list.di.scopes.LaunchesScope
-import com.melih.list.ui.LaunchesActivity
-import dagger.Module
-import dagger.android.ContributesAndroidInjector
-
-/**
- * Contributes fragments & view models in this module
- */
-@Module
-abstract class LaunchesFeatureModule {
-
- // region Contributes
-
- @ContributesAndroidInjector(
- modules = [
- LaunchesContributor::class
- ]
- )
- @LaunchesScope
- abstract fun launchesActivity(): LaunchesActivity
- // endregion
-}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt
deleted file mode 100644
index 529b4ae..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.melih.list.ui
-
-import android.os.Bundle
-import androidx.navigation.fragment.NavHostFragment
-import com.melih.core.base.lifecycle.BaseActivity
-import com.melih.list.R
-import com.melih.list.databinding.LaunchesActivityBinding
-
-class LaunchesActivity : BaseActivity() {
-
- // region Functions
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setSupportActionBar(binding.toolbar)
- }
-
- override fun getLayoutId(): Int = R.layout.activity_launches
-
- override fun createNavHostFragment() =
- NavHostFragment.create(R.navigation.nav_launches)
-
- override fun addNavHostTo(): Int = R.id.container
- // endregion
-}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt
deleted file mode 100644
index 816f40c..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.melih.list.ui
-
-import android.os.Bundle
-import android.view.View
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import com.melih.core.actions.Actions
-import com.melih.core.base.lifecycle.BaseDaggerFragment
-import com.melih.core.extensions.createFor
-import com.melih.core.extensions.observe
-import com.melih.list.R
-import com.melih.list.databinding.ListBinding
-import com.melih.list.ui.adapters.LaunchesAdapter
-import com.melih.list.ui.vm.LaunchesViewModel
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.State
-
-class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener {
-
- // region Properties
-
- private val viewModel: LaunchesViewModel
- get() = viewModelFactory.createFor(this)
-
- private val launchesAdapter = LaunchesAdapter(::onItemSelected)
- // endregion
-
- // region Functions
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- //setHasOptionsMenu(true)
-
- binding.rocketList.adapter = launchesAdapter
- binding.swipeRefreshLayout.setOnRefreshListener(this)
-
- observeDataChanges()
- }
-
- //override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- // inflater.inflate(R.menu.menu_rocket_list, menu)
- //
- // with(menu.findItem(R.id.search)) {
- // onExpandOrCollapse(::onSearchExpand, ::onSearchCollapse)
- // setSearchQueryListener(actionView as SearchView)
- // }
- //
- // super.onCreateOptionsMenu(menu, inflater)
- //}
-
- private fun observeDataChanges() {
-
- // Observing state to show loading
- observe(viewModel.stateData) {
- binding.swipeRefreshLayout.isRefreshing = it is State.Loading
- }
-
- // Observing error to show toast with retry action
- observe(viewModel.errorData) {
- showSnackbarWithAction(it) {
- viewModel.retry()
- }
- }
-
- observe(viewModel.pagedList) {
- launchesAdapter.submitList(it)
- }
-
- //observe(viewModel.filteredItems) {
- // launchesAdapter.submitList(it)
- //}
- }
-
- private fun onItemSelected(item: LaunchEntity) {
- startActivity(Actions.openDetailFor(item.id))
- }
-
- //private fun onSearchExpand() {
- // binding.swipeRefreshLayout.isEnabled = false
- //}
-
- //private fun onSearchCollapse() {
- // binding.swipeRefreshLayout.isEnabled = true
- //}
-
- //private fun setSearchQueryListener(searchView: SearchView) {
- // searchView.setOnQueryChangedListener {
- // viewModel.filterItemListBy(it)
- // }
- //}
-
- override fun onRefresh() {
- viewModel.refresh()
- }
-
- override fun getLayoutId(): Int = R.layout.fragment_launches
- // endregion
-}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt
deleted file mode 100644
index c998b2c..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.melih.list.ui.adapters
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import com.melih.core.base.recycler.BasePagingListAdapter
-import com.melih.core.base.recycler.BaseViewHolder
-import com.melih.core.extensions.getDiffCallbackForType
-import com.melih.list.databinding.LaunchRowBinding
-import com.melih.repository.entities.LaunchEntity
-
-class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter(
- getDiffCallbackForType { oldItem, newItem -> oldItem.id == newItem.id },
- itemClickListener
-) {
- override fun createViewHolder(
- inflater: LayoutInflater,
- parent: ViewGroup,
- viewType: Int
- ): BaseViewHolder =
- LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false))
-
-}
-
-class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) {
-
- override fun bind(item: LaunchEntity) {
- binding.entity = item
-
- val missions = item.missions
- binding.tvDescription.text = if (!missions.isNullOrEmpty()) missions[0].description else ""
-
- binding.executePendingBindings()
- }
-}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt
deleted file mode 100644
index 9ec0bf8..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.melih.list.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 kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import javax.inject.Inject
-
-class LaunchesPagingSource @Inject constructor(
- private val getLaunches: GetLaunches,
- private val getLaunchesParams: GetLaunches.Params
-) : BasePagingDataSource() {
-
- //region Functions
-
- @UseExperimental(ExperimentalCoroutinesApi::class)
- override fun loadDataForPage(page: Int): Flow>> =
- getLaunches(
- getLaunchesParams.copy(
- page = page
- )
- )
- //endregion
-}
diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt
deleted file mode 100644
index 4576d9c..0000000
--- a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.melih.list.ui.vm
-
-import androidx.paging.PagedList
-import com.melih.core.base.paging.BasePagingFactory
-import com.melih.core.base.viewmodel.BasePagingViewModel
-import com.melih.list.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() {
-
- // region Properties
-
- override val factory: BasePagingFactory
- get() = launchesPagingSourceFactory
-
- override val config: PagedList.Config
- get() = launchesPagingConfig
-
- //private val _filteredItems = MediatorLiveData>()
-
- //val filteredItems: LiveData>
- // get() = _filteredItems
- // endregion
-
- //init {
- // _filteredItems.addSource(pagedList, _filteredItems::setValue)
- //}
-
- // region Functions
-
- //fun filterItemListBy(query: String?) {
- //
- // _filteredItems.value = if (!query.isNullOrBlank()) {
- // pagedList.value
- // ?.snapshot() as PagedList
- // } else {
- // pagedList.value
- // }
- //}
- // endregion
-}
diff --git a/features/launches/src/main/res/layout/activity_launches.xml b/features/launches/src/main/res/layout/activity_launches.xml
deleted file mode 100644
index 4369e55..0000000
--- a/features/launches/src/main/res/layout/activity_launches.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml
index cac321d..967e940 100644
--- a/features/launches/src/main/res/layout/fragment_launches.xml
+++ b/features/launches/src/main/res/layout/fragment_launches.xml
@@ -1,37 +1,36 @@
+ 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/launches/src/main/res/layout/row_launch.xml b/features/launches/src/main/res/layout/row_launch.xml
index bb62bbf..731d166 100644
--- a/features/launches/src/main/res/layout/row_launch.xml
+++ b/features/launches/src/main/res/layout/row_launch.xml
@@ -8,10 +8,10 @@
+ type="com.melih.launches.data.LaunchItem" />
-
-
-
-
+
-
+
diff --git a/features/launches/src/main/res/navigation/nav_launches.xml b/features/launches/src/main/res/navigation/nav_launches.xml
index 03ee663..4df04f4 100644
--- a/features/launches/src/main/res/navigation/nav_launches.xml
+++ b/features/launches/src/main/res/navigation/nav_launches.xml
@@ -1,13 +1,15 @@
-
+
-
-
\ No newline at end of file
+
+
diff --git a/features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt
similarity index 88%
rename from features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt
rename to features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt
index 358b7d3..43d86ae 100644
--- a/features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt
+++ b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt
@@ -1,6 +1,4 @@
-@file:UseExperimental(ExperimentalCoroutinesApi::class)
-
-package com.melih.list
+package com.melih.launches
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -10,6 +8,7 @@ import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
+@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseTestWithMainThread {
protected val dispatcher = TestCoroutineDispatcher()
diff --git a/gradle.properties b/gradle.properties
index 70e38b1..0f7ea80 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,26 +1,22 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
+## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app's APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
+#Wed Jan 01 15:32:47 CET 2020
+android.databinding.incremental=true
android.enableJetifier=true
-# Kotlin code style for this project: "official" or "obsolete":
-kotlin.code.style=official
-# For build performance
-kotlin.incremental=true
+android.useAndroidX=true
+kapt.include.compile.classpath=false
kapt.incremental.apt=true
kapt.use.worker.api=true
-kapt.include.compile.classpath=false
+kotlin.code.style=official
+kotlin.incremental=true
+org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 944f2c1..ca90413 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Jun 13 10:50:25 CEST 2019
+#Mon Jul 06 12:03:00 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-rc-1-all.zip
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 1af640d..0000000
--- a/repository/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/repository/src/main/kotlin/com/melih/repository/Constants.kt b/repository/src/main/kotlin/com/melih/repository/Constants.kt
deleted file mode 100644
index 05de778..0000000
--- a/repository/src/main/kotlin/com/melih/repository/Constants.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.melih.repository
-
-const val DEFAULT_NAME = "Default name"
-const val EMPTY_STRING = ""
-const val DEFAULT_IMAGE_SIZE = 480
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 82a9d41..0000000
--- a/repository/src/main/kotlin/com/melih/repository/Repository.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.melih.repository
-
-import com.melih.repository.entities.LaunchEntity
-import com.melih.repository.interactors.base.Result
-
-/**
- * Abstract class to create contract in sources to seperate low level business logic from build and return type
- */
-abstract class Repository {
-
- internal abstract suspend fun getNextLaunches(count: Int, page: Int): Result>
- internal abstract suspend fun getLaunchById(id: Long): Result
-}
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 7bf13d5..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt
+++ /dev/null
@@ -1,49 +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() {
-
- @field:Inject
- internal lateinit var networkSource: NetworkSource
-
- @field:Inject
- internal lateinit var persistenceSource: PersistenceSource
-
- 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)
- }
- }
-
- 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 5cc80ec..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt
+++ /dev/null
@@ -1,48 +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>() {
-
- @field:Inject
- internal lateinit var networkSource: NetworkSource
-
- @field:Inject
- internal lateinit var persistenceSource: PersistenceSource
-
- 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))
- }
- }
- }
-
- 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 e14b96e..0000000
--- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt
+++ /dev/null
@@ -1,18 +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)
-
-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)
diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt
deleted file mode 100644
index df4b014..0000000
--- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.melih.repository.persistence.dao
-
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
-import com.melih.repository.entities.LaunchEntity
-
-/**
- * DAO for list of [launches][LaunchEntity]
- */
-@Dao
-internal abstract class LaunchesDao {
-
- // region Queries
-
- @Query("SELECT * FROM Launches ORDER BY launchStartTime DESC LIMIT :count OFFSET :page*:count")
- abstract suspend fun getLaunches(count: Int, page: Int): List
-
- @Query("SELECT * FROM Launches WHERE id=:id LIMIT 1")
- abstract suspend fun getLaunchById(id: Long): LaunchEntity?
-
- @Query("DELETE FROM Launches")
- abstract suspend fun nukeLaunches()
- // endregion
-
- // region Insertion
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- abstract suspend fun saveLaunches(launches: List)
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- abstract suspend fun saveLaunch(launch: LaunchEntity)
- // 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 ed14fbe..0000000
--- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.melih.repository.sources
-
-import android.net.NetworkInfo
-import com.melih.repository.DEFAULT_IMAGE_SIZE
-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
-
-/**
- * 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 suspend inline fun safeExecute(
- block: suspend () -> 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 366c683..0000000
--- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt
+++ /dev/null
@@ -1,49 +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 81%
rename from scripts/detekt.gradle
rename to scripts/cq/detekt.gradle
index 5f07c3c..68347a6 100644
--- a/scripts/detekt.gradle
+++ b/scripts/cq/detekt.gradle
@@ -1,9 +1,8 @@
apply plugin: 'io.gitlab.arturbosch.detekt'
detekt {
- toolVersion = "1.0.0"
+ failFast = true
config = files("$rootProject.projectDir/default-detekt-config.yml")
- filters = ".*/resources/.*,.*/build/.*"
reports {
html {
diff --git a/scripts/cq/dokka.gradle b/scripts/cq/dokka.gradle
new file mode 100644
index 0000000..2590827
--- /dev/null
+++ b/scripts/cq/dokka.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'org.jetbrains.dokka'
+
+dokka {
+ outputFormat = "html"
+ outputDirectory = "$rootProject.projectDir/reports/javadoc"
+
+ configuration {
+ jdkVersion = 8
+
+ reportUndocumented = true
+ skipEmptyPackages = true
+ }
+}
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 b5ab234..ed21087 100644
--- a/scripts/default_android_config.gradle
+++ b/scripts/default_android_config.gradle
@@ -1,7 +1,10 @@
-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
+ buildToolsVersion versions.buildToolsVersion
defaultConfig {
minSdkVersion versions.minSdkVersion
@@ -10,4 +13,18 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+
+ testOptions.unitTests.all {
+ jvmArgs "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap"
+ }
+
+ defaultConfig {
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = [
+ "dagger.experimentalDaggerErrorMessages": "enabled"
+ ]
+ }
+ }
+ }
}
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 2c3811a..17d113a 100644
--- a/scripts/dependencies.gradle
+++ b/scripts/dependencies.gradle
@@ -1,77 +1,80 @@
ext {
versions = [
- minSdkVersion : 21,
- compileSdkVersion : 28,
- targetSdkVersion : 28,
- buildToolsVersion : "28.0.3",
- supportLibraryVersion : "28.0.0",
- appCompatVersion : "1.1.0-rc01",
- lifecycleVersion : "2.2.0-alpha03",
- fragmentVersion : "1.2.0-alpha02",
- workManagerVersion : "2.2.0-rc01",
- constraintLayoutVesion: "2.0.0-beta1",
- cardViewVersion : "1.0.0",
- recyclerViewVersion : "1.1.0-beta02",
- pagingVersion : "2.1.0",
- viewPagerVersion : "1.0.0-beta03",
- collectionVersion : "1.1.0",
- roomVersion : "2.2.0-alpha02",
- daggerVersion : "2.24",
- okHttpVersion : "4.0.1",
- retrofitVersion : "2.6.1",
- picassoVersion : "2.71828",
- moshiVersion : "1.8.0",
- coroutinesVersion : "1.3.0-RC2",
- leakCanaryVersion : "2.0-beta-2",
- timberVersion : "4.7.1",
- jUnitVersion : "5.5.1",
- espressoVersion : "3.2.0",
- mockkVersion : "1.9.3",
- kluentVersion : "1.53",
+ minSdkVersion : 21,
+ compileSdkVersion : 29,
+ targetSdkVersion : 29,
+ buildToolsVersion : "29.0.3",
+ appCompatVersion : "1.1.0",
+ lifecycleVersion : "2.2.0",
+ fragmentVersion : "1.3.0-alpha06",
+ workManagerVersion : "2.4.0-alpha01",
+ constraintLayoutVesion : "2.0.0-beta7",
+ cardViewVersion : "1.0.0",
+ recyclerViewVersion : "1.2.0-alpha01",
+ pagingVersion : "2.1.2",
+ viewPagerVersion : "1.0.0",
+ materialVersion : "1.3.0-alpha01",
+ swipeRefreshLayoutVersion: "1.1.0",
+ collectionVersion : "1.1.0",
+ roomVersion : "2.2.5",
+ daggerVersion : "2.27",
+ okHttpVersion : "4.7.2",
+ retrofitVersion : "2.9.0",
+ picassoVersion : "2.71828",
+ moshiVersion : "1.9.3",
+ coroutinesVersion : "1.3.5",
+ leakCanaryVersion : "2.2",
+ timberVersion : "4.7.1",
+ jUnitVersion : "5.6.2",
+ espressoVersion : "3.2.0",
+ mockkVersion : "1.10.0",
+ kluentVersion : "1.60",
]
libraries = [
/**
* Android libraries
*/
- appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}",
- recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}",
- cardView : "androidx.cardview:cardview:${versions.cardViewVersion}",
- constraintLayout: "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}",
- multixDex : "androidx.multidex:multidex:2.0.1",
- fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}",
+ appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}",
+ recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}",
+ cardView : "androidx.cardview:cardview:${versions.cardViewVersion}",
+ constraintLayout : "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}",
+ multixDex : "androidx.multidex:multidex:2.0.1",
+ fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}",
+ material : "com.google.android.material:material:${versions.materialVersion}",
/**
* Jetpack
*/
- navigation : [
+ navigation : [
"androidx.navigation:navigation-fragment-ktx:$nav_version",
"androidx.navigation:navigation-ui-ktx:$nav_version"
],
- room : [
+ room : [
"androidx.room:room-runtime:${versions.roomVersion}",
"androidx.room:room-ktx:${versions.roomVersion}"
],
- lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}",
- liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}",
- workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}",
- paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}",
- viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}",
- collection : "androidx.collection:collection-ktx:${versions.collectionVersion}",
+ lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}",
+ liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}",
+ workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}",
+ paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}",
+ viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}",
+ collection : "androidx.collection:collection-ktx:${versions.collectionVersion}",
+ swipeRefreshLayout: "androidx.swiperefreshlayout:swiperefreshlayout:${versions.swipeRefreshLayoutVersion}",
/**
* Kotlin
*/
- kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version",
- coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}",
+ kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version",
+ coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}",
/**
* Dagger
*/
- dagger : [
+ dagger : [
"com.google.dagger:dagger:${versions.daggerVersion}",
"com.google.dagger:dagger-android:${versions.daggerVersion}",
"com.google.dagger:dagger-android-support:${versions.daggerVersion}"
@@ -80,17 +83,17 @@ ext {
/**
* OkHttp
*/
- okHttp : [
+ okHttp : [
"com.squareup.okhttp3:okhttp:${versions.okHttpVersion}",
"com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}"
],
- okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}",
+ okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}",
/**
* Retrofit
*/
- retrofit : [
+ retrofit : [
"com.squareup.retrofit2:retrofit:${versions.retrofitVersion}",
"com.squareup.retrofit2:converter-moshi:${versions.retrofitVersion}"
],
@@ -98,27 +101,27 @@ ext {
/**
* Moshi
*/
- moshi : [
+ moshi : [
"com.squareup.moshi:moshi:${versions.moshiVersion}",
"com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}"
],
- moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}",
+ moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}",
/**
* Picasso for image loading
*/
- picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}",
+ picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}",
/**
* LeakCanary
*/
- leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}",
+ leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}",
/**
* Timber
*/
- timber : "com.jakewharton.timber:timber:${versions.timberVersion}"
+ timber : "com.jakewharton.timber:timber:${versions.timberVersion}"
]
annotationProcessors = [
diff --git a/scripts/dokka.gradle b/scripts/dokka.gradle
deleted file mode 100644
index 59bed4c..0000000
--- a/scripts/dokka.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'org.jetbrains.dokka-android'
-
-dokka {
- outputFormat = "html"
- outputDirectory = "$rootProject.projectDir/reports/javadoc"
- jdkVersion = 8
-
- reportUndocumented = true
- skipEmptyPackages = true
-}
diff --git a/scripts/feature_module.gradle b/scripts/feature_module.gradle
index e6ceeb1..83bba26 100644
--- a/scripts/feature_module.gradle
+++ b/scripts/feature_module.gradle
@@ -1,13 +1,19 @@
+apply plugin: "androidx.navigation.safeargs"
+
apply from: "$rootProject.projectDir/scripts/module.gradle"
+apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle"
android {
- dataBinding {
- enabled = true
+
+ buildFeatures{
+ dataBinding = true
}
}
dependencies {
implementation project(':core')
+ implementation project(':data:interactors')
+ implementation project(':data:definitions')
implementation libraries.fragment
implementation libraries.lifecycle
diff --git a/scripts/flavors.gradle b/scripts/flavors.gradle
deleted file mode 100644
index 496ab22..0000000
--- a/scripts/flavors.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-android {
- buildTypes {
- release {
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
-
- debug {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- testCoverageEnabled = true
- }
-
- dev {
- initWith debug
- }
- }
-}
diff --git a/scripts/module.gradle b/scripts/module.gradle
index cd8295f..de40f3f 100644
--- a/scripts/module.gradle
+++ b/scripts/module.gradle
@@ -1,3 +1,2 @@
apply from: "$rootProject.projectDir/scripts/default_android_config.gradle"
apply from: "$rootProject.projectDir/scripts/sources.gradle"
-apply from: "$rootProject.projectDir/scripts/flavors.gradle"
\ No newline at end of file
diff --git a/scripts/sources.gradle b/scripts/sources.gradle
index dcbd8fc..a6af0a8 100644
--- a/scripts/sources.gradle
+++ b/scripts/sources.gradle
@@ -14,4 +14,21 @@ android {
jvmTarget = '1.8'
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
}
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+
+ debug {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ testCoverageEnabled = true
+ }
+
+ dev {
+ initWith debug
+ }
+ }
}
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'