diff --git a/.circleci/config.yml b/.circleci/config.yml index 11b3e90..f174524 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,7 @@ jobs: - image: circleci/android:api-28 environment: JVM_OPTS: -Xmx3200m + CODECOV_TOKEN: "cd1376e1-2cfd-49a2-9f25-03ef69056b4d" steps: - checkout - restore_cache: @@ -32,7 +33,10 @@ jobs: # Tests - run: name: Tests - command: fastlane test_all + command: | + fastlane test_all + ./gradlew 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 - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ diff --git a/.gitignore b/.gitignore index 84d7b28..d1bd31e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /build /captures .externalNativeBuild +/projectFilesBackup # Project reports /reports diff --git a/.gradletasknamecache b/.gradletasknamecache new file mode 100644 index 0000000..aeab705 --- /dev/null +++ b/.gradletasknamecache @@ -0,0 +1,3130 @@ +app:androidDependencies +core:androidDependencies +features:detail:androidDependencies +features:launches:androidDependencies +repository:androidDependencies +app:signingReport +core:signingReport +features:detail:signingReport +features:launches:signingReport +repository:signingReport +app:sourceSets +core:sourceSets +features:detail:sourceSets +features:launches:sourceSets +repository:sourceSets +app:assemble +core:assemble +features:detail:assemble +features:launches:assemble +repository:assemble +app:assembleAndroidTest +core:assembleAndroidTest +features:detail:assembleAndroidTest +features:launches:assembleAndroidTest +repository:assembleAndroidTest +app:build +core:build +features:detail:build +features:launches:build +repository:build +app:buildDependents +core:buildDependents +features:detail:buildDependents +features:launches:buildDependents +repository:buildDependents +app:buildNeeded +core:buildNeeded +features:detail:buildNeeded +features:launches:buildNeeded +repository:buildNeeded +app:bundle +app:clean +core:clean +features:detail:clean +features:launches:clean +repository:clean +app:cleanBuildCache +core:cleanBuildCache +features:detail:cleanBuildCache +features:launches:cleanBuildCache +repository:cleanBuildCache +app:compileDebugAndroidTestSources +core:compileDebugAndroidTestSources +features:detail:compileDebugAndroidTestSources +features:launches:compileDebugAndroidTestSources +repository:compileDebugAndroidTestSources +app:compileDebugSources +core:compileDebugSources +features:detail:compileDebugSources +features:launches:compileDebugSources +repository:compileDebugSources +app:compileDebugUnitTestSources +core:compileDebugUnitTestSources +features:detail:compileDebugUnitTestSources +features:launches:compileDebugUnitTestSources +repository:compileDebugUnitTestSources +app:compileDevSources +core:compileDevSources +features:detail:compileDevSources +features:launches:compileDevSources +repository:compileDevSources +app:compileDevUnitTestSources +core:compileDevUnitTestSources +features:detail:compileDevUnitTestSources +features:launches:compileDevUnitTestSources +repository:compileDevUnitTestSources +app:compileReleaseSources +core:compileReleaseSources +features:detail:compileReleaseSources +features:launches:compileReleaseSources +repository:compileReleaseSources +app:compileReleaseUnitTestSources +core:compileReleaseUnitTestSources +features:detail:compileReleaseUnitTestSources +features:launches:compileReleaseUnitTestSources +repository:compileReleaseUnitTestSources +core:extractDebugAnnotations +features:detail:extractDebugAnnotations +features:launches:extractDebugAnnotations +repository:extractDebugAnnotations +core:extractDevAnnotations +features:detail:extractDevAnnotations +features:launches:extractDevAnnotations +repository:extractDevAnnotations +core:extractReleaseAnnotations +features:detail:extractReleaseAnnotations +features:launches:extractReleaseAnnotations +repository:extractReleaseAnnotations +init +wrapper +app:lintFix +core:lintFix +features:detail:lintFix +features:launches:lintFix +repository:lintFix +dokka +app:dokka +core:dokka +features:detail:dokka +features:launches:dokka +repository:dokka +buildEnvironment +app:buildEnvironment +core:buildEnvironment +features:buildEnvironment +features:detail:buildEnvironment +features:launches:buildEnvironment +repository:buildEnvironment +components +app:components +core:components +features:components +features:detail:components +features:launches:components +repository:components +dependencies +app:dependencies +core:dependencies +features:dependencies +features:detail:dependencies +features:launches:dependencies +repository:dependencies +dependencyInsight +app:dependencyInsight +core:dependencyInsight +features:dependencyInsight +features:detail:dependencyInsight +features:launches:dependencyInsight +repository:dependencyInsight +dependentComponents +app:dependentComponents +core:dependentComponents +features:dependentComponents +features:detail:dependentComponents +features:launches:dependentComponents +repository:dependentComponents +help +app:help +core:help +features:help +features:detail:help +features:launches:help +repository:help +model +app:model +core:model +features:model +features:detail:model +features:launches:model +repository:model +projects +app:projects +core:projects +features:projects +features:detail:projects +features:launches:projects +repository:projects +properties +app:properties +core:properties +features:properties +features:detail:properties +features:launches:properties +repository:properties +tasks +app:tasks +core:tasks +features:tasks +features:detail:tasks +features:launches:tasks +repository:tasks +app:installDebug +app:installDebugAndroidTest +core:installDebugAndroidTest +features:detail:installDebugAndroidTest +features:launches:installDebugAndroidTest +repository:installDebugAndroidTest +app:installDev +app:uninstallAll +core:uninstallAll +features:detail:uninstallAll +features:launches:uninstallAll +repository:uninstallAll +app:uninstallDebug +app:uninstallDebugAndroidTest +core:uninstallDebugAndroidTest +features:detail:uninstallDebugAndroidTest +features:launches:uninstallDebugAndroidTest +repository:uninstallDebugAndroidTest +app:uninstallDev +app:uninstallRelease +app:jacocoTestReport +core:jacocoTestReport +features:detail:jacocoTestReport +features:launches:jacocoTestReport +repository:jacocoTestReport +app:jacocoTestReportDebug +core:jacocoTestReportDebug +features:detail:jacocoTestReportDebug +features:launches:jacocoTestReportDebug +repository:jacocoTestReportDebug +app:jacocoTestReportDev +core:jacocoTestReportDev +features:detail:jacocoTestReportDev +features:launches:jacocoTestReportDev +repository:jacocoTestReportDev +app:jacocoTestReportRelease +core:jacocoTestReportRelease +features:detail:jacocoTestReportRelease +features:launches:jacocoTestReportRelease +repository:jacocoTestReportRelease +app:check +core:check +features:detail:check +features:launches:check +repository:check +app:connectedAndroidTest +core:connectedAndroidTest +features:detail:connectedAndroidTest +features:launches:connectedAndroidTest +repository:connectedAndroidTest +app:connectedCheck +core:connectedCheck +features:detail:connectedCheck +features:launches:connectedCheck +repository:connectedCheck +app:connectedDebugAndroidTest +core:connectedDebugAndroidTest +features:detail:connectedDebugAndroidTest +features:launches:connectedDebugAndroidTest +repository:connectedDebugAndroidTest +app:createDebugCoverageReport +core:createDebugCoverageReport +features:detail:createDebugCoverageReport +features:launches:createDebugCoverageReport +repository:createDebugCoverageReport +app:createDevCoverageReport +core:createDevCoverageReport +features:detail:createDevCoverageReport +features:launches:createDevCoverageReport +repository:createDevCoverageReport +detekt +app:detekt +core:detekt +features:detail:detekt +features:launches:detekt +repository:detekt +detektBaseline +app:detektBaseline +core:detektBaseline +features:detail:detektBaseline +features:launches:detektBaseline +repository:detektBaseline +detektGenerateConfig +app:detektGenerateConfig +core:detektGenerateConfig +features:detail:detektGenerateConfig +features:launches:detektGenerateConfig +repository:detektGenerateConfig +detektIdeaFormat +app:detektIdeaFormat +core:detektIdeaFormat +features:detail:detektIdeaFormat +features:launches:detektIdeaFormat +repository:detektIdeaFormat +detektIdeaInspect +app:detektIdeaInspect +core:detektIdeaInspect +features:detail:detektIdeaInspect +features:launches:detektIdeaInspect +repository:detektIdeaInspect +app:deviceAndroidTest +core:deviceAndroidTest +features:detail:deviceAndroidTest +features:launches:deviceAndroidTest +repository:deviceAndroidTest +app:deviceCheck +core:deviceCheck +features:detail:deviceCheck +features:launches:deviceCheck +repository:deviceCheck +app:lint +core:lint +features:detail:lint +features:launches:lint +repository:lint +app:lintDebug +core:lintDebug +features:detail:lintDebug +features:launches:lintDebug +repository:lintDebug +app:lintDev +core:lintDev +features:detail:lintDev +features:launches:lintDev +repository:lintDev +app:lintRelease +core:lintRelease +features:detail:lintRelease +features:launches:lintRelease +repository:lintRelease +app:lintVitalRelease +app:test +core:test +features:detail:test +features:launches:test +repository:test +app:testDebugUnitTest +core:testDebugUnitTest +features:detail:testDebugUnitTest +features:launches:testDebugUnitTest +repository:testDebugUnitTest +app:testDevUnitTest +core:testDevUnitTest +features:detail:testDevUnitTest +features:launches:testDevUnitTest +repository:testDevUnitTest +app:testReleaseUnitTest +core:testReleaseUnitTest +features:detail:testReleaseUnitTest +features:launches:testReleaseUnitTest +repository:testReleaseUnitTest +app:assembleDebug +core:assembleDebug +features:detail:assembleDebug +features:launches:assembleDebug +repository:assembleDebug +app:assembleDebugAndroidTest +core:assembleDebugAndroidTest +features:detail:assembleDebugAndroidTest +features:launches:assembleDebugAndroidTest +repository:assembleDebugAndroidTest +app:assembleDebugUnitTest +core:assembleDebugUnitTest +features:detail:assembleDebugUnitTest +features:launches:assembleDebugUnitTest +repository:assembleDebugUnitTest +app:assembleDev +core:assembleDev +features:detail:assembleDev +features:launches:assembleDev +repository:assembleDev +app:assembleDevUnitTest +core:assembleDevUnitTest +features:detail:assembleDevUnitTest +features:launches:assembleDevUnitTest +repository:assembleDevUnitTest +app:assembleRelease +core:assembleRelease +features:detail:assembleRelease +features:launches:assembleRelease +repository:assembleRelease +app:assembleReleaseUnitTest +core:assembleReleaseUnitTest +features:detail:assembleReleaseUnitTest +features:launches:assembleReleaseUnitTest +repository:assembleReleaseUnitTest +app:buildDebugPreBundle +app:buildDevPreBundle +app:buildReleasePreBundle +app:bundleDebug +core:bundleDebugAar +features:detail:bundleDebugAar +features:launches:bundleDebugAar +repository:bundleDebugAar +app:bundleDebugAndroidTestClasses +app:bundleDebugAndroidTestResources +core:bundleDebugAndroidTestResources +features:detail:bundleDebugAndroidTestResources +features:launches:bundleDebugAndroidTestResources +repository:bundleDebugAndroidTestResources +app:bundleDebugClasses +app:bundleDebugResources +app:bundleDebugUnitTestClasses +app:bundleDev +core:bundleDevAar +features:detail:bundleDevAar +features:launches:bundleDevAar +repository:bundleDevAar +app:bundleDevClasses +app:bundleDevResources +app:bundleDevUnitTestClasses +core:bundleLibCompileDebug +features:detail:bundleLibCompileDebug +features:launches:bundleLibCompileDebug +repository:bundleLibCompileDebug +core:bundleLibCompileDebugAndroidTest +features:detail:bundleLibCompileDebugAndroidTest +features:launches:bundleLibCompileDebugAndroidTest +repository:bundleLibCompileDebugAndroidTest +core:bundleLibCompileDebugUnitTest +features:detail:bundleLibCompileDebugUnitTest +features:launches:bundleLibCompileDebugUnitTest +repository:bundleLibCompileDebugUnitTest +core:bundleLibCompileDev +features:detail:bundleLibCompileDev +features:launches:bundleLibCompileDev +repository:bundleLibCompileDev +core:bundleLibCompileDevUnitTest +features:detail:bundleLibCompileDevUnitTest +features:launches:bundleLibCompileDevUnitTest +repository:bundleLibCompileDevUnitTest +core:bundleLibCompileRelease +features:detail:bundleLibCompileRelease +features:launches:bundleLibCompileRelease +repository:bundleLibCompileRelease +core:bundleLibCompileReleaseUnitTest +features:detail:bundleLibCompileReleaseUnitTest +features:launches:bundleLibCompileReleaseUnitTest +repository:bundleLibCompileReleaseUnitTest +core:bundleLibResDebug +features:detail:bundleLibResDebug +features:launches:bundleLibResDebug +repository:bundleLibResDebug +core:bundleLibResDev +features:detail:bundleLibResDev +features:launches:bundleLibResDev +repository:bundleLibResDev +core:bundleLibResRelease +features:detail:bundleLibResRelease +features:launches:bundleLibResRelease +repository:bundleLibResRelease +core:bundleLibRuntimeDebug +features:detail:bundleLibRuntimeDebug +features:launches:bundleLibRuntimeDebug +repository:bundleLibRuntimeDebug +core:bundleLibRuntimeDev +features:detail:bundleLibRuntimeDev +features:launches:bundleLibRuntimeDev +repository:bundleLibRuntimeDev +core:bundleLibRuntimeRelease +features:detail:bundleLibRuntimeRelease +features:launches:bundleLibRuntimeRelease +repository:bundleLibRuntimeRelease +app:bundleRelease +core:bundleReleaseAar +features:detail:bundleReleaseAar +features:launches:bundleReleaseAar +repository:bundleReleaseAar +app:bundleReleaseClasses +app:bundleReleaseResources +app:bundleReleaseUnitTestClasses +app:checkDebugAndroidTestDuplicateClasses +core:checkDebugAndroidTestDuplicateClasses +features:detail:checkDebugAndroidTestDuplicateClasses +features:launches:checkDebugAndroidTestDuplicateClasses +repository:checkDebugAndroidTestDuplicateClasses +app:checkDebugDuplicateClasses +app:checkDebugManifest +core:checkDebugManifest +features:detail:checkDebugManifest +features:launches:checkDebugManifest +repository:checkDebugManifest +app:checkDevDuplicateClasses +app:checkDevManifest +core:checkDevManifest +features:detail:checkDevManifest +features:launches:checkDevManifest +repository:checkDevManifest +app:checkReleaseManifest +core:checkReleaseManifest +features:detail:checkReleaseManifest +features:launches:checkReleaseManifest +repository:checkReleaseManifest +clean +app:collectDebugDependencies +app:collectDevDependencies +app:collectReleaseDependencies +app:compileDebugAidl +core:compileDebugAidl +features:detail:compileDebugAidl +features:launches:compileDebugAidl +repository:compileDebugAidl +app:compileDebugAndroidTestAidl +core:compileDebugAndroidTestAidl +features:detail:compileDebugAndroidTestAidl +features:launches:compileDebugAndroidTestAidl +repository:compileDebugAndroidTestAidl +app:compileDebugAndroidTestJavaWithJavac +core:compileDebugAndroidTestJavaWithJavac +features:detail:compileDebugAndroidTestJavaWithJavac +features:launches:compileDebugAndroidTestJavaWithJavac +repository:compileDebugAndroidTestJavaWithJavac +app:compileDebugAndroidTestKotlin +core:compileDebugAndroidTestKotlin +features:detail:compileDebugAndroidTestKotlin +features:launches:compileDebugAndroidTestKotlin +repository:compileDebugAndroidTestKotlin +app:compileDebugAndroidTestRenderscript +core:compileDebugAndroidTestRenderscript +features:detail:compileDebugAndroidTestRenderscript +features:launches:compileDebugAndroidTestRenderscript +repository:compileDebugAndroidTestRenderscript +app:compileDebugAndroidTestShaders +core:compileDebugAndroidTestShaders +features:detail:compileDebugAndroidTestShaders +features:launches:compileDebugAndroidTestShaders +repository:compileDebugAndroidTestShaders +app:compileDebugJavaWithJavac +core:compileDebugJavaWithJavac +features:detail:compileDebugJavaWithJavac +features:launches:compileDebugJavaWithJavac +repository:compileDebugJavaWithJavac +app:compileDebugKotlin +core:compileDebugKotlin +features:detail:compileDebugKotlin +features:launches:compileDebugKotlin +repository:compileDebugKotlin +app:compileDebugRenderscript +core:compileDebugRenderscript +features:detail:compileDebugRenderscript +features:launches:compileDebugRenderscript +repository:compileDebugRenderscript +app:compileDebugShaders +core:compileDebugShaders +features:detail:compileDebugShaders +features:launches:compileDebugShaders +repository:compileDebugShaders +app:compileDebugUnitTestJavaWithJavac +core:compileDebugUnitTestJavaWithJavac +features:detail:compileDebugUnitTestJavaWithJavac +features:launches:compileDebugUnitTestJavaWithJavac +repository:compileDebugUnitTestJavaWithJavac +app:compileDebugUnitTestKotlin +core:compileDebugUnitTestKotlin +features:detail:compileDebugUnitTestKotlin +features:launches:compileDebugUnitTestKotlin +repository:compileDebugUnitTestKotlin +app:compileDevAidl +core:compileDevAidl +features:detail:compileDevAidl +features:launches:compileDevAidl +repository:compileDevAidl +app:compileDevJavaWithJavac +core:compileDevJavaWithJavac +features:detail:compileDevJavaWithJavac +features:launches:compileDevJavaWithJavac +repository:compileDevJavaWithJavac +app:compileDevKotlin +core:compileDevKotlin +features:detail:compileDevKotlin +features:launches:compileDevKotlin +repository:compileDevKotlin +app:compileDevRenderscript +core:compileDevRenderscript +features:detail:compileDevRenderscript +features:launches:compileDevRenderscript +repository:compileDevRenderscript +app:compileDevShaders +core:compileDevShaders +features:detail:compileDevShaders +features:launches:compileDevShaders +repository:compileDevShaders +app:compileDevUnitTestJavaWithJavac +core:compileDevUnitTestJavaWithJavac +features:detail:compileDevUnitTestJavaWithJavac +features:launches:compileDevUnitTestJavaWithJavac +repository:compileDevUnitTestJavaWithJavac +app:compileDevUnitTestKotlin +core:compileDevUnitTestKotlin +features:detail:compileDevUnitTestKotlin +features:launches:compileDevUnitTestKotlin +repository:compileDevUnitTestKotlin +app:compileLint +core:compileLint +features:detail:compileLint +features:launches:compileLint +repository:compileLint +app:compileReleaseAidl +core:compileReleaseAidl +features:detail:compileReleaseAidl +features:launches:compileReleaseAidl +repository:compileReleaseAidl +app:compileReleaseJavaWithJavac +core:compileReleaseJavaWithJavac +features:detail:compileReleaseJavaWithJavac +features:launches:compileReleaseJavaWithJavac +repository:compileReleaseJavaWithJavac +app:compileReleaseKotlin +core:compileReleaseKotlin +features:detail:compileReleaseKotlin +features:launches:compileReleaseKotlin +repository:compileReleaseKotlin +app:compileReleaseRenderscript +core:compileReleaseRenderscript +features:detail:compileReleaseRenderscript +features:launches:compileReleaseRenderscript +repository:compileReleaseRenderscript +app:compileReleaseShaders +core:compileReleaseShaders +features:detail:compileReleaseShaders +features:launches:compileReleaseShaders +repository:compileReleaseShaders +app:compileReleaseUnitTestJavaWithJavac +core:compileReleaseUnitTestJavaWithJavac +features:detail:compileReleaseUnitTestJavaWithJavac +features:launches:compileReleaseUnitTestJavaWithJavac +repository:compileReleaseUnitTestJavaWithJavac +app:compileReleaseUnitTestKotlin +core:compileReleaseUnitTestKotlin +features:detail:compileReleaseUnitTestKotlin +features:launches:compileReleaseUnitTestKotlin +repository:compileReleaseUnitTestKotlin +app:configureDebugDependencies +app:configureDevDependencies +app:configureReleaseDependencies +app:consumeConfigAttr +core:consumeConfigAttr +features:detail:consumeConfigAttr +features:launches:consumeConfigAttr +repository:consumeConfigAttr +app:createDebugAndroidTestCoverageReport +core:createDebugAndroidTestCoverageReport +features:detail:createDebugAndroidTestCoverageReport +features:launches:createDebugAndroidTestCoverageReport +repository:createDebugAndroidTestCoverageReport +app:createDebugCompatibleScreenManifests +app:createDevCompatibleScreenManifests +core:createFullJarDebug +features:detail:createFullJarDebug +features:launches:createFullJarDebug +repository:createFullJarDebug +core:createFullJarDev +features:detail:createFullJarDev +features:launches:createFullJarDev +repository:createFullJarDev +core:createFullJarRelease +features:detail:createFullJarRelease +features:launches:createFullJarRelease +repository:createFullJarRelease +app:createMockableJar +core:createMockableJar +features:detail:createMockableJar +features:launches:createMockableJar +repository:createMockableJar +app:createReleaseCompatibleScreenManifests +app:dataBindingExportBuildInfoDebug +core:dataBindingExportBuildInfoDebug +features:detail:dataBindingExportBuildInfoDebug +features:launches:dataBindingExportBuildInfoDebug +core:dataBindingExportBuildInfoDebugAndroidTest +features:detail:dataBindingExportBuildInfoDebugAndroidTest +features:launches:dataBindingExportBuildInfoDebugAndroidTest +app:dataBindingExportBuildInfoDev +core:dataBindingExportBuildInfoDev +features:detail:dataBindingExportBuildInfoDev +features:launches:dataBindingExportBuildInfoDev +app:dataBindingExportBuildInfoRelease +core:dataBindingExportBuildInfoRelease +features:detail:dataBindingExportBuildInfoRelease +features:launches:dataBindingExportBuildInfoRelease +app:dataBindingExportFeaturePackageIdsDebug +app:dataBindingExportFeaturePackageIdsDev +app:dataBindingExportFeaturePackageIdsRelease +app:dataBindingGenBaseClassesDebug +core:dataBindingGenBaseClassesDebug +features:detail:dataBindingGenBaseClassesDebug +features:launches:dataBindingGenBaseClassesDebug +core:dataBindingGenBaseClassesDebugAndroidTest +features:detail:dataBindingGenBaseClassesDebugAndroidTest +features:launches:dataBindingGenBaseClassesDebugAndroidTest +app:dataBindingGenBaseClassesDev +core:dataBindingGenBaseClassesDev +features:detail:dataBindingGenBaseClassesDev +features:launches:dataBindingGenBaseClassesDev +app:dataBindingGenBaseClassesRelease +core:dataBindingGenBaseClassesRelease +features:detail:dataBindingGenBaseClassesRelease +features:launches:dataBindingGenBaseClassesRelease +app:dataBindingMergeDependencyArtifactsDebug +core:dataBindingMergeDependencyArtifactsDebug +features:detail:dataBindingMergeDependencyArtifactsDebug +features:launches:dataBindingMergeDependencyArtifactsDebug +core:dataBindingMergeDependencyArtifactsDebugAndroidTest +features:detail:dataBindingMergeDependencyArtifactsDebugAndroidTest +features:launches:dataBindingMergeDependencyArtifactsDebugAndroidTest +app:dataBindingMergeDependencyArtifactsDev +core:dataBindingMergeDependencyArtifactsDev +features:detail:dataBindingMergeDependencyArtifactsDev +features:launches:dataBindingMergeDependencyArtifactsDev +app:dataBindingMergeDependencyArtifactsRelease +core:dataBindingMergeDependencyArtifactsRelease +features:detail:dataBindingMergeDependencyArtifactsRelease +features:launches:dataBindingMergeDependencyArtifactsRelease +app:dataBindingMergeGenClassesDebug +core:dataBindingMergeGenClassesDebug +features:detail:dataBindingMergeGenClassesDebug +features:launches:dataBindingMergeGenClassesDebug +core:dataBindingMergeGenClassesDebugAndroidTest +features:detail:dataBindingMergeGenClassesDebugAndroidTest +features:launches:dataBindingMergeGenClassesDebugAndroidTest +app:dataBindingMergeGenClassesDev +core:dataBindingMergeGenClassesDev +features:detail:dataBindingMergeGenClassesDev +features:launches:dataBindingMergeGenClassesDev +app:dataBindingMergeGenClassesRelease +core:dataBindingMergeGenClassesRelease +features:detail:dataBindingMergeGenClassesRelease +features:launches:dataBindingMergeGenClassesRelease +app:desugarDebugAndroidTestFileDependencies +core:desugarDebugAndroidTestFileDependencies +features:detail:desugarDebugAndroidTestFileDependencies +features:launches:desugarDebugAndroidTestFileDependencies +repository:desugarDebugAndroidTestFileDependencies +app:desugarDebugFileDependencies +app:desugarDevFileDependencies +core:dexDebug +features:detail:dexDebug +features:launches:dexDebug +repository:dexDebug +core:dexDev +features:detail:dexDev +features:launches:dexDev +repository:dexDev +core:dexRelease +features:detail:dexRelease +features:launches:dexRelease +repository:dexRelease +app:dummydebugUnitTest +core:dummydebugUnitTest +features:detail:dummydebugUnitTest +features:launches:dummydebugUnitTest +repository:dummydebugUnitTest +app:dummydevUnitTest +core:dummydevUnitTest +features:detail:dummydevUnitTest +features:launches:dummydevUnitTest +repository:dummydevUnitTest +app:dummyreleaseUnitTest +core:dummyreleaseUnitTest +features:detail:dummyreleaseUnitTest +features:launches:dummyreleaseUnitTest +repository:dummyreleaseUnitTest +app:extractApksForDebug +app:extractApksForDev +app:extractApksForRelease +app:extractProguardFiles +core:extractProguardFiles +features:detail:extractProguardFiles +features:launches:extractProguardFiles +repository:extractProguardFiles +app:generateDebugAndroidTestAssets +core:generateDebugAndroidTestAssets +features:detail:generateDebugAndroidTestAssets +features:launches:generateDebugAndroidTestAssets +repository:generateDebugAndroidTestAssets +app:generateDebugAndroidTestBuildConfig +core:generateDebugAndroidTestBuildConfig +features:detail:generateDebugAndroidTestBuildConfig +features:launches:generateDebugAndroidTestBuildConfig +repository:generateDebugAndroidTestBuildConfig +app:generateDebugAndroidTestResources +core:generateDebugAndroidTestResources +features:detail:generateDebugAndroidTestResources +features:launches:generateDebugAndroidTestResources +repository:generateDebugAndroidTestResources +app:generateDebugAndroidTestResValues +core:generateDebugAndroidTestResValues +features:detail:generateDebugAndroidTestResValues +features:launches:generateDebugAndroidTestResValues +repository:generateDebugAndroidTestResValues +app:generateDebugAndroidTestSources +core:generateDebugAndroidTestSources +features:detail:generateDebugAndroidTestSources +features:launches:generateDebugAndroidTestSources +repository:generateDebugAndroidTestSources +app:generateDebugAssets +core:generateDebugAssets +features:detail:generateDebugAssets +features:launches:generateDebugAssets +repository:generateDebugAssets +app:generateDebugBuildConfig +core:generateDebugBuildConfig +features:detail:generateDebugBuildConfig +features:launches:generateDebugBuildConfig +repository:generateDebugBuildConfig +app:generateDebugFeatureMetadata +app:generateDebugFeatureTransitiveDeps +app:generateDebugResources +core:generateDebugResources +features:detail:generateDebugResources +features:launches:generateDebugResources +repository:generateDebugResources +app:generateDebugResValues +core:generateDebugResValues +features:detail:generateDebugResValues +features:launches:generateDebugResValues +repository:generateDebugResValues +core:generateDebugRFile +features:detail:generateDebugRFile +features:launches:generateDebugRFile +repository:generateDebugRFile +app:generateDebugSources +core:generateDebugSources +features:detail:generateDebugSources +features:launches:generateDebugSources +repository:generateDebugSources +app:generateDebugUnitTestAssets +core:generateDebugUnitTestAssets +features:detail:generateDebugUnitTestAssets +features:launches:generateDebugUnitTestAssets +repository:generateDebugUnitTestAssets +app:generateDebugUnitTestResources +core:generateDebugUnitTestResources +features:detail:generateDebugUnitTestResources +features:launches:generateDebugUnitTestResources +repository:generateDebugUnitTestResources +app:generateDebugUnitTestSources +core:generateDebugUnitTestSources +features:detail:generateDebugUnitTestSources +features:launches:generateDebugUnitTestSources +repository:generateDebugUnitTestSources +app:generateDevAssets +core:generateDevAssets +features:detail:generateDevAssets +features:launches:generateDevAssets +repository:generateDevAssets +app:generateDevBuildConfig +core:generateDevBuildConfig +features:detail:generateDevBuildConfig +features:launches:generateDevBuildConfig +repository:generateDevBuildConfig +app:generateDevFeatureMetadata +app:generateDevFeatureTransitiveDeps +app:generateDevResources +core:generateDevResources +features:detail:generateDevResources +features:launches:generateDevResources +repository:generateDevResources +app:generateDevResValues +core:generateDevResValues +features:detail:generateDevResValues +features:launches:generateDevResValues +repository:generateDevResValues +core:generateDevRFile +features:detail:generateDevRFile +features:launches:generateDevRFile +repository:generateDevRFile +app:generateDevSources +core:generateDevSources +features:detail:generateDevSources +features:launches:generateDevSources +repository:generateDevSources +app:generateDevUnitTestAssets +core:generateDevUnitTestAssets +features:detail:generateDevUnitTestAssets +features:launches:generateDevUnitTestAssets +repository:generateDevUnitTestAssets +app:generateDevUnitTestResources +core:generateDevUnitTestResources +features:detail:generateDevUnitTestResources +features:launches:generateDevUnitTestResources +repository:generateDevUnitTestResources +app:generateDevUnitTestSources +core:generateDevUnitTestSources +features:detail:generateDevUnitTestSources +features:launches:generateDevUnitTestSources +repository:generateDevUnitTestSources +app:generateReleaseAssets +core:generateReleaseAssets +features:detail:generateReleaseAssets +features:launches:generateReleaseAssets +repository:generateReleaseAssets +app:generateReleaseBuildConfig +core:generateReleaseBuildConfig +features:detail:generateReleaseBuildConfig +features:launches:generateReleaseBuildConfig +repository:generateReleaseBuildConfig +app:generateReleaseFeatureMetadata +app:generateReleaseFeatureTransitiveDeps +core:generateReleaseLibraryProguardRules +features:detail:generateReleaseLibraryProguardRules +features:launches:generateReleaseLibraryProguardRules +repository:generateReleaseLibraryProguardRules +app:generateReleaseResources +core:generateReleaseResources +features:detail:generateReleaseResources +features:launches:generateReleaseResources +repository:generateReleaseResources +app:generateReleaseResValues +core:generateReleaseResValues +features:detail:generateReleaseResValues +features:launches:generateReleaseResValues +repository:generateReleaseResValues +core:generateReleaseRFile +features:detail:generateReleaseRFile +features:launches:generateReleaseRFile +repository:generateReleaseRFile +app:generateReleaseSources +core:generateReleaseSources +features:detail:generateReleaseSources +features:launches:generateReleaseSources +repository:generateReleaseSources +app:generateReleaseUnitTestAssets +core:generateReleaseUnitTestAssets +features:detail:generateReleaseUnitTestAssets +features:launches:generateReleaseUnitTestAssets +repository:generateReleaseUnitTestAssets +app:generateReleaseUnitTestResources +core:generateReleaseUnitTestResources +features:detail:generateReleaseUnitTestResources +features:launches:generateReleaseUnitTestResources +repository:generateReleaseUnitTestResources +app:generateReleaseUnitTestSources +core:generateReleaseUnitTestSources +features:detail:generateReleaseUnitTestSources +features:launches:generateReleaseUnitTestSources +repository:generateReleaseUnitTestSources +features:detail:generateSafeArgsDebug +features:detail:generateSafeArgsDev +features:detail:generateSafeArgsRelease +app:jacocoDebug +core:jacocoDebug +features:detail:jacocoDebug +features:launches:jacocoDebug +repository:jacocoDebug +app:jacocoDev +core:jacocoDev +features:detail:jacocoDev +features:launches:jacocoDev +repository:jacocoDev +app:javaPreCompileDebug +core:javaPreCompileDebug +features:detail:javaPreCompileDebug +features:launches:javaPreCompileDebug +repository:javaPreCompileDebug +app:javaPreCompileDebugAndroidTest +core:javaPreCompileDebugAndroidTest +features:detail:javaPreCompileDebugAndroidTest +features:launches:javaPreCompileDebugAndroidTest +repository:javaPreCompileDebugAndroidTest +app:javaPreCompileDebugUnitTest +core:javaPreCompileDebugUnitTest +features:detail:javaPreCompileDebugUnitTest +features:launches:javaPreCompileDebugUnitTest +repository:javaPreCompileDebugUnitTest +app:javaPreCompileDev +core:javaPreCompileDev +features:detail:javaPreCompileDev +features:launches:javaPreCompileDev +repository:javaPreCompileDev +app:javaPreCompileDevUnitTest +core:javaPreCompileDevUnitTest +features:detail:javaPreCompileDevUnitTest +features:launches:javaPreCompileDevUnitTest +repository:javaPreCompileDevUnitTest +app:javaPreCompileRelease +core:javaPreCompileRelease +features:detail:javaPreCompileRelease +features:launches:javaPreCompileRelease +repository:javaPreCompileRelease +app:javaPreCompileReleaseUnitTest +core:javaPreCompileReleaseUnitTest +features:detail:javaPreCompileReleaseUnitTest +features:launches:javaPreCompileReleaseUnitTest +repository:javaPreCompileReleaseUnitTest +app:kaptDebugAndroidTestKotlin +core:kaptDebugAndroidTestKotlin +features:detail:kaptDebugAndroidTestKotlin +features:launches:kaptDebugAndroidTestKotlin +repository:kaptDebugAndroidTestKotlin +app:kaptDebugKotlin +core:kaptDebugKotlin +features:detail:kaptDebugKotlin +features:launches:kaptDebugKotlin +repository:kaptDebugKotlin +app:kaptDebugUnitTestKotlin +core:kaptDebugUnitTestKotlin +features:detail:kaptDebugUnitTestKotlin +features:launches:kaptDebugUnitTestKotlin +repository:kaptDebugUnitTestKotlin +app:kaptDevKotlin +core:kaptDevKotlin +features:detail:kaptDevKotlin +features:launches:kaptDevKotlin +repository:kaptDevKotlin +app:kaptDevUnitTestKotlin +core:kaptDevUnitTestKotlin +features:detail:kaptDevUnitTestKotlin +features:launches:kaptDevUnitTestKotlin +repository:kaptDevUnitTestKotlin +app:kaptGenerateStubsDebugAndroidTestKotlin +core:kaptGenerateStubsDebugAndroidTestKotlin +features:detail:kaptGenerateStubsDebugAndroidTestKotlin +features:launches:kaptGenerateStubsDebugAndroidTestKotlin +repository:kaptGenerateStubsDebugAndroidTestKotlin +app:kaptGenerateStubsDebugKotlin +core:kaptGenerateStubsDebugKotlin +features:detail:kaptGenerateStubsDebugKotlin +features:launches:kaptGenerateStubsDebugKotlin +repository:kaptGenerateStubsDebugKotlin +app:kaptGenerateStubsDebugUnitTestKotlin +core:kaptGenerateStubsDebugUnitTestKotlin +features:detail:kaptGenerateStubsDebugUnitTestKotlin +features:launches:kaptGenerateStubsDebugUnitTestKotlin +repository:kaptGenerateStubsDebugUnitTestKotlin +app:kaptGenerateStubsDevKotlin +core:kaptGenerateStubsDevKotlin +features:detail:kaptGenerateStubsDevKotlin +features:launches:kaptGenerateStubsDevKotlin +repository:kaptGenerateStubsDevKotlin +app:kaptGenerateStubsDevUnitTestKotlin +core:kaptGenerateStubsDevUnitTestKotlin +features:detail:kaptGenerateStubsDevUnitTestKotlin +features:launches:kaptGenerateStubsDevUnitTestKotlin +repository:kaptGenerateStubsDevUnitTestKotlin +app:kaptGenerateStubsReleaseKotlin +core:kaptGenerateStubsReleaseKotlin +features:detail:kaptGenerateStubsReleaseKotlin +features:launches:kaptGenerateStubsReleaseKotlin +repository:kaptGenerateStubsReleaseKotlin +app:kaptGenerateStubsReleaseUnitTestKotlin +core:kaptGenerateStubsReleaseUnitTestKotlin +features:detail:kaptGenerateStubsReleaseUnitTestKotlin +features:launches:kaptGenerateStubsReleaseUnitTestKotlin +repository:kaptGenerateStubsReleaseUnitTestKotlin +app:kaptReleaseKotlin +core:kaptReleaseKotlin +features:detail:kaptReleaseKotlin +features:launches:kaptReleaseKotlin +repository:kaptReleaseKotlin +app:kaptReleaseUnitTestKotlin +core:kaptReleaseUnitTestKotlin +features:detail:kaptReleaseUnitTestKotlin +features:launches:kaptReleaseUnitTestKotlin +repository:kaptReleaseUnitTestKotlin +app:mainApkListPersistenceDebug +app:mainApkListPersistenceDebugAndroidTest +core:mainApkListPersistenceDebugAndroidTest +features:detail:mainApkListPersistenceDebugAndroidTest +features:launches:mainApkListPersistenceDebugAndroidTest +repository:mainApkListPersistenceDebugAndroidTest +app:mainApkListPersistenceDev +app:mainApkListPersistenceRelease +app:makeApkFromBundleForDebug +app:makeApkFromBundleForDev +app:makeApkFromBundleForRelease +app:mergeDebugAndroidTestAssets +core:mergeDebugAndroidTestAssets +features:detail:mergeDebugAndroidTestAssets +features:launches:mergeDebugAndroidTestAssets +repository:mergeDebugAndroidTestAssets +app:mergeDebugAndroidTestGeneratedProguardFiles +core:mergeDebugAndroidTestGeneratedProguardFiles +features:detail:mergeDebugAndroidTestGeneratedProguardFiles +features:launches:mergeDebugAndroidTestGeneratedProguardFiles +repository:mergeDebugAndroidTestGeneratedProguardFiles +app:mergeDebugAndroidTestJavaResource +core:mergeDebugAndroidTestJavaResource +features:detail:mergeDebugAndroidTestJavaResource +features:launches:mergeDebugAndroidTestJavaResource +repository:mergeDebugAndroidTestJavaResource +app:mergeDebugAndroidTestJniLibFolders +core:mergeDebugAndroidTestJniLibFolders +features:detail:mergeDebugAndroidTestJniLibFolders +features:launches:mergeDebugAndroidTestJniLibFolders +repository:mergeDebugAndroidTestJniLibFolders +app:mergeDebugAndroidTestNativeLibs +core:mergeDebugAndroidTestNativeLibs +features:detail:mergeDebugAndroidTestNativeLibs +features:launches:mergeDebugAndroidTestNativeLibs +repository:mergeDebugAndroidTestNativeLibs +app:mergeDebugAndroidTestResources +core:mergeDebugAndroidTestResources +features:detail:mergeDebugAndroidTestResources +features:launches:mergeDebugAndroidTestResources +repository:mergeDebugAndroidTestResources +app:mergeDebugAndroidTestShaders +core:mergeDebugAndroidTestShaders +features:detail:mergeDebugAndroidTestShaders +features:launches:mergeDebugAndroidTestShaders +repository:mergeDebugAndroidTestShaders +app:mergeDebugAssets +core:mergeDebugAssets +features:detail:mergeDebugAssets +features:launches:mergeDebugAssets +repository:mergeDebugAssets +core:mergeDebugConsumerProguardFiles +features:detail:mergeDebugConsumerProguardFiles +features:launches:mergeDebugConsumerProguardFiles +repository:mergeDebugConsumerProguardFiles +app:mergeDebugGeneratedProguardFiles +core:mergeDebugGeneratedProguardFiles +features:detail:mergeDebugGeneratedProguardFiles +features:launches:mergeDebugGeneratedProguardFiles +repository:mergeDebugGeneratedProguardFiles +app:mergeDebugJavaResource +core:mergeDebugJavaResource +features:detail:mergeDebugJavaResource +features:launches:mergeDebugJavaResource +repository:mergeDebugJavaResource +app:mergeDebugJniLibFolders +core:mergeDebugJniLibFolders +features:detail:mergeDebugJniLibFolders +features:launches:mergeDebugJniLibFolders +repository:mergeDebugJniLibFolders +app:mergeDebugNativeLibs +core:mergeDebugNativeLibs +features:detail:mergeDebugNativeLibs +features:launches:mergeDebugNativeLibs +repository:mergeDebugNativeLibs +app:mergeDebugResources +core:mergeDebugResources +features:detail:mergeDebugResources +features:launches:mergeDebugResources +repository:mergeDebugResources +app:mergeDebugShaders +core:mergeDebugShaders +features:detail:mergeDebugShaders +features:launches:mergeDebugShaders +repository:mergeDebugShaders +app:mergeDevAssets +core:mergeDevAssets +features:detail:mergeDevAssets +features:launches:mergeDevAssets +repository:mergeDevAssets +core:mergeDevConsumerProguardFiles +features:detail:mergeDevConsumerProguardFiles +features:launches:mergeDevConsumerProguardFiles +repository:mergeDevConsumerProguardFiles +app:mergeDevGeneratedProguardFiles +core:mergeDevGeneratedProguardFiles +features:detail:mergeDevGeneratedProguardFiles +features:launches:mergeDevGeneratedProguardFiles +repository:mergeDevGeneratedProguardFiles +app:mergeDevJavaResource +core:mergeDevJavaResource +features:detail:mergeDevJavaResource +features:launches:mergeDevJavaResource +repository:mergeDevJavaResource +app:mergeDevJniLibFolders +core:mergeDevJniLibFolders +features:detail:mergeDevJniLibFolders +features:launches:mergeDevJniLibFolders +repository:mergeDevJniLibFolders +app:mergeDevNativeLibs +core:mergeDevNativeLibs +features:detail:mergeDevNativeLibs +features:launches:mergeDevNativeLibs +repository:mergeDevNativeLibs +app:mergeDevResources +core:mergeDevResources +features:detail:mergeDevResources +features:launches:mergeDevResources +repository:mergeDevResources +app:mergeDevShaders +core:mergeDevShaders +features:detail:mergeDevShaders +features:launches:mergeDevShaders +repository:mergeDevShaders +app:mergeExtDexDebug +app:mergeExtDexDebugAndroidTest +core:mergeExtDexDebugAndroidTest +features:detail:mergeExtDexDebugAndroidTest +features:launches:mergeExtDexDebugAndroidTest +repository:mergeExtDexDebugAndroidTest +app:mergeExtDexDev +app:mergeLibDexDebug +app:mergeLibDexDebugAndroidTest +core:mergeLibDexDebugAndroidTest +features:detail:mergeLibDexDebugAndroidTest +features:launches:mergeLibDexDebugAndroidTest +repository:mergeLibDexDebugAndroidTest +app:mergeLibDexDev +app:mergeProjectDexDebug +app:mergeProjectDexDebugAndroidTest +core:mergeProjectDexDebugAndroidTest +features:detail:mergeProjectDexDebugAndroidTest +features:launches:mergeProjectDexDebugAndroidTest +repository:mergeProjectDexDebugAndroidTest +app:mergeProjectDexDev +app:mergeReleaseAssets +core:mergeReleaseAssets +features:detail:mergeReleaseAssets +features:launches:mergeReleaseAssets +repository:mergeReleaseAssets +core:mergeReleaseConsumerProguardFiles +features:detail:mergeReleaseConsumerProguardFiles +features:launches:mergeReleaseConsumerProguardFiles +repository:mergeReleaseConsumerProguardFiles +app:mergeReleaseGeneratedProguardFiles +core:mergeReleaseGeneratedProguardFiles +features:detail:mergeReleaseGeneratedProguardFiles +features:launches:mergeReleaseGeneratedProguardFiles +repository:mergeReleaseGeneratedProguardFiles +app:mergeReleaseJavaResource +core:mergeReleaseJavaResource +features:detail:mergeReleaseJavaResource +features:launches:mergeReleaseJavaResource +repository:mergeReleaseJavaResource +app:mergeReleaseJniLibFolders +core:mergeReleaseJniLibFolders +features:detail:mergeReleaseJniLibFolders +features:launches:mergeReleaseJniLibFolders +repository:mergeReleaseJniLibFolders +app:mergeReleaseNativeLibs +core:mergeReleaseNativeLibs +features:detail:mergeReleaseNativeLibs +features:launches:mergeReleaseNativeLibs +repository:mergeReleaseNativeLibs +app:mergeReleaseResources +core:mergeReleaseResources +features:detail:mergeReleaseResources +features:launches:mergeReleaseResources +repository:mergeReleaseResources +app:mergeReleaseShaders +core:mergeReleaseShaders +features:detail:mergeReleaseShaders +features:launches:mergeReleaseShaders +repository:mergeReleaseShaders +app:packageDebug +app:packageDebugAndroidTest +core:packageDebugAndroidTest +features:detail:packageDebugAndroidTest +features:launches:packageDebugAndroidTest +repository:packageDebugAndroidTest +core:packageDebugAssets +features:detail:packageDebugAssets +features:launches:packageDebugAssets +repository:packageDebugAssets +app:packageDebugBundle +core:packageDebugRenderscript +features:detail:packageDebugRenderscript +features:launches:packageDebugRenderscript +repository:packageDebugRenderscript +core:packageDebugResources +features:detail:packageDebugResources +features:launches:packageDebugResources +repository:packageDebugResources +app:packageDebugUniversalApk +app:packageDev +core:packageDevAssets +features:detail:packageDevAssets +features:launches:packageDevAssets +repository:packageDevAssets +app:packageDevBundle +core:packageDevRenderscript +features:detail:packageDevRenderscript +features:launches:packageDevRenderscript +repository:packageDevRenderscript +core:packageDevResources +features:detail:packageDevResources +features:launches:packageDevResources +repository:packageDevResources +app:packageDevUniversalApk +app:packageRelease +core:packageReleaseAssets +features:detail:packageReleaseAssets +features:launches:packageReleaseAssets +repository:packageReleaseAssets +app:packageReleaseBundle +core:packageReleaseRenderscript +features:detail:packageReleaseRenderscript +features:launches:packageReleaseRenderscript +repository:packageReleaseRenderscript +core:packageReleaseResources +features:detail:packageReleaseResources +features:launches:packageReleaseResources +repository:packageReleaseResources +app:packageReleaseUniversalApk +core:parseDebugLibraryResources +features:detail:parseDebugLibraryResources +features:launches:parseDebugLibraryResources +repository:parseDebugLibraryResources +core:parseDevLibraryResources +features:detail:parseDevLibraryResources +features:launches:parseDevLibraryResources +repository:parseDevLibraryResources +core:parseReleaseLibraryResources +features:detail:parseReleaseLibraryResources +features:launches:parseReleaseLibraryResources +repository:parseReleaseLibraryResources +app:preBuild +core:preBuild +features:detail:preBuild +features:launches:preBuild +repository:preBuild +app:preDebugAndroidTestBuild +core:preDebugAndroidTestBuild +features:detail:preDebugAndroidTestBuild +features:launches:preDebugAndroidTestBuild +repository:preDebugAndroidTestBuild +app:preDebugBuild +core:preDebugBuild +features:detail:preDebugBuild +features:launches:preDebugBuild +repository:preDebugBuild +app:preDebugUnitTestBuild +core:preDebugUnitTestBuild +features:detail:preDebugUnitTestBuild +features:launches:preDebugUnitTestBuild +repository:preDebugUnitTestBuild +app:preDevBuild +core:preDevBuild +features:detail:preDevBuild +features:launches:preDevBuild +repository:preDevBuild +app:preDevUnitTestBuild +core:preDevUnitTestBuild +features:detail:preDevUnitTestBuild +features:launches:preDevUnitTestBuild +repository:preDevUnitTestBuild +prepareKotlinBuildScriptModel +app:prepareKotlinBuildScriptModel +core:prepareKotlinBuildScriptModel +features:prepareKotlinBuildScriptModel +features:detail:prepareKotlinBuildScriptModel +features:launches:prepareKotlinBuildScriptModel +repository:prepareKotlinBuildScriptModel +app:prepareLintJar +core:prepareLintJar +features:detail:prepareLintJar +features:launches:prepareLintJar +repository:prepareLintJar +app:prepareLintJarForPublish +core:prepareLintJarForPublish +features:detail:prepareLintJarForPublish +features:launches:prepareLintJarForPublish +repository:prepareLintJarForPublish +app:preReleaseBuild +core:preReleaseBuild +features:detail:preReleaseBuild +features:launches:preReleaseBuild +repository:preReleaseBuild +app:preReleaseUnitTestBuild +core:preReleaseUnitTestBuild +features:detail:preReleaseUnitTestBuild +features:launches:preReleaseUnitTestBuild +repository:preReleaseUnitTestBuild +app:processDebugAndroidTestJavaRes +core:processDebugAndroidTestJavaRes +features:detail:processDebugAndroidTestJavaRes +features:launches:processDebugAndroidTestJavaRes +repository:processDebugAndroidTestJavaRes +app:processDebugAndroidTestManifest +core:processDebugAndroidTestManifest +features:detail:processDebugAndroidTestManifest +features:launches:processDebugAndroidTestManifest +repository:processDebugAndroidTestManifest +app:processDebugAndroidTestResources +core:processDebugAndroidTestResources +features:detail:processDebugAndroidTestResources +features:launches:processDebugAndroidTestResources +repository:processDebugAndroidTestResources +app:processDebugJavaRes +core:processDebugJavaRes +features:detail:processDebugJavaRes +features:launches:processDebugJavaRes +repository:processDebugJavaRes +app:processDebugManifest +core:processDebugManifest +features:detail:processDebugManifest +features:launches:processDebugManifest +repository:processDebugManifest +app:processDebugResources +app:processDebugUnitTestJavaRes +core:processDebugUnitTestJavaRes +features:detail:processDebugUnitTestJavaRes +features:launches:processDebugUnitTestJavaRes +repository:processDebugUnitTestJavaRes +app:processDevJavaRes +core:processDevJavaRes +features:detail:processDevJavaRes +features:launches:processDevJavaRes +repository:processDevJavaRes +app:processDevManifest +core:processDevManifest +features:detail:processDevManifest +features:launches:processDevManifest +repository:processDevManifest +app:processDevResources +app:processDevUnitTestJavaRes +core:processDevUnitTestJavaRes +features:detail:processDevUnitTestJavaRes +features:launches:processDevUnitTestJavaRes +repository:processDevUnitTestJavaRes +app:processReleaseJavaRes +core:processReleaseJavaRes +features:detail:processReleaseJavaRes +features:launches:processReleaseJavaRes +repository:processReleaseJavaRes +app:processReleaseManifest +core:processReleaseManifest +features:detail:processReleaseManifest +features:launches:processReleaseManifest +repository:processReleaseManifest +app:processReleaseResources +app:processReleaseUnitTestJavaRes +core:processReleaseUnitTestJavaRes +features:detail:processReleaseUnitTestJavaRes +features:launches:processReleaseUnitTestJavaRes +repository:processReleaseUnitTestJavaRes +projectDependencyGraph +removeReports +app:reportBuildArtifactsDebug +core:reportBuildArtifactsDebug +features:detail:reportBuildArtifactsDebug +features:launches:reportBuildArtifactsDebug +repository:reportBuildArtifactsDebug +app:reportBuildArtifactsDev +core:reportBuildArtifactsDev +features:detail:reportBuildArtifactsDev +features:launches:reportBuildArtifactsDev +repository:reportBuildArtifactsDev +app:reportBuildArtifactsRelease +core:reportBuildArtifactsRelease +features:detail:reportBuildArtifactsRelease +features:launches:reportBuildArtifactsRelease +repository:reportBuildArtifactsRelease +app:reportSourceSetTransformAndroidTest +core:reportSourceSetTransformAndroidTest +features:detail:reportSourceSetTransformAndroidTest +features:launches:reportSourceSetTransformAndroidTest +repository:reportSourceSetTransformAndroidTest +app:reportSourceSetTransformAndroidTestDebug +core:reportSourceSetTransformAndroidTestDebug +features:detail:reportSourceSetTransformAndroidTestDebug +features:launches:reportSourceSetTransformAndroidTestDebug +repository:reportSourceSetTransformAndroidTestDebug +app:reportSourceSetTransformDebug +core:reportSourceSetTransformDebug +features:detail:reportSourceSetTransformDebug +features:launches:reportSourceSetTransformDebug +repository:reportSourceSetTransformDebug +app:reportSourceSetTransformDev +core:reportSourceSetTransformDev +features:detail:reportSourceSetTransformDev +features:launches:reportSourceSetTransformDev +repository:reportSourceSetTransformDev +app:reportSourceSetTransformMain +core:reportSourceSetTransformMain +features:detail:reportSourceSetTransformMain +features:launches:reportSourceSetTransformMain +repository:reportSourceSetTransformMain +app:reportSourceSetTransformRelease +core:reportSourceSetTransformRelease +features:detail:reportSourceSetTransformRelease +features:launches:reportSourceSetTransformRelease +repository:reportSourceSetTransformRelease +app:reportSourceSetTransformTest +core:reportSourceSetTransformTest +features:detail:reportSourceSetTransformTest +features:launches:reportSourceSetTransformTest +repository:reportSourceSetTransformTest +app:reportSourceSetTransformTestDebug +core:reportSourceSetTransformTestDebug +features:detail:reportSourceSetTransformTestDebug +features:launches:reportSourceSetTransformTestDebug +repository:reportSourceSetTransformTestDebug +app:reportSourceSetTransformTestDev +core:reportSourceSetTransformTestDev +features:detail:reportSourceSetTransformTestDev +features:launches:reportSourceSetTransformTestDev +repository:reportSourceSetTransformTestDev +app:reportSourceSetTransformTestRelease +core:reportSourceSetTransformTestRelease +features:detail:reportSourceSetTransformTestRelease +features:launches:reportSourceSetTransformTestRelease +repository:reportSourceSetTransformTestRelease +app:resolveConfigAttr +core:resolveConfigAttr +features:detail:resolveConfigAttr +features:launches:resolveConfigAttr +repository:resolveConfigAttr +app:signDebugBundle +app:signDevBundle +app:signingConfigWriterDebug +app:signingConfigWriterDebugAndroidTest +core:signingConfigWriterDebugAndroidTest +features:detail:signingConfigWriterDebugAndroidTest +features:launches:signingConfigWriterDebugAndroidTest +repository:signingConfigWriterDebugAndroidTest +app:signingConfigWriterDev +app:signingConfigWriterRelease +app:signReleaseBundle +app:stripDebugDebugSymbols +core:stripDebugDebugSymbols +features:detail:stripDebugDebugSymbols +features:launches:stripDebugDebugSymbols +repository:stripDebugDebugSymbols +app:stripDevDebugSymbols +core:stripDevDebugSymbols +features:detail:stripDevDebugSymbols +features:launches:stripDevDebugSymbols +repository:stripDevDebugSymbols +app:stripReleaseDebugSymbols +core:stripReleaseDebugSymbols +features:detail:stripReleaseDebugSymbols +features:launches:stripReleaseDebugSymbols +repository:stripReleaseDebugSymbols +app:transformClassesAndResourcesWithR8ForRelease +core:transformClassesAndResourcesWithR8ForRelease +features:detail:transformClassesAndResourcesWithR8ForRelease +features:launches:transformClassesAndResourcesWithR8ForRelease +repository:transformClassesAndResourcesWithR8ForRelease +core:transformClassesAndResourcesWithSyncLibJarsForDebug +features:detail:transformClassesAndResourcesWithSyncLibJarsForDebug +features:launches:transformClassesAndResourcesWithSyncLibJarsForDebug +repository:transformClassesAndResourcesWithSyncLibJarsForDebug +core:transformClassesAndResourcesWithSyncLibJarsForDev +features:detail:transformClassesAndResourcesWithSyncLibJarsForDev +features:launches:transformClassesAndResourcesWithSyncLibJarsForDev +repository:transformClassesAndResourcesWithSyncLibJarsForDev +core:transformClassesAndResourcesWithSyncLibJarsForRelease +features:detail:transformClassesAndResourcesWithSyncLibJarsForRelease +features:launches:transformClassesAndResourcesWithSyncLibJarsForRelease +repository:transformClassesAndResourcesWithSyncLibJarsForRelease +app:transformClassesWithDexBuilderForDebug +app:transformClassesWithDexBuilderForDebugAndroidTest +core:transformClassesWithDexBuilderForDebugAndroidTest +features:detail:transformClassesWithDexBuilderForDebugAndroidTest +features:launches:transformClassesWithDexBuilderForDebugAndroidTest +repository:transformClassesWithDexBuilderForDebugAndroidTest +app:transformClassesWithDexBuilderForDev +core:transformNativeLibsWithIntermediateJniLibsForDebug +features:detail:transformNativeLibsWithIntermediateJniLibsForDebug +features:launches:transformNativeLibsWithIntermediateJniLibsForDebug +repository:transformNativeLibsWithIntermediateJniLibsForDebug +core:transformNativeLibsWithIntermediateJniLibsForDev +features:detail:transformNativeLibsWithIntermediateJniLibsForDev +features:launches:transformNativeLibsWithIntermediateJniLibsForDev +repository:transformNativeLibsWithIntermediateJniLibsForDev +core:transformNativeLibsWithIntermediateJniLibsForRelease +features:detail:transformNativeLibsWithIntermediateJniLibsForRelease +features:launches:transformNativeLibsWithIntermediateJniLibsForRelease +repository:transformNativeLibsWithIntermediateJniLibsForRelease +core:transformNativeLibsWithSyncJniLibsForDebug +features:detail:transformNativeLibsWithSyncJniLibsForDebug +features:launches:transformNativeLibsWithSyncJniLibsForDebug +repository:transformNativeLibsWithSyncJniLibsForDebug +core:transformNativeLibsWithSyncJniLibsForDev +features:detail:transformNativeLibsWithSyncJniLibsForDev +features:launches:transformNativeLibsWithSyncJniLibsForDev +repository:transformNativeLibsWithSyncJniLibsForDev +core:transformNativeLibsWithSyncJniLibsForRelease +features:detail:transformNativeLibsWithSyncJniLibsForRelease +features:launches:transformNativeLibsWithSyncJniLibsForRelease +repository:transformNativeLibsWithSyncJniLibsForRelease +app:validateSigningDebug +app:validateSigningDebugAndroidTest +core:validateSigningDebugAndroidTest +features:detail:validateSigningDebugAndroidTest +features:launches:validateSigningDebugAndroidTest +repository:validateSigningDebugAndroidTest +app:validateSigningDev +core:verifyReleaseResources +features:detail:verifyReleaseResources +features:launches:verifyReleaseResources +repository:verifyReleaseResources +app:writeDebugApplicationId +app:writeDebugModuleMetadata +app:writeDevApplicationId +app:writeDevModuleMetadata +app:writeReleaseApplicationId +app:writeReleaseModuleMetadata +androidDependencies +androidDependencies +androidDependencies +androidDependencies +androidDependencies +signingReport +signingReport +signingReport +signingReport +signingReport +sourceSets +sourceSets +sourceSets +sourceSets +sourceSets +assemble +assemble +assemble +assemble +assemble +assembleAndroidTest +assembleAndroidTest +assembleAndroidTest +assembleAndroidTest +assembleAndroidTest +build +build +build +build +build +buildDependents +buildDependents +buildDependents +buildDependents +buildDependents +buildNeeded +buildNeeded +buildNeeded +buildNeeded +buildNeeded +bundle +clean +clean +clean +clean +clean +cleanBuildCache +cleanBuildCache +cleanBuildCache +cleanBuildCache +cleanBuildCache +compileDebugAndroidTestSources +compileDebugAndroidTestSources +compileDebugAndroidTestSources +compileDebugAndroidTestSources +compileDebugAndroidTestSources +compileDebugSources +compileDebugSources +compileDebugSources +compileDebugSources +compileDebugSources +compileDebugUnitTestSources +compileDebugUnitTestSources +compileDebugUnitTestSources +compileDebugUnitTestSources +compileDebugUnitTestSources +compileDevSources +compileDevSources +compileDevSources +compileDevSources +compileDevSources +compileDevUnitTestSources +compileDevUnitTestSources +compileDevUnitTestSources +compileDevUnitTestSources +compileDevUnitTestSources +compileReleaseSources +compileReleaseSources +compileReleaseSources +compileReleaseSources +compileReleaseSources +compileReleaseUnitTestSources +compileReleaseUnitTestSources +compileReleaseUnitTestSources +compileReleaseUnitTestSources +compileReleaseUnitTestSources +extractDebugAnnotations +extractDebugAnnotations +extractDebugAnnotations +extractDebugAnnotations +extractDevAnnotations +extractDevAnnotations +extractDevAnnotations +extractDevAnnotations +extractReleaseAnnotations +extractReleaseAnnotations +extractReleaseAnnotations +extractReleaseAnnotations +init +wrapper +lintFix +lintFix +lintFix +lintFix +lintFix +dokka +dokka +dokka +dokka +dokka +dokka +buildEnvironment +buildEnvironment +buildEnvironment +buildEnvironment +buildEnvironment +buildEnvironment +buildEnvironment +components +components +components +components +components +components +components +dependencies +dependencies +dependencies +dependencies +dependencies +dependencies +dependencies +dependencyInsight +dependencyInsight +dependencyInsight +dependencyInsight +dependencyInsight +dependencyInsight +dependencyInsight +dependentComponents +dependentComponents +dependentComponents +dependentComponents +dependentComponents +dependentComponents +dependentComponents +help +help +help +help +help +help +help +model +model +model +model +model +model +model +projects +projects +projects +projects +projects +projects +projects +properties +properties +properties +properties +properties +properties +properties +tasks +tasks +tasks +tasks +tasks +tasks +tasks +installDebug +installDebugAndroidTest +installDebugAndroidTest +installDebugAndroidTest +installDebugAndroidTest +installDebugAndroidTest +installDev +uninstallAll +uninstallAll +uninstallAll +uninstallAll +uninstallAll +uninstallDebug +uninstallDebugAndroidTest +uninstallDebugAndroidTest +uninstallDebugAndroidTest +uninstallDebugAndroidTest +uninstallDebugAndroidTest +uninstallDev +uninstallRelease +jacocoTestReport +jacocoTestReport +jacocoTestReport +jacocoTestReport +jacocoTestReport +jacocoTestReportDebug +jacocoTestReportDebug +jacocoTestReportDebug +jacocoTestReportDebug +jacocoTestReportDebug +jacocoTestReportDev +jacocoTestReportDev +jacocoTestReportDev +jacocoTestReportDev +jacocoTestReportDev +jacocoTestReportRelease +jacocoTestReportRelease +jacocoTestReportRelease +jacocoTestReportRelease +jacocoTestReportRelease +check +check +check +check +check +connectedAndroidTest +connectedAndroidTest +connectedAndroidTest +connectedAndroidTest +connectedAndroidTest +connectedCheck +connectedCheck +connectedCheck +connectedCheck +connectedCheck +connectedDebugAndroidTest +connectedDebugAndroidTest +connectedDebugAndroidTest +connectedDebugAndroidTest +connectedDebugAndroidTest +createDebugCoverageReport +createDebugCoverageReport +createDebugCoverageReport +createDebugCoverageReport +createDebugCoverageReport +createDevCoverageReport +createDevCoverageReport +createDevCoverageReport +createDevCoverageReport +createDevCoverageReport +detekt +detekt +detekt +detekt +detekt +detekt +detektBaseline +detektBaseline +detektBaseline +detektBaseline +detektBaseline +detektBaseline +detektGenerateConfig +detektGenerateConfig +detektGenerateConfig +detektGenerateConfig +detektGenerateConfig +detektGenerateConfig +detektIdeaFormat +detektIdeaFormat +detektIdeaFormat +detektIdeaFormat +detektIdeaFormat +detektIdeaFormat +detektIdeaInspect +detektIdeaInspect +detektIdeaInspect +detektIdeaInspect +detektIdeaInspect +detektIdeaInspect +deviceAndroidTest +deviceAndroidTest +deviceAndroidTest +deviceAndroidTest +deviceAndroidTest +deviceCheck +deviceCheck +deviceCheck +deviceCheck +deviceCheck +lint +lint +lint +lint +lint +lintDebug +lintDebug +lintDebug +lintDebug +lintDebug +lintDev +lintDev +lintDev +lintDev +lintDev +lintRelease +lintRelease +lintRelease +lintRelease +lintRelease +lintVitalRelease +test +test +test +test +test +testDebugUnitTest +testDebugUnitTest +testDebugUnitTest +testDebugUnitTest +testDebugUnitTest +testDevUnitTest +testDevUnitTest +testDevUnitTest +testDevUnitTest +testDevUnitTest +testReleaseUnitTest +testReleaseUnitTest +testReleaseUnitTest +testReleaseUnitTest +testReleaseUnitTest +assembleDebug +assembleDebug +assembleDebug +assembleDebug +assembleDebug +assembleDebugAndroidTest +assembleDebugAndroidTest +assembleDebugAndroidTest +assembleDebugAndroidTest +assembleDebugAndroidTest +assembleDebugUnitTest +assembleDebugUnitTest +assembleDebugUnitTest +assembleDebugUnitTest +assembleDebugUnitTest +assembleDev +assembleDev +assembleDev +assembleDev +assembleDev +assembleDevUnitTest +assembleDevUnitTest +assembleDevUnitTest +assembleDevUnitTest +assembleDevUnitTest +assembleRelease +assembleRelease +assembleRelease +assembleRelease +assembleRelease +assembleReleaseUnitTest +assembleReleaseUnitTest +assembleReleaseUnitTest +assembleReleaseUnitTest +assembleReleaseUnitTest +buildDebugPreBundle +buildDevPreBundle +buildReleasePreBundle +bundleDebug +bundleDebugAar +bundleDebugAar +bundleDebugAar +bundleDebugAar +bundleDebugAndroidTestClasses +bundleDebugAndroidTestResources +bundleDebugAndroidTestResources +bundleDebugAndroidTestResources +bundleDebugAndroidTestResources +bundleDebugAndroidTestResources +bundleDebugClasses +bundleDebugResources +bundleDebugUnitTestClasses +bundleDev +bundleDevAar +bundleDevAar +bundleDevAar +bundleDevAar +bundleDevClasses +bundleDevResources +bundleDevUnitTestClasses +bundleLibCompileDebug +bundleLibCompileDebug +bundleLibCompileDebug +bundleLibCompileDebug +bundleLibCompileDebugAndroidTest +bundleLibCompileDebugAndroidTest +bundleLibCompileDebugAndroidTest +bundleLibCompileDebugAndroidTest +bundleLibCompileDebugUnitTest +bundleLibCompileDebugUnitTest +bundleLibCompileDebugUnitTest +bundleLibCompileDebugUnitTest +bundleLibCompileDev +bundleLibCompileDev +bundleLibCompileDev +bundleLibCompileDev +bundleLibCompileDevUnitTest +bundleLibCompileDevUnitTest +bundleLibCompileDevUnitTest +bundleLibCompileDevUnitTest +bundleLibCompileRelease +bundleLibCompileRelease +bundleLibCompileRelease +bundleLibCompileRelease +bundleLibCompileReleaseUnitTest +bundleLibCompileReleaseUnitTest +bundleLibCompileReleaseUnitTest +bundleLibCompileReleaseUnitTest +bundleLibResDebug +bundleLibResDebug +bundleLibResDebug +bundleLibResDebug +bundleLibResDev +bundleLibResDev +bundleLibResDev +bundleLibResDev +bundleLibResRelease +bundleLibResRelease +bundleLibResRelease +bundleLibResRelease +bundleLibRuntimeDebug +bundleLibRuntimeDebug +bundleLibRuntimeDebug +bundleLibRuntimeDebug +bundleLibRuntimeDev +bundleLibRuntimeDev +bundleLibRuntimeDev +bundleLibRuntimeDev +bundleLibRuntimeRelease +bundleLibRuntimeRelease +bundleLibRuntimeRelease +bundleLibRuntimeRelease +bundleRelease +bundleReleaseAar +bundleReleaseAar +bundleReleaseAar +bundleReleaseAar +bundleReleaseClasses +bundleReleaseResources +bundleReleaseUnitTestClasses +checkDebugAndroidTestDuplicateClasses +checkDebugAndroidTestDuplicateClasses +checkDebugAndroidTestDuplicateClasses +checkDebugAndroidTestDuplicateClasses +checkDebugAndroidTestDuplicateClasses +checkDebugDuplicateClasses +checkDebugManifest +checkDebugManifest +checkDebugManifest +checkDebugManifest +checkDebugManifest +checkDevDuplicateClasses +checkDevManifest +checkDevManifest +checkDevManifest +checkDevManifest +checkDevManifest +checkReleaseManifest +checkReleaseManifest +checkReleaseManifest +checkReleaseManifest +checkReleaseManifest +clean +collectDebugDependencies +collectDevDependencies +collectReleaseDependencies +compileDebugAidl +compileDebugAidl +compileDebugAidl +compileDebugAidl +compileDebugAidl +compileDebugAndroidTestAidl +compileDebugAndroidTestAidl +compileDebugAndroidTestAidl +compileDebugAndroidTestAidl +compileDebugAndroidTestAidl +compileDebugAndroidTestJavaWithJavac +compileDebugAndroidTestJavaWithJavac +compileDebugAndroidTestJavaWithJavac +compileDebugAndroidTestJavaWithJavac +compileDebugAndroidTestJavaWithJavac +compileDebugAndroidTestKotlin +compileDebugAndroidTestKotlin +compileDebugAndroidTestKotlin +compileDebugAndroidTestKotlin +compileDebugAndroidTestKotlin +compileDebugAndroidTestRenderscript +compileDebugAndroidTestRenderscript +compileDebugAndroidTestRenderscript +compileDebugAndroidTestRenderscript +compileDebugAndroidTestRenderscript +compileDebugAndroidTestShaders +compileDebugAndroidTestShaders +compileDebugAndroidTestShaders +compileDebugAndroidTestShaders +compileDebugAndroidTestShaders +compileDebugJavaWithJavac +compileDebugJavaWithJavac +compileDebugJavaWithJavac +compileDebugJavaWithJavac +compileDebugJavaWithJavac +compileDebugKotlin +compileDebugKotlin +compileDebugKotlin +compileDebugKotlin +compileDebugKotlin +compileDebugRenderscript +compileDebugRenderscript +compileDebugRenderscript +compileDebugRenderscript +compileDebugRenderscript +compileDebugShaders +compileDebugShaders +compileDebugShaders +compileDebugShaders +compileDebugShaders +compileDebugUnitTestJavaWithJavac +compileDebugUnitTestJavaWithJavac +compileDebugUnitTestJavaWithJavac +compileDebugUnitTestJavaWithJavac +compileDebugUnitTestJavaWithJavac +compileDebugUnitTestKotlin +compileDebugUnitTestKotlin +compileDebugUnitTestKotlin +compileDebugUnitTestKotlin +compileDebugUnitTestKotlin +compileDevAidl +compileDevAidl +compileDevAidl +compileDevAidl +compileDevAidl +compileDevJavaWithJavac +compileDevJavaWithJavac +compileDevJavaWithJavac +compileDevJavaWithJavac +compileDevJavaWithJavac +compileDevKotlin +compileDevKotlin +compileDevKotlin +compileDevKotlin +compileDevKotlin +compileDevRenderscript +compileDevRenderscript +compileDevRenderscript +compileDevRenderscript +compileDevRenderscript +compileDevShaders +compileDevShaders +compileDevShaders +compileDevShaders +compileDevShaders +compileDevUnitTestJavaWithJavac +compileDevUnitTestJavaWithJavac +compileDevUnitTestJavaWithJavac +compileDevUnitTestJavaWithJavac +compileDevUnitTestJavaWithJavac +compileDevUnitTestKotlin +compileDevUnitTestKotlin +compileDevUnitTestKotlin +compileDevUnitTestKotlin +compileDevUnitTestKotlin +compileLint +compileLint +compileLint +compileLint +compileLint +compileReleaseAidl +compileReleaseAidl +compileReleaseAidl +compileReleaseAidl +compileReleaseAidl +compileReleaseJavaWithJavac +compileReleaseJavaWithJavac +compileReleaseJavaWithJavac +compileReleaseJavaWithJavac +compileReleaseJavaWithJavac +compileReleaseKotlin +compileReleaseKotlin +compileReleaseKotlin +compileReleaseKotlin +compileReleaseKotlin +compileReleaseRenderscript +compileReleaseRenderscript +compileReleaseRenderscript +compileReleaseRenderscript +compileReleaseRenderscript +compileReleaseShaders +compileReleaseShaders +compileReleaseShaders +compileReleaseShaders +compileReleaseShaders +compileReleaseUnitTestJavaWithJavac +compileReleaseUnitTestJavaWithJavac +compileReleaseUnitTestJavaWithJavac +compileReleaseUnitTestJavaWithJavac +compileReleaseUnitTestJavaWithJavac +compileReleaseUnitTestKotlin +compileReleaseUnitTestKotlin +compileReleaseUnitTestKotlin +compileReleaseUnitTestKotlin +compileReleaseUnitTestKotlin +configureDebugDependencies +configureDevDependencies +configureReleaseDependencies +consumeConfigAttr +consumeConfigAttr +consumeConfigAttr +consumeConfigAttr +consumeConfigAttr +createDebugAndroidTestCoverageReport +createDebugAndroidTestCoverageReport +createDebugAndroidTestCoverageReport +createDebugAndroidTestCoverageReport +createDebugAndroidTestCoverageReport +createDebugCompatibleScreenManifests +createDevCompatibleScreenManifests +createFullJarDebug +createFullJarDebug +createFullJarDebug +createFullJarDebug +createFullJarDev +createFullJarDev +createFullJarDev +createFullJarDev +createFullJarRelease +createFullJarRelease +createFullJarRelease +createFullJarRelease +createMockableJar +createMockableJar +createMockableJar +createMockableJar +createMockableJar +createReleaseCompatibleScreenManifests +dataBindingExportBuildInfoDebug +dataBindingExportBuildInfoDebug +dataBindingExportBuildInfoDebug +dataBindingExportBuildInfoDebug +dataBindingExportBuildInfoDebugAndroidTest +dataBindingExportBuildInfoDebugAndroidTest +dataBindingExportBuildInfoDebugAndroidTest +dataBindingExportBuildInfoDev +dataBindingExportBuildInfoDev +dataBindingExportBuildInfoDev +dataBindingExportBuildInfoDev +dataBindingExportBuildInfoRelease +dataBindingExportBuildInfoRelease +dataBindingExportBuildInfoRelease +dataBindingExportBuildInfoRelease +dataBindingExportFeaturePackageIdsDebug +dataBindingExportFeaturePackageIdsDev +dataBindingExportFeaturePackageIdsRelease +dataBindingGenBaseClassesDebug +dataBindingGenBaseClassesDebug +dataBindingGenBaseClassesDebug +dataBindingGenBaseClassesDebug +dataBindingGenBaseClassesDebugAndroidTest +dataBindingGenBaseClassesDebugAndroidTest +dataBindingGenBaseClassesDebugAndroidTest +dataBindingGenBaseClassesDev +dataBindingGenBaseClassesDev +dataBindingGenBaseClassesDev +dataBindingGenBaseClassesDev +dataBindingGenBaseClassesRelease +dataBindingGenBaseClassesRelease +dataBindingGenBaseClassesRelease +dataBindingGenBaseClassesRelease +dataBindingMergeDependencyArtifactsDebug +dataBindingMergeDependencyArtifactsDebug +dataBindingMergeDependencyArtifactsDebug +dataBindingMergeDependencyArtifactsDebug +dataBindingMergeDependencyArtifactsDebugAndroidTest +dataBindingMergeDependencyArtifactsDebugAndroidTest +dataBindingMergeDependencyArtifactsDebugAndroidTest +dataBindingMergeDependencyArtifactsDev +dataBindingMergeDependencyArtifactsDev +dataBindingMergeDependencyArtifactsDev +dataBindingMergeDependencyArtifactsDev +dataBindingMergeDependencyArtifactsRelease +dataBindingMergeDependencyArtifactsRelease +dataBindingMergeDependencyArtifactsRelease +dataBindingMergeDependencyArtifactsRelease +dataBindingMergeGenClassesDebug +dataBindingMergeGenClassesDebug +dataBindingMergeGenClassesDebug +dataBindingMergeGenClassesDebug +dataBindingMergeGenClassesDebugAndroidTest +dataBindingMergeGenClassesDebugAndroidTest +dataBindingMergeGenClassesDebugAndroidTest +dataBindingMergeGenClassesDev +dataBindingMergeGenClassesDev +dataBindingMergeGenClassesDev +dataBindingMergeGenClassesDev +dataBindingMergeGenClassesRelease +dataBindingMergeGenClassesRelease +dataBindingMergeGenClassesRelease +dataBindingMergeGenClassesRelease +desugarDebugAndroidTestFileDependencies +desugarDebugAndroidTestFileDependencies +desugarDebugAndroidTestFileDependencies +desugarDebugAndroidTestFileDependencies +desugarDebugAndroidTestFileDependencies +desugarDebugFileDependencies +desugarDevFileDependencies +dexDebug +dexDebug +dexDebug +dexDebug +dexDev +dexDev +dexDev +dexDev +dexRelease +dexRelease +dexRelease +dexRelease +dummydebugUnitTest +dummydebugUnitTest +dummydebugUnitTest +dummydebugUnitTest +dummydebugUnitTest +dummydevUnitTest +dummydevUnitTest +dummydevUnitTest +dummydevUnitTest +dummydevUnitTest +dummyreleaseUnitTest +dummyreleaseUnitTest +dummyreleaseUnitTest +dummyreleaseUnitTest +dummyreleaseUnitTest +extractApksForDebug +extractApksForDev +extractApksForRelease +extractProguardFiles +extractProguardFiles +extractProguardFiles +extractProguardFiles +extractProguardFiles +generateDebugAndroidTestAssets +generateDebugAndroidTestAssets +generateDebugAndroidTestAssets +generateDebugAndroidTestAssets +generateDebugAndroidTestAssets +generateDebugAndroidTestBuildConfig +generateDebugAndroidTestBuildConfig +generateDebugAndroidTestBuildConfig +generateDebugAndroidTestBuildConfig +generateDebugAndroidTestBuildConfig +generateDebugAndroidTestResources +generateDebugAndroidTestResources +generateDebugAndroidTestResources +generateDebugAndroidTestResources +generateDebugAndroidTestResources +generateDebugAndroidTestResValues +generateDebugAndroidTestResValues +generateDebugAndroidTestResValues +generateDebugAndroidTestResValues +generateDebugAndroidTestResValues +generateDebugAndroidTestSources +generateDebugAndroidTestSources +generateDebugAndroidTestSources +generateDebugAndroidTestSources +generateDebugAndroidTestSources +generateDebugAssets +generateDebugAssets +generateDebugAssets +generateDebugAssets +generateDebugAssets +generateDebugBuildConfig +generateDebugBuildConfig +generateDebugBuildConfig +generateDebugBuildConfig +generateDebugBuildConfig +generateDebugFeatureMetadata +generateDebugFeatureTransitiveDeps +generateDebugResources +generateDebugResources +generateDebugResources +generateDebugResources +generateDebugResources +generateDebugResValues +generateDebugResValues +generateDebugResValues +generateDebugResValues +generateDebugResValues +generateDebugRFile +generateDebugRFile +generateDebugRFile +generateDebugRFile +generateDebugSources +generateDebugSources +generateDebugSources +generateDebugSources +generateDebugSources +generateDebugUnitTestAssets +generateDebugUnitTestAssets +generateDebugUnitTestAssets +generateDebugUnitTestAssets +generateDebugUnitTestAssets +generateDebugUnitTestResources +generateDebugUnitTestResources +generateDebugUnitTestResources +generateDebugUnitTestResources +generateDebugUnitTestResources +generateDebugUnitTestSources +generateDebugUnitTestSources +generateDebugUnitTestSources +generateDebugUnitTestSources +generateDebugUnitTestSources +generateDevAssets +generateDevAssets +generateDevAssets +generateDevAssets +generateDevAssets +generateDevBuildConfig +generateDevBuildConfig +generateDevBuildConfig +generateDevBuildConfig +generateDevBuildConfig +generateDevFeatureMetadata +generateDevFeatureTransitiveDeps +generateDevResources +generateDevResources +generateDevResources +generateDevResources +generateDevResources +generateDevResValues +generateDevResValues +generateDevResValues +generateDevResValues +generateDevResValues +generateDevRFile +generateDevRFile +generateDevRFile +generateDevRFile +generateDevSources +generateDevSources +generateDevSources +generateDevSources +generateDevSources +generateDevUnitTestAssets +generateDevUnitTestAssets +generateDevUnitTestAssets +generateDevUnitTestAssets +generateDevUnitTestAssets +generateDevUnitTestResources +generateDevUnitTestResources +generateDevUnitTestResources +generateDevUnitTestResources +generateDevUnitTestResources +generateDevUnitTestSources +generateDevUnitTestSources +generateDevUnitTestSources +generateDevUnitTestSources +generateDevUnitTestSources +generateReleaseAssets +generateReleaseAssets +generateReleaseAssets +generateReleaseAssets +generateReleaseAssets +generateReleaseBuildConfig +generateReleaseBuildConfig +generateReleaseBuildConfig +generateReleaseBuildConfig +generateReleaseBuildConfig +generateReleaseFeatureMetadata +generateReleaseFeatureTransitiveDeps +generateReleaseLibraryProguardRules +generateReleaseLibraryProguardRules +generateReleaseLibraryProguardRules +generateReleaseLibraryProguardRules +generateReleaseResources +generateReleaseResources +generateReleaseResources +generateReleaseResources +generateReleaseResources +generateReleaseResValues +generateReleaseResValues +generateReleaseResValues +generateReleaseResValues +generateReleaseResValues +generateReleaseRFile +generateReleaseRFile +generateReleaseRFile +generateReleaseRFile +generateReleaseSources +generateReleaseSources +generateReleaseSources +generateReleaseSources +generateReleaseSources +generateReleaseUnitTestAssets +generateReleaseUnitTestAssets +generateReleaseUnitTestAssets +generateReleaseUnitTestAssets +generateReleaseUnitTestAssets +generateReleaseUnitTestResources +generateReleaseUnitTestResources +generateReleaseUnitTestResources +generateReleaseUnitTestResources +generateReleaseUnitTestResources +generateReleaseUnitTestSources +generateReleaseUnitTestSources +generateReleaseUnitTestSources +generateReleaseUnitTestSources +generateReleaseUnitTestSources +generateSafeArgsDebug +generateSafeArgsDev +generateSafeArgsRelease +jacocoDebug +jacocoDebug +jacocoDebug +jacocoDebug +jacocoDebug +jacocoDev +jacocoDev +jacocoDev +jacocoDev +jacocoDev +javaPreCompileDebug +javaPreCompileDebug +javaPreCompileDebug +javaPreCompileDebug +javaPreCompileDebug +javaPreCompileDebugAndroidTest +javaPreCompileDebugAndroidTest +javaPreCompileDebugAndroidTest +javaPreCompileDebugAndroidTest +javaPreCompileDebugAndroidTest +javaPreCompileDebugUnitTest +javaPreCompileDebugUnitTest +javaPreCompileDebugUnitTest +javaPreCompileDebugUnitTest +javaPreCompileDebugUnitTest +javaPreCompileDev +javaPreCompileDev +javaPreCompileDev +javaPreCompileDev +javaPreCompileDev +javaPreCompileDevUnitTest +javaPreCompileDevUnitTest +javaPreCompileDevUnitTest +javaPreCompileDevUnitTest +javaPreCompileDevUnitTest +javaPreCompileRelease +javaPreCompileRelease +javaPreCompileRelease +javaPreCompileRelease +javaPreCompileRelease +javaPreCompileReleaseUnitTest +javaPreCompileReleaseUnitTest +javaPreCompileReleaseUnitTest +javaPreCompileReleaseUnitTest +javaPreCompileReleaseUnitTest +kaptDebugAndroidTestKotlin +kaptDebugAndroidTestKotlin +kaptDebugAndroidTestKotlin +kaptDebugAndroidTestKotlin +kaptDebugAndroidTestKotlin +kaptDebugKotlin +kaptDebugKotlin +kaptDebugKotlin +kaptDebugKotlin +kaptDebugKotlin +kaptDebugUnitTestKotlin +kaptDebugUnitTestKotlin +kaptDebugUnitTestKotlin +kaptDebugUnitTestKotlin +kaptDebugUnitTestKotlin +kaptDevKotlin +kaptDevKotlin +kaptDevKotlin +kaptDevKotlin +kaptDevKotlin +kaptDevUnitTestKotlin +kaptDevUnitTestKotlin +kaptDevUnitTestKotlin +kaptDevUnitTestKotlin +kaptDevUnitTestKotlin +kaptGenerateStubsDebugAndroidTestKotlin +kaptGenerateStubsDebugAndroidTestKotlin +kaptGenerateStubsDebugAndroidTestKotlin +kaptGenerateStubsDebugAndroidTestKotlin +kaptGenerateStubsDebugAndroidTestKotlin +kaptGenerateStubsDebugKotlin +kaptGenerateStubsDebugKotlin +kaptGenerateStubsDebugKotlin +kaptGenerateStubsDebugKotlin +kaptGenerateStubsDebugKotlin +kaptGenerateStubsDebugUnitTestKotlin +kaptGenerateStubsDebugUnitTestKotlin +kaptGenerateStubsDebugUnitTestKotlin +kaptGenerateStubsDebugUnitTestKotlin +kaptGenerateStubsDebugUnitTestKotlin +kaptGenerateStubsDevKotlin +kaptGenerateStubsDevKotlin +kaptGenerateStubsDevKotlin +kaptGenerateStubsDevKotlin +kaptGenerateStubsDevKotlin +kaptGenerateStubsDevUnitTestKotlin +kaptGenerateStubsDevUnitTestKotlin +kaptGenerateStubsDevUnitTestKotlin +kaptGenerateStubsDevUnitTestKotlin +kaptGenerateStubsDevUnitTestKotlin +kaptGenerateStubsReleaseKotlin +kaptGenerateStubsReleaseKotlin +kaptGenerateStubsReleaseKotlin +kaptGenerateStubsReleaseKotlin +kaptGenerateStubsReleaseKotlin +kaptGenerateStubsReleaseUnitTestKotlin +kaptGenerateStubsReleaseUnitTestKotlin +kaptGenerateStubsReleaseUnitTestKotlin +kaptGenerateStubsReleaseUnitTestKotlin +kaptGenerateStubsReleaseUnitTestKotlin +kaptReleaseKotlin +kaptReleaseKotlin +kaptReleaseKotlin +kaptReleaseKotlin +kaptReleaseKotlin +kaptReleaseUnitTestKotlin +kaptReleaseUnitTestKotlin +kaptReleaseUnitTestKotlin +kaptReleaseUnitTestKotlin +kaptReleaseUnitTestKotlin +mainApkListPersistenceDebug +mainApkListPersistenceDebugAndroidTest +mainApkListPersistenceDebugAndroidTest +mainApkListPersistenceDebugAndroidTest +mainApkListPersistenceDebugAndroidTest +mainApkListPersistenceDebugAndroidTest +mainApkListPersistenceDev +mainApkListPersistenceRelease +makeApkFromBundleForDebug +makeApkFromBundleForDev +makeApkFromBundleForRelease +mergeDebugAndroidTestAssets +mergeDebugAndroidTestAssets +mergeDebugAndroidTestAssets +mergeDebugAndroidTestAssets +mergeDebugAndroidTestAssets +mergeDebugAndroidTestGeneratedProguardFiles +mergeDebugAndroidTestGeneratedProguardFiles +mergeDebugAndroidTestGeneratedProguardFiles +mergeDebugAndroidTestGeneratedProguardFiles +mergeDebugAndroidTestGeneratedProguardFiles +mergeDebugAndroidTestJavaResource +mergeDebugAndroidTestJavaResource +mergeDebugAndroidTestJavaResource +mergeDebugAndroidTestJavaResource +mergeDebugAndroidTestJavaResource +mergeDebugAndroidTestJniLibFolders +mergeDebugAndroidTestJniLibFolders +mergeDebugAndroidTestJniLibFolders +mergeDebugAndroidTestJniLibFolders +mergeDebugAndroidTestJniLibFolders +mergeDebugAndroidTestNativeLibs +mergeDebugAndroidTestNativeLibs +mergeDebugAndroidTestNativeLibs +mergeDebugAndroidTestNativeLibs +mergeDebugAndroidTestNativeLibs +mergeDebugAndroidTestResources +mergeDebugAndroidTestResources +mergeDebugAndroidTestResources +mergeDebugAndroidTestResources +mergeDebugAndroidTestResources +mergeDebugAndroidTestShaders +mergeDebugAndroidTestShaders +mergeDebugAndroidTestShaders +mergeDebugAndroidTestShaders +mergeDebugAndroidTestShaders +mergeDebugAssets +mergeDebugAssets +mergeDebugAssets +mergeDebugAssets +mergeDebugAssets +mergeDebugConsumerProguardFiles +mergeDebugConsumerProguardFiles +mergeDebugConsumerProguardFiles +mergeDebugConsumerProguardFiles +mergeDebugGeneratedProguardFiles +mergeDebugGeneratedProguardFiles +mergeDebugGeneratedProguardFiles +mergeDebugGeneratedProguardFiles +mergeDebugGeneratedProguardFiles +mergeDebugJavaResource +mergeDebugJavaResource +mergeDebugJavaResource +mergeDebugJavaResource +mergeDebugJavaResource +mergeDebugJniLibFolders +mergeDebugJniLibFolders +mergeDebugJniLibFolders +mergeDebugJniLibFolders +mergeDebugJniLibFolders +mergeDebugNativeLibs +mergeDebugNativeLibs +mergeDebugNativeLibs +mergeDebugNativeLibs +mergeDebugNativeLibs +mergeDebugResources +mergeDebugResources +mergeDebugResources +mergeDebugResources +mergeDebugResources +mergeDebugShaders +mergeDebugShaders +mergeDebugShaders +mergeDebugShaders +mergeDebugShaders +mergeDevAssets +mergeDevAssets +mergeDevAssets +mergeDevAssets +mergeDevAssets +mergeDevConsumerProguardFiles +mergeDevConsumerProguardFiles +mergeDevConsumerProguardFiles +mergeDevConsumerProguardFiles +mergeDevGeneratedProguardFiles +mergeDevGeneratedProguardFiles +mergeDevGeneratedProguardFiles +mergeDevGeneratedProguardFiles +mergeDevGeneratedProguardFiles +mergeDevJavaResource +mergeDevJavaResource +mergeDevJavaResource +mergeDevJavaResource +mergeDevJavaResource +mergeDevJniLibFolders +mergeDevJniLibFolders +mergeDevJniLibFolders +mergeDevJniLibFolders +mergeDevJniLibFolders +mergeDevNativeLibs +mergeDevNativeLibs +mergeDevNativeLibs +mergeDevNativeLibs +mergeDevNativeLibs +mergeDevResources +mergeDevResources +mergeDevResources +mergeDevResources +mergeDevResources +mergeDevShaders +mergeDevShaders +mergeDevShaders +mergeDevShaders +mergeDevShaders +mergeExtDexDebug +mergeExtDexDebugAndroidTest +mergeExtDexDebugAndroidTest +mergeExtDexDebugAndroidTest +mergeExtDexDebugAndroidTest +mergeExtDexDebugAndroidTest +mergeExtDexDev +mergeLibDexDebug +mergeLibDexDebugAndroidTest +mergeLibDexDebugAndroidTest +mergeLibDexDebugAndroidTest +mergeLibDexDebugAndroidTest +mergeLibDexDebugAndroidTest +mergeLibDexDev +mergeProjectDexDebug +mergeProjectDexDebugAndroidTest +mergeProjectDexDebugAndroidTest +mergeProjectDexDebugAndroidTest +mergeProjectDexDebugAndroidTest +mergeProjectDexDebugAndroidTest +mergeProjectDexDev +mergeReleaseAssets +mergeReleaseAssets +mergeReleaseAssets +mergeReleaseAssets +mergeReleaseAssets +mergeReleaseConsumerProguardFiles +mergeReleaseConsumerProguardFiles +mergeReleaseConsumerProguardFiles +mergeReleaseConsumerProguardFiles +mergeReleaseGeneratedProguardFiles +mergeReleaseGeneratedProguardFiles +mergeReleaseGeneratedProguardFiles +mergeReleaseGeneratedProguardFiles +mergeReleaseGeneratedProguardFiles +mergeReleaseJavaResource +mergeReleaseJavaResource +mergeReleaseJavaResource +mergeReleaseJavaResource +mergeReleaseJavaResource +mergeReleaseJniLibFolders +mergeReleaseJniLibFolders +mergeReleaseJniLibFolders +mergeReleaseJniLibFolders +mergeReleaseJniLibFolders +mergeReleaseNativeLibs +mergeReleaseNativeLibs +mergeReleaseNativeLibs +mergeReleaseNativeLibs +mergeReleaseNativeLibs +mergeReleaseResources +mergeReleaseResources +mergeReleaseResources +mergeReleaseResources +mergeReleaseResources +mergeReleaseShaders +mergeReleaseShaders +mergeReleaseShaders +mergeReleaseShaders +mergeReleaseShaders +packageDebug +packageDebugAndroidTest +packageDebugAndroidTest +packageDebugAndroidTest +packageDebugAndroidTest +packageDebugAndroidTest +packageDebugAssets +packageDebugAssets +packageDebugAssets +packageDebugAssets +packageDebugBundle +packageDebugRenderscript +packageDebugRenderscript +packageDebugRenderscript +packageDebugRenderscript +packageDebugResources +packageDebugResources +packageDebugResources +packageDebugResources +packageDebugUniversalApk +packageDev +packageDevAssets +packageDevAssets +packageDevAssets +packageDevAssets +packageDevBundle +packageDevRenderscript +packageDevRenderscript +packageDevRenderscript +packageDevRenderscript +packageDevResources +packageDevResources +packageDevResources +packageDevResources +packageDevUniversalApk +packageRelease +packageReleaseAssets +packageReleaseAssets +packageReleaseAssets +packageReleaseAssets +packageReleaseBundle +packageReleaseRenderscript +packageReleaseRenderscript +packageReleaseRenderscript +packageReleaseRenderscript +packageReleaseResources +packageReleaseResources +packageReleaseResources +packageReleaseResources +packageReleaseUniversalApk +parseDebugLibraryResources +parseDebugLibraryResources +parseDebugLibraryResources +parseDebugLibraryResources +parseDevLibraryResources +parseDevLibraryResources +parseDevLibraryResources +parseDevLibraryResources +parseReleaseLibraryResources +parseReleaseLibraryResources +parseReleaseLibraryResources +parseReleaseLibraryResources +preBuild +preBuild +preBuild +preBuild +preBuild +preDebugAndroidTestBuild +preDebugAndroidTestBuild +preDebugAndroidTestBuild +preDebugAndroidTestBuild +preDebugAndroidTestBuild +preDebugBuild +preDebugBuild +preDebugBuild +preDebugBuild +preDebugBuild +preDebugUnitTestBuild +preDebugUnitTestBuild +preDebugUnitTestBuild +preDebugUnitTestBuild +preDebugUnitTestBuild +preDevBuild +preDevBuild +preDevBuild +preDevBuild +preDevBuild +preDevUnitTestBuild +preDevUnitTestBuild +preDevUnitTestBuild +preDevUnitTestBuild +preDevUnitTestBuild +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareKotlinBuildScriptModel +prepareLintJar +prepareLintJar +prepareLintJar +prepareLintJar +prepareLintJar +prepareLintJarForPublish +prepareLintJarForPublish +prepareLintJarForPublish +prepareLintJarForPublish +prepareLintJarForPublish +preReleaseBuild +preReleaseBuild +preReleaseBuild +preReleaseBuild +preReleaseBuild +preReleaseUnitTestBuild +preReleaseUnitTestBuild +preReleaseUnitTestBuild +preReleaseUnitTestBuild +preReleaseUnitTestBuild +processDebugAndroidTestJavaRes +processDebugAndroidTestJavaRes +processDebugAndroidTestJavaRes +processDebugAndroidTestJavaRes +processDebugAndroidTestJavaRes +processDebugAndroidTestManifest +processDebugAndroidTestManifest +processDebugAndroidTestManifest +processDebugAndroidTestManifest +processDebugAndroidTestManifest +processDebugAndroidTestResources +processDebugAndroidTestResources +processDebugAndroidTestResources +processDebugAndroidTestResources +processDebugAndroidTestResources +processDebugJavaRes +processDebugJavaRes +processDebugJavaRes +processDebugJavaRes +processDebugJavaRes +processDebugManifest +processDebugManifest +processDebugManifest +processDebugManifest +processDebugManifest +processDebugResources +processDebugUnitTestJavaRes +processDebugUnitTestJavaRes +processDebugUnitTestJavaRes +processDebugUnitTestJavaRes +processDebugUnitTestJavaRes +processDevJavaRes +processDevJavaRes +processDevJavaRes +processDevJavaRes +processDevJavaRes +processDevManifest +processDevManifest +processDevManifest +processDevManifest +processDevManifest +processDevResources +processDevUnitTestJavaRes +processDevUnitTestJavaRes +processDevUnitTestJavaRes +processDevUnitTestJavaRes +processDevUnitTestJavaRes +processReleaseJavaRes +processReleaseJavaRes +processReleaseJavaRes +processReleaseJavaRes +processReleaseJavaRes +processReleaseManifest +processReleaseManifest +processReleaseManifest +processReleaseManifest +processReleaseManifest +processReleaseResources +processReleaseUnitTestJavaRes +processReleaseUnitTestJavaRes +processReleaseUnitTestJavaRes +processReleaseUnitTestJavaRes +processReleaseUnitTestJavaRes +projectDependencyGraph +removeReports +reportBuildArtifactsDebug +reportBuildArtifactsDebug +reportBuildArtifactsDebug +reportBuildArtifactsDebug +reportBuildArtifactsDebug +reportBuildArtifactsDev +reportBuildArtifactsDev +reportBuildArtifactsDev +reportBuildArtifactsDev +reportBuildArtifactsDev +reportBuildArtifactsRelease +reportBuildArtifactsRelease +reportBuildArtifactsRelease +reportBuildArtifactsRelease +reportBuildArtifactsRelease +reportSourceSetTransformAndroidTest +reportSourceSetTransformAndroidTest +reportSourceSetTransformAndroidTest +reportSourceSetTransformAndroidTest +reportSourceSetTransformAndroidTest +reportSourceSetTransformAndroidTestDebug +reportSourceSetTransformAndroidTestDebug +reportSourceSetTransformAndroidTestDebug +reportSourceSetTransformAndroidTestDebug +reportSourceSetTransformAndroidTestDebug +reportSourceSetTransformDebug +reportSourceSetTransformDebug +reportSourceSetTransformDebug +reportSourceSetTransformDebug +reportSourceSetTransformDebug +reportSourceSetTransformDev +reportSourceSetTransformDev +reportSourceSetTransformDev +reportSourceSetTransformDev +reportSourceSetTransformDev +reportSourceSetTransformMain +reportSourceSetTransformMain +reportSourceSetTransformMain +reportSourceSetTransformMain +reportSourceSetTransformMain +reportSourceSetTransformRelease +reportSourceSetTransformRelease +reportSourceSetTransformRelease +reportSourceSetTransformRelease +reportSourceSetTransformRelease +reportSourceSetTransformTest +reportSourceSetTransformTest +reportSourceSetTransformTest +reportSourceSetTransformTest +reportSourceSetTransformTest +reportSourceSetTransformTestDebug +reportSourceSetTransformTestDebug +reportSourceSetTransformTestDebug +reportSourceSetTransformTestDebug +reportSourceSetTransformTestDebug +reportSourceSetTransformTestDev +reportSourceSetTransformTestDev +reportSourceSetTransformTestDev +reportSourceSetTransformTestDev +reportSourceSetTransformTestDev +reportSourceSetTransformTestRelease +reportSourceSetTransformTestRelease +reportSourceSetTransformTestRelease +reportSourceSetTransformTestRelease +reportSourceSetTransformTestRelease +resolveConfigAttr +resolveConfigAttr +resolveConfigAttr +resolveConfigAttr +resolveConfigAttr +signDebugBundle +signDevBundle +signingConfigWriterDebug +signingConfigWriterDebugAndroidTest +signingConfigWriterDebugAndroidTest +signingConfigWriterDebugAndroidTest +signingConfigWriterDebugAndroidTest +signingConfigWriterDebugAndroidTest +signingConfigWriterDev +signingConfigWriterRelease +signReleaseBundle +stripDebugDebugSymbols +stripDebugDebugSymbols +stripDebugDebugSymbols +stripDebugDebugSymbols +stripDebugDebugSymbols +stripDevDebugSymbols +stripDevDebugSymbols +stripDevDebugSymbols +stripDevDebugSymbols +stripDevDebugSymbols +stripReleaseDebugSymbols +stripReleaseDebugSymbols +stripReleaseDebugSymbols +stripReleaseDebugSymbols +stripReleaseDebugSymbols +transformClassesAndResourcesWithR8ForRelease +transformClassesAndResourcesWithR8ForRelease +transformClassesAndResourcesWithR8ForRelease +transformClassesAndResourcesWithR8ForRelease +transformClassesAndResourcesWithR8ForRelease +transformClassesAndResourcesWithSyncLibJarsForDebug +transformClassesAndResourcesWithSyncLibJarsForDebug +transformClassesAndResourcesWithSyncLibJarsForDebug +transformClassesAndResourcesWithSyncLibJarsForDebug +transformClassesAndResourcesWithSyncLibJarsForDev +transformClassesAndResourcesWithSyncLibJarsForDev +transformClassesAndResourcesWithSyncLibJarsForDev +transformClassesAndResourcesWithSyncLibJarsForDev +transformClassesAndResourcesWithSyncLibJarsForRelease +transformClassesAndResourcesWithSyncLibJarsForRelease +transformClassesAndResourcesWithSyncLibJarsForRelease +transformClassesAndResourcesWithSyncLibJarsForRelease +transformClassesWithDexBuilderForDebug +transformClassesWithDexBuilderForDebugAndroidTest +transformClassesWithDexBuilderForDebugAndroidTest +transformClassesWithDexBuilderForDebugAndroidTest +transformClassesWithDexBuilderForDebugAndroidTest +transformClassesWithDexBuilderForDebugAndroidTest +transformClassesWithDexBuilderForDev +transformNativeLibsWithIntermediateJniLibsForDebug +transformNativeLibsWithIntermediateJniLibsForDebug +transformNativeLibsWithIntermediateJniLibsForDebug +transformNativeLibsWithIntermediateJniLibsForDebug +transformNativeLibsWithIntermediateJniLibsForDev +transformNativeLibsWithIntermediateJniLibsForDev +transformNativeLibsWithIntermediateJniLibsForDev +transformNativeLibsWithIntermediateJniLibsForDev +transformNativeLibsWithIntermediateJniLibsForRelease +transformNativeLibsWithIntermediateJniLibsForRelease +transformNativeLibsWithIntermediateJniLibsForRelease +transformNativeLibsWithIntermediateJniLibsForRelease +transformNativeLibsWithSyncJniLibsForDebug +transformNativeLibsWithSyncJniLibsForDebug +transformNativeLibsWithSyncJniLibsForDebug +transformNativeLibsWithSyncJniLibsForDebug +transformNativeLibsWithSyncJniLibsForDev +transformNativeLibsWithSyncJniLibsForDev +transformNativeLibsWithSyncJniLibsForDev +transformNativeLibsWithSyncJniLibsForDev +transformNativeLibsWithSyncJniLibsForRelease +transformNativeLibsWithSyncJniLibsForRelease +transformNativeLibsWithSyncJniLibsForRelease +transformNativeLibsWithSyncJniLibsForRelease +validateSigningDebug +validateSigningDebugAndroidTest +validateSigningDebugAndroidTest +validateSigningDebugAndroidTest +validateSigningDebugAndroidTest +validateSigningDebugAndroidTest +validateSigningDev +verifyReleaseResources +verifyReleaseResources +verifyReleaseResources +verifyReleaseResources +writeDebugApplicationId +writeDebugModuleMetadata +writeDevApplicationId +writeDevModuleMetadata +writeReleaseApplicationId +writeReleaseModuleMetadata diff --git a/app/build.gradle b/app/build.gradle index b99eac8..abf4201 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,7 +23,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':core') - implementation project(':features:list') + implementation project(':features:launches') implementation project(':features:detail') implementation libraries.coroutines 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 3ed9830..e120675 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt @@ -1,6 +1,8 @@ package com.melih.rocketscience.di import com.melih.core.di.CoreComponent +import com.melih.detail.di.DetailModule +import com.melih.list.di.LaunchesFeatureModule import com.melih.rocketscience.App import dagger.Component import dagger.android.AndroidInjectionModule @@ -8,7 +10,10 @@ import dagger.android.AndroidInjector @AppScope @Component( - modules = [AndroidInjectionModule::class, AppModule::class], + modules = [AndroidInjectionModule::class, + LaunchesFeatureModule::class, + DetailModule::class], + dependencies = [CoreComponent::class] ) interface AppComponent : AndroidInjector { diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt deleted file mode 100644 index c80bd10..0000000 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.melih.rocketscience.di - -import com.melih.detail.di.DetailContributor -import com.melih.detail.ui.DetailActivity -import com.melih.list.di.LaunchesContributor -import com.melih.list.ui.LaunchesActivity -import dagger.Module -import dagger.android.ContributesAndroidInjector - -@Module -abstract class AppModule { - - @ContributesAndroidInjector( - modules = [ - LaunchesContributor::class - ] - ) - abstract fun launchesActivity(): LaunchesActivity - - @ContributesAndroidInjector( - modules = [ - DetailContributor::class - ] - ) - abstract fun detailActivity(): DetailActivity -} diff --git a/build.gradle b/build.gradle index 30e23e9..2f4bf76 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.40' + ext.kotlin_version = '1.3.41' ext.nav_version = '2.1.0-alpha06' repositories { @@ -9,10 +9,10 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0-beta05' + classpath 'com.android.tools.build:gradle:3.5.0-rc01' 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.4.2.1" + classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.0.0" 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 @@ -22,6 +22,7 @@ buildscript { plugins { id "io.gitlab.arturbosch.detekt" version "1.0.0-RC14" id "org.jetbrains.dokka" version "0.9.18" + id "jacoco" } allprojects { @@ -159,4 +160,9 @@ task projectDependencyGraph { } } +jacoco { + toolVersion = "0.8.4" + reportsDir = file("reports/jacoco") +} + apply from: "scripts/dependencies.gradle" diff --git a/core/jacoco.exec b/core/jacoco.exec new file mode 100644 index 0000000..45a3fec Binary files /dev/null and b/core/jacoco.exec differ 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 index 33f8285..ba672c4 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt @@ -8,7 +8,6 @@ import androidx.databinding.ViewDataBinding import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.NavigationUI import dagger.android.support.DaggerAppCompatActivity -import kotlinx.coroutines.ExperimentalCoroutinesApi const val NAV_HOST_FRAGMENT_TAG = "nav_host_fragment_tag" @@ -20,7 +19,6 @@ abstract class BaseActivity : DaggerAppCompatActivity() { protected lateinit var binding: T protected lateinit var navHostFragment: NavHostFragment - @ExperimentalCoroutinesApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 7dce574..4224e72 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 @@ -12,7 +12,6 @@ import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import com.google.android.material.snackbar.Snackbar import com.melih.repository.interactors.base.Reason -import kotlinx.coroutines.ExperimentalCoroutinesApi /** * Parent of all fragments. @@ -41,7 +40,6 @@ abstract class BaseFragment : Fragment() { return binding.root } - @ExperimentalCoroutinesApi protected fun showSnackbarWithAction(reason: Reason, block: () -> Unit) { Snackbar.make( binding.root, 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 new file mode 100644 index 0000000..47b6126 --- /dev/null +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt @@ -0,0 +1,140 @@ +package com.melih.core.base.paging + +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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +const val INITIAL_PAGE = 0 + +/** + * Base class for all [pageKeyedDataSources][PageKeyedDataSource] in project. + * + * Purpose of this class is to ease handling of [result][Result]. It overrides [loadInitial], [loadAfter] and [loadBefore] + * so sources extends from base does not need to override them, they just need to provide way of loading data by overriding [loadDataForPage]. + * + * [handleState] & [handleFailure] updates corresponding [liveData][LiveData] objects [stateData] & [reasonData], + * which can be used with [androidx.lifecycle.Transformations] to observe changes on the source state & error. + * + * This source has it's own [coroutineScope][CoroutineScope] that's backed up by a [SupervisorJob] to handle networking operations. + * It's cancelled automatically when source factory [invalidates][invalidate] the source. + */ + +@UseExperimental(ExperimentalCoroutinesApi::class) +abstract class BasePagingDataSource : PageKeyedDataSource() { + + // region Abstractions + + abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s) + // endregion + + // region Properties + + private val _stateData = MutableLiveData() + private val _reasonData = MutableLiveData() + private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + /** + * Observe [stateData] to get notified of state of data + */ + val stateData: LiveData + get() = _stateData + + /** + * Observe [reasonData] to get notified if an error occurs + */ + val reasonData: LiveData + get() = _reasonData + // endregion + + // region Functions + + 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 -> + result.onState(::handleState) + .onFailure(::handleFailure) + .onSuccess { + // When we receive data without any failures, we transform it and return list, also what's the value for next page + callback.onResult( + it, + INITIAL_PAGE, + INITIAL_PAGE + 1 + ) + } + } + .launchIn(coroutineScope) + } + + override fun loadAfter(params: LoadParams, callback: LoadCallback) { + // Key for which page to load is in params + val page = params.key + + loadDataForPage(page) + .onEach { result -> + result + .onState(::handleState) + .onFailure(::handleFailure) + .onSuccess { + // When we receive data without any failures, we transform it and return list, also what's the value for next page + callback.onResult( + it, + page + 1 + ) + } + } + .launchIn(coroutineScope) + } + + /** + * 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) { + // no-op + } + + /** + * Default state handler which assigns given [state] to [stateData] + * + * @param state state of operation + */ + @CallSuper + protected fun handleState(state: State) { + _stateData.value = state + } + + /** + * Default error handler which assign received [reason] to [reasonData] + * + * @param reason check [Reason] class for possible error types + */ + @CallSuper + protected fun handleFailure(reason: Reason) { + _reasonData.value = reason + } + + /** + * Canceling [coroutineScope] + */ + @CallSuper + override fun invalidate() { + coroutineScope.cancel() + super.invalidate() + } + // 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 new file mode 100644 index 0000000..34a6ea3 --- /dev/null +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt @@ -0,0 +1,42 @@ +package com.melih.core.base.paging + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.paging.DataSource + +/** + * Base [factory][DataSource.Factory] class for any [dataSource][DataSource]s in project. + * + * It's purpose is to provide latest source so [basePagingViewModel][be.mediahuis.core.base.viewmodel.BasePagingViewModel] can obtain + * [stateData][BasePagingDataSource.stateData] and [reasonData][BasePagingDataSource.reasonData] from it. + * + * This is done under the hood by telling this factory how to create a source by overriding [createSource]. + * + * Purpose of this transmission is to encapuslate [basePagingDataSource][BasePagingDataSource]. + */ +abstract class BasePagingFactory : DataSource.Factory() { + + // region Abstractions + + abstract fun createSource(): BasePagingDataSource + // endregion + + // region Properties + + private val _currentSource = MutableLiveData>() + + val currentSource: LiveData> + get() = _currentSource + // endregion + + // region Functions + + override fun create(): DataSource = createSource().apply { _currentSource.postValue(this) } + + /** + * Invalidating the [currentSource] + * by calling [be.mediahuis.core.base.paging.BasePagingDataSource.invalidate] + */ + fun invalidateDataSource() = currentSource.value?.invalidate() + // endregion +} diff --git a/core/src/main/kotlin/com/melih/core/base/recycler/BaseListAdapter.kt b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt similarity index 89% rename from core/src/main/kotlin/com/melih/core/base/recycler/BaseListAdapter.kt rename to core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt index 8ec796b..e3ec4bc 100644 --- a/core/src/main/kotlin/com/melih/core/base/recycler/BaseListAdapter.kt +++ b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt @@ -3,8 +3,8 @@ package com.melih.core.base.recycler import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.ViewDataBinding +import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView /** @@ -12,10 +12,10 @@ import androidx.recyclerview.widget.RecyclerView * * */ -abstract class BaseListAdapter( +abstract class BasePagingListAdapter( callback: DiffUtil.ItemCallback, - private val clickListener: (T) -> Unit -) : ListAdapter>(callback) { + private val clickListener: (T?) -> Unit +) : PagedListAdapter>(callback) { private var itemClickListener: ((T) -> Unit)? = null @@ -48,12 +48,12 @@ abstract class BaseListAdapter( override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { val item = getItem(position) + holder.itemView.setOnClickListener { clickListener(item) } holder.bind(item) - } } @@ -68,5 +68,5 @@ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHo * @param item entity * @param position position from adapter */ - abstract fun bind(item: T) + abstract fun bind(item: T?) } 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 new file mode 100644 index 0000000..57da05b --- /dev/null +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt @@ -0,0 +1,70 @@ +package com.melih.core.base.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import androidx.lifecycle.ViewModel +import androidx.paging.PagedList +import androidx.paging.toLiveData +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]. + * + * Since data handling is done via [be.mediahuis.core.base.paging.BasePagingDataSource], this view model doesn't need + * a [kotlinx.coroutines.channels.ReceiveChannel] and will not provide any default operations of data, but instead will + * provde [pagedList] which should be observed and submitted. + * + * If paging won't be used, use [BaseViewModel] instead. + */ +abstract class BasePagingViewModel : ViewModel() { + + // region Abstractions + + abstract val factory: BasePagingFactory + abstract val config: PagedList.Config + // endregion + + // region Properties + + /** + * Observe [stateData] to get notified of state of data + */ + val stateData: LiveData by lazy { + Transformations.switchMap(factory.currentSource) { + it.stateData + } + } + + /** + * Observe [errorData] to get notified if an error occurs + */ + val errorData: LiveData by lazy { + Transformations.switchMap(factory.currentSource) { + it.reasonData + } + } + + /** + * Observe [pagedList] to submit list it provides + */ + val pagedList: LiveData> by lazy { + factory.toLiveData(config) + } + // endregion + + // region Functions + + fun refresh() { + factory.currentSource.value?.invalidate() + } + + /** + * Retry loading data, incase there's difference between refresh and retry, should go here + */ + fun retry() { + factory.currentSource.value?.invalidate() + } + // 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 d30406b..2e3246b 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,8 +3,10 @@ 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.Result +import com.melih.repository.interactors.base.State +import kotlinx.coroutines.launch /** * Base [ViewModel] for view models that will process data. @@ -15,13 +17,19 @@ abstract class BaseViewModel : ViewModel() { // region Abstractions - abstract fun loadData() + abstract suspend fun loadData() // endregion + init { + viewModelScope.launch { + loadData() + } + } + // region Properties private val _successData = MutableLiveData() - private val _stateData = MutableLiveData() + private val _stateData = MutableLiveData() private val _errorData = MutableLiveData() /** @@ -33,7 +41,7 @@ abstract class BaseViewModel : ViewModel() { /** * Observe [stateData] to get notified of state of data */ - val stateData: LiveData + val stateData: LiveData get() = _stateData /** @@ -59,7 +67,7 @@ abstract class BaseViewModel : ViewModel() { * * @param state state of operation */ - protected fun handleState(state: Result.State) { + protected fun handleState(state: State) { _stateData.value = state } @@ -76,14 +84,18 @@ abstract class BaseViewModel : ViewModel() { * Reload data */ fun refresh() { - loadData() + viewModelScope.launch { + loadData() + } } /** * Retry loading data, incase there's difference between refresh and retry, should go here */ fun retry() { - loadData() + viewModelScope.launch { + loadData() + } } // endregion } diff --git a/core/src/main/kotlin/com/melih/core/di/CoreComponent.kt b/core/src/main/kotlin/com/melih/core/di/CoreComponent.kt index 859796c..bc49abc 100644 --- a/core/src/main/kotlin/com/melih/core/di/CoreComponent.kt +++ b/core/src/main/kotlin/com/melih/core/di/CoreComponent.kt @@ -1,8 +1,8 @@ package com.melih.core.di import android.app.Application +import android.content.Context import android.net.NetworkInfo -import com.melih.repository.persistence.LaunchesDatabase import dagger.BindsInstance import dagger.Component import javax.inject.Singleton @@ -11,9 +11,9 @@ import javax.inject.Singleton @Component(modules = [CoreModule::class]) interface CoreComponent { - fun getNetworkInfo(): NetworkInfo? + fun getAppContext(): Context - fun getLaunchesDatabase(): LaunchesDatabase + fun getNetworkInfo(): NetworkInfo? @Component.Factory interface Factory { 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 1867c58..bc8aab2 100644 --- a/core/src/main/kotlin/com/melih/core/di/CoreModule.kt +++ b/core/src/main/kotlin/com/melih/core/di/CoreModule.kt @@ -4,23 +4,16 @@ import android.app.Application import android.content.Context import android.net.ConnectivityManager import android.net.NetworkInfo -import androidx.room.Room -import com.melih.repository.persistence.DB_NAME -import com.melih.repository.persistence.LaunchesDatabase import dagger.Module import dagger.Provides -import javax.inject.Singleton @Module class CoreModule { @Provides - fun provideNetworkInfo(app: Application): NetworkInfo? = - (app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo + fun proivdeAppContext(app: Application): Context = app.applicationContext @Provides - @Singleton - fun provideLaunchesDatabase(app: Application) = - Room.databaseBuilder(app.applicationContext, LaunchesDatabase::class.java, DB_NAME) - .build() + fun provideNetworkInfo(app: Application): NetworkInfo? = + (app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo } diff --git a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt b/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt index 0b69ae2..c57840f 100644 --- a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt +++ b/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt @@ -1,5 +1,6 @@ package com.melih.core.extensions +import android.view.MenuItem import androidx.appcompat.widget.SearchView import com.melih.core.utils.ClearFocusQueryTextListener @@ -8,4 +9,24 @@ import com.melih.core.utils.ClearFocusQueryTextListener */ 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/kotlin/com/melih/core/utils/SnackbarBehaviour.kt b/core/src/main/kotlin/com/melih/core/utils/SnackbarBehaviour.kt index 1bd4a28..26291ae 100644 --- a/core/src/main/kotlin/com/melih/core/utils/SnackbarBehaviour.kt +++ b/core/src/main/kotlin/com/melih/core/utils/SnackbarBehaviour.kt @@ -9,6 +9,7 @@ import com.google.android.material.snackbar.Snackbar /** * Simple behaviour for pushing views when snackbar is animating so none of views will remain under snackbar */ +@Suppress("UNUSED_PARAMETER") class SnackbarBehaviour constructor( context: Context, attributeSet: AttributeSet diff --git a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt index 203a012..d8d68fb 100644 --- a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt +++ b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt @@ -6,19 +6,19 @@ import androidx.lifecycle.LiveData import com.melih.core.observers.OneShotObserverWithLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -import java.util.concurrent.Executors import kotlin.coroutines.suspendCoroutine +@UseExperimental(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + private val dispatcher = TestCoroutineDispatcher() @BeforeEach - @ExperimentalCoroutinesApi fun setUp() { Dispatchers.setMain(dispatcher) ArchTaskExecutor.getInstance() @@ -32,12 +32,12 @@ abstract class BaseTestWithMainThread { } @AfterEach - @ExperimentalCoroutinesApi fun tearDown() { - Dispatchers.resetMain() - dispatcher.close() ArchTaskExecutor.getInstance() .setDelegate(null) + + Dispatchers.resetMain() + dispatcher.cleanupTestCoroutines() } } 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 4436517..b195388 100644 --- a/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt +++ b/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt @@ -1,37 +1,32 @@ package com.melih.core.base +import com.melih.core.BaseTestWithMainThread import com.melih.core.base.viewmodel.BaseViewModel +import io.mockk.coVerify import io.mockk.spyk -import io.mockk.verify import org.junit.jupiter.api.Test -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class BaseViewModelTest { - - val baseVm = spyk(TestViewModel()) +class BaseViewModelTest : BaseTestWithMainThread() { @Test fun `refresh should invoke loadData`() { + val baseVm = spyk(TestViewModel()) baseVm.refresh() - verify(exactly = 1) { baseVm.loadData() } + coVerify(exactly = 1) { baseVm.loadData() } } @Test fun `retry should invoke loadData`() { + val baseVm = spyk(TestViewModel()) baseVm.retry() - verify(exactly = 1) { baseVm.loadData() } + coVerify(exactly = 1) { baseVm.loadData() } } } -class TestViewModel : BaseViewModel() { - override public fun loadData() { +class TestViewModel : BaseViewModel() { + override suspend fun loadData() { // no - op } - } diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt new file mode 100644 index 0000000..5750ad1 --- /dev/null +++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt @@ -0,0 +1,166 @@ +@file:UseExperimental(ExperimentalCoroutinesApi::class) + +package com.melih.core.paging + +import androidx.paging.PageKeyedDataSource +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 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class BasePagingDataSourceTest : BaseTestWithMainThread() { + + val source = spyk(TestSource()) + val failureSource = spyk(TestFailureSource()) + + val data = 10 + val errorMessage = "Generic Error" + + @Nested + inner class BasePagingSource { + + @Nested + inner class LoadInitial { + + @Test + + fun `should update state accordingly`() { + val params = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) + + runBlocking { + + // Fake loading + source.loadInitial(params, callback) + + source.stateData.testObserve { + it shouldBeInstanceOf State.Loading::class + } + } + } + + @Test + + fun `should update error Error accordingly`() { + val params = PageKeyedDataSource.LoadInitialParams(10, false) + val callback = mockk>(relaxed = true) + + runBlocking { + + // Fake loading + failureSource.loadInitial(params, callback) + + failureSource.reasonData.testObserve { + it shouldBeInstanceOf GenericError::class + } + } + } + } + + @Nested + inner class LoadAfter { + + @Test + + fun `should update state accordingly`() { + val params = PageKeyedDataSource.LoadParams(2, 10) + val callback = mockk>(relaxed = true) + + runBlocking { + + // Fake loading + source.loadAfter(params, callback) + + source.stateData.testObserve { + it shouldBeInstanceOf State.Loading::class + } + } + } + + @Test + + fun `should update error Error accordingly`() { + val params = PageKeyedDataSource.LoadParams(2, 10) + val callback = mockk>(relaxed = true) + + runBlocking { + + // Fake loading + failureSource.loadAfter(params, callback) + + failureSource.reasonData.testObserve { + it shouldBeInstanceOf GenericError::class + } + } + } + } + + @Test + + fun `should use loadDataForPage in loadInitial and transform emmited value`() { + val params = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) + + // Fake loading + source.loadInitial(params, callback) + + // Make sure load initial called only once + verify(exactly = 1) { source.loadDataForPage(any()) } + + // Notified callback + verify(exactly = 1) { callback.onResult(any(), any(), any()) } + } + + @Test + + fun `should use loadDataForPage in loadAfter and transform emmited value`() { + val params = PageKeyedDataSource.LoadParams(2, 10) + val callback = mockk>(relaxed = true) + + // Fake loading + source.loadAfter(params, callback) + + // Make sure load initial called only once + verify(exactly = 1) { source.loadDataForPage(any()) } + + // Notified callback + verify(exactly = 1) { callback.onResult(any(), any()) } + } + } + + inner class TestSource : BasePagingDataSource() { + + + val result = flow { + emit(State.Loading()) + emit(Success(listOf(data))) + } + + + override fun loadDataForPage(page: Int): Flow>> = result + } + + inner class TestFailureSource : BasePagingDataSource() { + + val result = flow { + emit(State.Loading()) + emit(Failure(GenericError())) + } + + override fun loadDataForPage(page: Int): Flow>> = result + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt new file mode 100644 index 0000000..5829189 --- /dev/null +++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt @@ -0,0 +1,33 @@ +package com.melih.core.paging + +import com.melih.core.BaseTestWithMainThread +import com.melih.core.base.paging.BasePagingDataSource +import com.melih.core.base.paging.BasePagingFactory +import com.melih.core.testObserve +import io.mockk.mockk +import io.mockk.spyk +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.shouldEqual +import org.junit.jupiter.api.Test + +class BasePagingFactoryTest : BaseTestWithMainThread() { + + val factory = spyk(TestFactory()) + + @Test + fun `create should update current source when it creates a new one`() { + val source = factory.create() + + runBlocking { + factory.currentSource.testObserve { + it shouldEqual source + } + } + } + + inner class TestFactory : BasePagingFactory() { + + override fun createSource(): BasePagingDataSource = mockk(relaxed = true) + + } +} diff --git a/docs/dependency_hierarchy.png b/docs/dependency_hierarchy.png new file mode 100644 index 0000000..3cd5045 Binary files /dev/null and b/docs/dependency_hierarchy.png differ diff --git a/docs/module_graph.png b/docs/module_graph.png new file mode 100644 index 0000000..93bab79 Binary files /dev/null and b/docs/module_graph.png differ diff --git a/features/detail/build.gradle b/features/detail/build.gradle index e380aa6..0b93486 100644 --- a/features/detail/build.gradle +++ b/features/detail/build.gradle @@ -15,8 +15,5 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation testLibraries.jUnitApi - testImplementation testLibraries.mockk - testImplementation testLibraries.kluent testImplementation testLibraries.coroutinesTest } diff --git a/features/detail/jacoco.exec b/features/detail/jacoco.exec new file mode 100644 index 0000000..b1922d8 Binary files /dev/null and b/features/detail/jacoco.exec differ 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 22a2c54..ff9261e 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,7 +1,9 @@ package com.melih.detail.di import com.melih.detail.di.modules.DetailBinds +import com.melih.detail.di.modules.DetailProvides import com.melih.detail.ui.DetailFragment +import com.melih.list.di.scopes.DetailFragmentScope import dagger.Module import dagger.android.ContributesAndroidInjector @@ -15,9 +17,11 @@ abstract class DetailContributor { @ContributesAndroidInjector( modules = [ - DetailBinds::class + DetailBinds::class, + DetailProvides::class ] ) + @DetailFragmentScope abstract fun detailFragment(): DetailFragment // endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailModule.kt new file mode 100644 index 0000000..eab7f69 --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/di/DetailModule.kt @@ -0,0 +1,24 @@ +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 DetailModule { + + // 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/DetailBinds.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailBinds.kt index 578d247..934faa2 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailBinds.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailBinds.kt @@ -6,7 +6,6 @@ import com.melih.detail.ui.DetailViewModel import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap -import kotlinx.coroutines.ExperimentalCoroutinesApi @Module abstract class DetailBinds { @@ -16,7 +15,6 @@ abstract class DetailBinds { @Binds @IntoMap @ViewModelKey(DetailViewModel::class) - @ExperimentalCoroutinesApi abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel // endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailProvides.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailProvides.kt new file mode 100644 index 0000000..3ee52f0 --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailProvides.kt @@ -0,0 +1,21 @@ +package com.melih.detail.di.modules + +import androidx.navigation.fragment.navArgs +import com.melih.detail.ui.DetailFragment +import com.melih.detail.ui.DetailFragmentArgs +import com.melih.repository.interactors.GetLaunchDetails +import dagger.Module +import dagger.Provides + +@Module +class DetailProvides { + + /** + * Provides launch detail params + */ + @Provides + fun provideGetLaunchDetailParams(fragment: DetailFragment): GetLaunchDetails.Params { + val args: DetailFragmentArgs 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 new file mode 100644 index 0000000..c5a983b --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt @@ -0,0 +1,7 @@ +package com.melih.list.di.scopes + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.BINARY) +annotation class DetailFragmentScope 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 new file mode 100644 index 0000000..e264fde --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt @@ -0,0 +1,7 @@ +package com.melih.list.di.scopes + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.BINARY) +annotation class DetailScope 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 index 519c20e..0e7fbae 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt @@ -6,7 +6,6 @@ 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 -import kotlinx.coroutines.ExperimentalCoroutinesApi const val INVALID_LAUNCH_ID = -1L @@ -14,13 +13,12 @@ class DetailActivity : BaseActivity() { // region Functions - @ExperimentalCoroutinesApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true); - supportActionBar?.setDisplayShowHomeEnabled(true); + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) } override fun getLayoutId(): Int = R.layout.activity_detail 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 c7bdfe6..b9a9ca5 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,38 +3,28 @@ package com.melih.detail.ui import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.view.View -import androidx.navigation.fragment.navArgs 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 -import kotlinx.coroutines.ExperimentalCoroutinesApi -import timber.log.Timber class DetailFragment : BaseDaggerFragment() { // region Properties - private val args: DetailFragmentArgs by navArgs() - - @ExperimentalCoroutinesApi private val viewModel: DetailViewModel get() = viewModelFactory.createFor(this) // endregion // region Functions - @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.tvDescription.movementMethod = ScrollingMovementMethod() binding.viewModel = viewModel - viewModel.createParamsFor(args.launchId) - viewModel.loadData() - // Observing error to show toast with retry action observe(viewModel.errorData) { showSnackbarWithAction(it) { 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 ad17cbb..83c28dd 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,24 +1,20 @@ package com.melih.detail.ui import androidx.lifecycle.Transformations -import androidx.lifecycle.viewModelScope import com.melih.core.base.viewmodel.BaseViewModel import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.GetLaunchDetails -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.melih.repository.interactors.base.handle import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import javax.inject.Inject -@ExperimentalCoroutinesApi class DetailViewModel @Inject constructor( - private val getLaunchDetails: GetLaunchDetails + private val getLaunchDetails: GetLaunchDetails, + private val getLaunchDetailsParams: GetLaunchDetails.Params ) : BaseViewModel() { // region Properties - private var params = GetLaunchDetails.Params(INVALID_LAUNCH_ID) - val rocketName = Transformations.map(successData) { it.rocket.name } @@ -38,18 +34,12 @@ class DetailViewModel @Inject constructor( // region Functions - fun createParamsFor(id: Long) { - params = GetLaunchDetails.Params(id) - } - /** * Triggering interactor in view model scope */ - override fun loadData() { - viewModelScope.launch { - getLaunchDetails(params).collect { - it.handle(::handleState, ::handleFailure, ::handleSuccess) - } + override suspend fun loadData() { + getLaunchDetails(getLaunchDetailsParams).collect { + it.handle(::handleState, ::handleFailure, ::handleSuccess) } } // endregion 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 94e3614..82056d4 100644 --- a/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt +++ b/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt @@ -1,4 +1,4 @@ -package com.melih.list +package com.melih.detail import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -8,19 +8,17 @@ import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach +@UseExperimental(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { - @ExperimentalCoroutinesApi protected val dispatcher = TestCoroutineDispatcher() @BeforeEach - @ExperimentalCoroutinesApi fun setUp() { Dispatchers.setMain(dispatcher) } @AfterEach - @ExperimentalCoroutinesApi fun tearDown() { Dispatchers.resetMain() dispatcher.cleanupTestCoroutines() 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 0e6a4d4..69ad7c9 100644 --- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt +++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt @@ -1,7 +1,6 @@ package com.melih.detail import com.melih.detail.ui.DetailViewModel -import com.melih.list.BaseTestWithMainThread import com.melih.repository.interactors.GetLaunchDetails import io.mockk.mockk import io.mockk.slot @@ -17,21 +16,20 @@ 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 getLaunchDetailsParams = GetLaunchDetails.Params(1013) - @ExperimentalCoroutinesApi - private val viewModel = spyk(DetailViewModel(getLaunchDetails)) + private val viewModel = spyk(DetailViewModel(getLaunchDetails, getLaunchDetailsParams)) @Test - @ExperimentalCoroutinesApi fun `loadData should invoke getLauchDetails with provided params`() { dispatcher.runBlockingTest { val paramsSlot = slot() - viewModel.createParamsFor(1013) viewModel.loadData() // init should have called it already due to creation above diff --git a/features/list/.gitignore b/features/launches/.gitignore similarity index 100% rename from features/list/.gitignore rename to features/launches/.gitignore diff --git a/features/list/build.gradle b/features/launches/build.gradle similarity index 68% rename from features/list/build.gradle rename to features/launches/build.gradle index df7af5b..1128480 100644 --- a/features/list/build.gradle +++ b/features/launches/build.gradle @@ -7,8 +7,7 @@ apply from: "$rootProject.projectDir/scripts/feature_module.gradle" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation testLibraries.jUnitApi - testImplementation testLibraries.mockk - testImplementation testLibraries.kluent + implementation libraries.paging + testImplementation testLibraries.coroutinesTest } diff --git a/features/list/consumer-rules.pro b/features/launches/consumer-rules.pro similarity index 100% rename from features/list/consumer-rules.pro rename to features/launches/consumer-rules.pro diff --git a/features/launches/jacoco.exec b/features/launches/jacoco.exec new file mode 100644 index 0000000..65d2458 Binary files /dev/null and b/features/launches/jacoco.exec differ diff --git a/features/list/proguard-rules.pro b/features/launches/proguard-rules.pro similarity index 100% rename from features/list/proguard-rules.pro rename to features/launches/proguard-rules.pro diff --git a/features/list/sampledata/launches.json b/features/launches/sampledata/launches.json similarity index 100% rename from features/list/sampledata/launches.json rename to features/launches/sampledata/launches.json diff --git a/features/list/src/main/AndroidManifest.xml b/features/launches/src/main/AndroidManifest.xml similarity index 100% rename from features/list/src/main/AndroidManifest.xml rename to features/launches/src/main/AndroidManifest.xml diff --git a/features/list/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt similarity index 79% rename from features/list/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt rename to features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt index a3065ee..9ab2f11 100644 --- a/features/list/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt @@ -2,6 +2,7 @@ package com.melih.list.di import com.melih.list.di.modules.LaunchesBinds import com.melih.list.di.modules.LaunchesProvides +import com.melih.list.di.scopes.LaunchesFragmentScope import com.melih.list.ui.LaunchesFragment import dagger.Module import dagger.android.ContributesAndroidInjector @@ -20,6 +21,7 @@ abstract class LaunchesContributor { LaunchesBinds::class ] ) - abstract fun listFragment(): LaunchesFragment + @LaunchesFragmentScope + abstract fun launchesFragment(): LaunchesFragment // 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 new file mode 100644 index 0000000..68ae1be --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt @@ -0,0 +1,24 @@ +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/list/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt similarity index 76% rename from features/list/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt rename to features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt index 35a77a4..a3a66c2 100644 --- a/features/list/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesBinds.kt @@ -2,11 +2,10 @@ package com.melih.list.di.modules import androidx.lifecycle.ViewModel import com.melih.core.di.keys.ViewModelKey -import com.melih.list.ui.LaunchesViewModel +import com.melih.list.ui.vm.LaunchesViewModel import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap -import kotlinx.coroutines.ExperimentalCoroutinesApi @Module abstract class LaunchesBinds { @@ -16,7 +15,6 @@ abstract class LaunchesBinds { @Binds @IntoMap @ViewModelKey(LaunchesViewModel::class) - @ExperimentalCoroutinesApi abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel // endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesProvides.kt b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesProvides.kt new file mode 100644 index 0000000..59cff00 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesProvides.kt @@ -0,0 +1,24 @@ +package com.melih.list.di.modules + +import androidx.paging.Config +import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT +import com.melih.repository.interactors.GetLaunches +import dagger.Module +import dagger.Provides + +@Module +class LaunchesProvides { + + /** + * Provides lauches, using default value of 15 + */ + @Provides + fun provideGetLaunchesParams() = GetLaunches.Params(page = 0) + + @Provides + fun getPagingConfig() = Config( + DEFAULT_LAUNCHES_AMOUNT, + prefetchDistance = 2, + enablePlaceholders = false + ) +} diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt b/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt new file mode 100644 index 0000000..e5b898b --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt @@ -0,0 +1,7 @@ +package com.melih.list.di.scopes + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.BINARY) +annotation class LaunchesFragmentScope diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt b/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt new file mode 100644 index 0000000..31b4887 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt @@ -0,0 +1,7 @@ +package com.melih.list.di.scopes + +import javax.inject.Scope + +@Scope +@Retention(AnnotationRetention.BINARY) +annotation class LaunchesScope diff --git a/features/list/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt similarity index 89% rename from features/list/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt rename to features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt index 3043883..529b4ae 100644 --- a/features/list/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt @@ -5,13 +5,11 @@ import androidx.navigation.fragment.NavHostFragment import com.melih.core.base.lifecycle.BaseActivity import com.melih.list.R import com.melih.list.databinding.LaunchesActivityBinding -import kotlinx.coroutines.ExperimentalCoroutinesApi class LaunchesActivity : BaseActivity() { // region Functions - @ExperimentalCoroutinesApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 new file mode 100644 index 0000000..8f27757 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt @@ -0,0 +1,97 @@ +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 ?: -1L)) + } + + //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/list/src/main/kotlin/com/melih/list/ui/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt similarity index 73% rename from features/list/src/main/kotlin/com/melih/list/ui/LaunchesAdapter.kt rename to features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt index a47c3e5..1547ef5 100644 --- a/features/list/src/main/kotlin/com/melih/list/ui/LaunchesAdapter.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt @@ -1,14 +1,14 @@ -package com.melih.list.ui +package com.melih.list.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil -import com.melih.core.base.recycler.BaseListAdapter +import com.melih.core.base.recycler.BasePagingListAdapter import com.melih.core.base.recycler.BaseViewHolder import com.melih.list.databinding.LaunchRowBinding import com.melih.repository.entities.LaunchEntity -class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BaseListAdapter( +class LaunchesAdapter(itemClickListener: (LaunchEntity?) -> Unit) : BasePagingListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: LaunchEntity, newItem: LaunchEntity): Boolean = oldItem.id == newItem.id @@ -30,11 +30,11 @@ class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BaseListAdapt class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) { - override fun bind(item: LaunchEntity) { + override fun bind(item: LaunchEntity?) { binding.entity = item - val missions = item.missions - binding.tvDescription.text = if (missions.isNotEmpty()) missions[0].description else "" + 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 new file mode 100644 index 0000000..9ec0bf8 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt @@ -0,0 +1,26 @@ +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/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt new file mode 100644 index 0000000..d176076 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt @@ -0,0 +1,14 @@ +package com.melih.list.ui.paging + +import com.melih.core.base.paging.BasePagingDataSource +import com.melih.core.base.paging.BasePagingFactory +import com.melih.repository.entities.LaunchEntity +import javax.inject.Inject +import javax.inject.Provider + +class LaunchesPagingSourceFactory @Inject constructor( + private val sourceProvider: Provider +) : BasePagingFactory() { + + override fun createSource(): BasePagingDataSource = sourceProvider.get() +} 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 new file mode 100644 index 0000000..4576d9c --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt @@ -0,0 +1,45 @@ +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/list/src/main/res/anim/item_enter.xml b/features/launches/src/main/res/anim/item_enter.xml similarity index 100% rename from features/list/src/main/res/anim/item_enter.xml rename to features/launches/src/main/res/anim/item_enter.xml diff --git a/features/list/src/main/res/anim/layout_item_enter.xml b/features/launches/src/main/res/anim/layout_item_enter.xml similarity index 100% rename from features/list/src/main/res/anim/layout_item_enter.xml rename to features/launches/src/main/res/anim/layout_item_enter.xml diff --git a/features/list/src/main/res/layout/activity_launches.xml b/features/launches/src/main/res/layout/activity_launches.xml similarity index 100% rename from features/list/src/main/res/layout/activity_launches.xml rename to features/launches/src/main/res/layout/activity_launches.xml diff --git a/features/list/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml similarity index 96% rename from features/list/src/main/res/layout/fragment_launches.xml rename to features/launches/src/main/res/layout/fragment_launches.xml index c70a7c6..cac321d 100644 --- a/features/list/src/main/res/layout/fragment_launches.xml +++ b/features/launches/src/main/res/layout/fragment_launches.xml @@ -8,7 +8,7 @@ + type="com.melih.list.ui.vm.LaunchesViewModel" /> (), SwipeRefreshLayout.OnRefreshListener { - - // region Properties - - @ExperimentalCoroutinesApi - private val viewModel: LaunchesViewModel - get() = viewModelFactory.createFor(this) - - private val launchesAdapter = LaunchesAdapter(::onItemSelected) - private val itemList = mutableListOf() - // endregion - - // region Functions - - @ExperimentalCoroutinesApi - 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) - setSearchQueryListener((menu.findItem(R.id.search).actionView as SearchView)) - super.onCreateOptionsMenu(menu, inflater) - } - - @ExperimentalCoroutinesApi - private fun observeDataChanges() { - - // Observing state to show loading - observe(viewModel.stateData) { - binding.swipeRefreshLayout.isRefreshing = it is Result.State.Loading - } - - // Observing error to show toast with retry action - observe(viewModel.errorData) { - showSnackbarWithAction(it) { - viewModel.retry() - } - } - - observe(viewModel.successData) { - itemList.addAll(it) - launchesAdapter.submitList(itemList) - binding.rocketList.scheduleLayoutAnimation() - } - } - - private fun onItemSelected(item: LaunchEntity) { - startActivity(Actions.openDetailFor(item.id)) - } - - private fun setSearchQueryListener(searchView: SearchView) { - searchView.setOnQueryChangedListener { - filterItemListBy(it) - } - } - - private fun filterItemListBy(query: String?) = - if (!query.isNullOrBlank()) { - itemList.filter { - it.rocket.name.containsIgnoreCase(query) || it.missions.any { it.description.containsIgnoreCase(query) } - } - } else { - itemList - } - - @ExperimentalCoroutinesApi - override fun onRefresh() { - viewModel.refresh() - } - - override fun getLayoutId(): Int = R.layout.fragment_launches - // endregion -} diff --git a/features/list/src/main/kotlin/com/melih/list/ui/LaunchesViewModel.kt b/features/list/src/main/kotlin/com/melih/list/ui/LaunchesViewModel.kt deleted file mode 100644 index 4f05f86..0000000 --- a/features/list/src/main/kotlin/com/melih/list/ui/LaunchesViewModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.melih.list.ui - -import androidx.lifecycle.viewModelScope -import com.melih.core.base.viewmodel.BaseViewModel -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.GetLaunches -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import javax.inject.Inject - -@ExperimentalCoroutinesApi -class LaunchesViewModel @Inject constructor( - private val getLaunches: GetLaunches, - private val getLaunchesParams: GetLaunches.Params -) : BaseViewModel>() { - - // region Initialization - - init { - loadData() - } - // endregion - - // region Functions - - /** - * Triggering interactor in view model scope - */ - override fun loadData() { - viewModelScope.launch { - getLaunches(getLaunchesParams).collect { - it.handle(::handleState, ::handleFailure, ::handleSuccess) - } - } - } - // endregion -} diff --git a/features/list/src/test/kotlin/com/melih/list/LaunchesViewModelTest.kt b/features/list/src/test/kotlin/com/melih/list/LaunchesViewModelTest.kt deleted file mode 100644 index 7a28959..0000000 --- a/features/list/src/test/kotlin/com/melih/list/LaunchesViewModelTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.melih.list - -import com.melih.list.ui.LaunchesViewModel -import com.melih.repository.interactors.GetLaunches -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.junit.jupiter.api.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class LaunchesViewModelTest : BaseTestWithMainThread() { - - val getLaunches: GetLaunches = mockk(relaxed = true) - val getLaunchesParams: GetLaunches.Params = mockk(relaxed = true) - - @Test - @ExperimentalCoroutinesApi - fun `loadData should invoke getLauches with provided params`() { - spyk(LaunchesViewModel(getLaunches, getLaunchesParams)) - - dispatcher.runBlockingTest { - - // init should have called it already due to creation above - verify(exactly = 1) { getLaunches(getLaunchesParams) } - } - } -} diff --git a/repository/build.gradle b/repository/build.gradle index 73ef37e..843f46d 100644 --- a/repository/build.gradle +++ b/repository/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation libraries.okHttpLogger kapt annotationProcessors.roomCompiler + kapt annotationProcessors.moshi testImplementation testLibraries.coroutinesCore testImplementation testLibraries.coroutinesTest diff --git a/repository/src/main/kotlin/com/melih/repository/Repository.kt b/repository/src/main/kotlin/com/melih/repository/Repository.kt index 297ac78..82a9d41 100644 --- a/repository/src/main/kotlin/com/melih/repository/Repository.kt +++ b/repository/src/main/kotlin/com/melih/repository/Repository.kt @@ -8,6 +8,6 @@ import com.melih.repository.interactors.base.Result */ abstract class Repository { - internal abstract suspend fun getNextLaunches(count: Int): Result> + 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/entities/LaunchEntity.kt b/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt index c7c3f27..2a7bdbd 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt +++ b/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt @@ -4,8 +4,10 @@ import androidx.room.Entity import androidx.room.PrimaryKey import com.melih.repository.DEFAULT_NAME import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass @Entity(tableName = "Launches") +@JsonClass(generateAdapter = true) data class LaunchEntity( @PrimaryKey val id: Long = 0L, val name: String = DEFAULT_NAME, diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt b/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt index 5119207..eaa55df 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt +++ b/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt @@ -1,5 +1,8 @@ package com.melih.repository.entities +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) data class LaunchesEntity( val id: Long = 0L, val launches: List = listOf(), diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt b/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt index 13bf125..652c4d3 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt +++ b/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt @@ -2,13 +2,16 @@ package com.melih.repository.entities import androidx.room.ColumnInfo import com.melih.repository.DEFAULT_NAME +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class LocationEntity( @ColumnInfo(name = "id_location") val id: Long = 0L, @ColumnInfo(name = "name_location") val name: String = DEFAULT_NAME, val pads: List = listOf(PadEntity()) ) +@JsonClass(generateAdapter = true) data class PadEntity( @ColumnInfo(name = "id_pad") val id: Long = 0L, @ColumnInfo(name = "name_pad") val name: String = DEFAULT_NAME, diff --git a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt b/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt index ab641e8..971ec00 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt +++ b/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt @@ -3,7 +3,9 @@ package com.melih.repository.entities import androidx.room.ColumnInfo import com.melih.repository.DEFAULT_NAME import com.melih.repository.EMPTY_STRING +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class MissionEntity( @ColumnInfo(name = "id_mission") val id: Long = 0L, @ColumnInfo(name = "name_mission") val name: String = DEFAULT_NAME, diff --git a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt b/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt index 3f220e5..7fd8c3d 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt +++ b/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt @@ -4,7 +4,9 @@ import androidx.room.ColumnInfo import com.melih.repository.DEFAULT_NAME import com.melih.repository.EMPTY_STRING import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class RocketEntity( @ColumnInfo(name = "id_rocket") val id: Long = 0L, @ColumnInfo(name = "name_rocket") val name: String = DEFAULT_NAME, diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt index c38bffa..7bf13d5 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt @@ -2,9 +2,12 @@ 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.sources.SourceManager +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 @@ -12,13 +15,32 @@ import javax.inject.Inject /** * Gets next given number of launches */ -class GetLaunchDetails @Inject constructor( - private val sourceManager: SourceManager -) : BaseInteractor() { +@UseExperimental(ExperimentalCoroutinesApi::class) +class GetLaunchDetails @Inject constructor() : BaseInteractor() { - @ExperimentalCoroutinesApi - override suspend fun run(collector: FlowCollector>, params: Params) { - collector.emit(sourceManager.getLaunchById(params.id)) + @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( diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt index ae4aa43..5cc80ec 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt @@ -4,24 +4,45 @@ 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.sources.SourceManager +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 */ -class GetLaunches @Inject constructor( - private val sourceManager: SourceManager -) : BaseInteractor, GetLaunches.Params>() { +@UseExperimental(ExperimentalCoroutinesApi::class) +class GetLaunches @Inject constructor() : BaseInteractor, GetLaunches.Params>() { - @ExperimentalCoroutinesApi - override suspend fun run(collector: FlowCollector>>, params: Params) { - collector.emit(sourceManager.getNextLaunches(params.count)) + @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 = 10 + 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/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt index e33ecfa..e431a2d 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt @@ -10,23 +10,22 @@ 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) abstract class BaseInteractor { // region Abstractions - @ExperimentalCoroutinesApi - protected abstract suspend fun run(collector: FlowCollector>, params: P) + protected abstract suspend fun FlowCollector>.run(params: P) // endregion // region Functions - @ExperimentalCoroutinesApi operator fun invoke(params: P) = - flow> { - emit(Result.State.Loading()) - run(this, params) - emit(Result.State.Loaded()) - }.flowOn(Dispatchers.IO) + flow> { + emit(State.Loading()) + run(params) + emit(State.Loaded()) + }.flowOn(Dispatchers.IO) // endregion } 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 index 5431133..e14b96e 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt @@ -5,15 +5,14 @@ import com.melih.repository.R /** - * [Result.Failure] reasons + * [Failure] reasons */ -sealed class Reason(@StringRes val messageRes: Int) { +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) -} +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/interactors/base/Result.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt index c4d5306..a5684c7 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt @@ -1,45 +1,53 @@ package com.melih.repository.interactors.base +import kotlinx.coroutines.ExperimentalCoroutinesApi + /** * Result class that wraps any [Success], [Failure] or [State] that can be generated by any derivation of [BaseInteractor] */ -sealed class Result { +@UseExperimental(ExperimentalCoroutinesApi::class) +sealed class Result - // region Subclasses +// region Subclasses - class Success(val successData: T) : Result() - class Failure(val errorData: Reason) : Result() +class Success(val successData: T) : Result() +class Failure(val errorData: Reason) : Result() - sealed class State : Result() { - class Loading : State() - class Loaded : State() - } - // endregion - - // region Functions - - inline fun handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) { - when (this) { - is Success -> successBlock(successData) - is Failure -> failureBlock(errorData) - is State -> stateBlock(this) - } - } - - inline fun handleSuccess(successBlock: (T) -> Unit) { - if (this is Success) - successBlock(successData) - } - - inline fun handleFailure(errorBlock: (Reason) -> Unit) { - if (this is Failure) - errorBlock(errorData) - } - - inline fun handleState(stateBlock: (State) -> Unit) { - if (this is State) - stateBlock(this) - } - // endregion +sealed class State : Result() { + class Loading : State() + class Loaded : State() } +// endregion + +// region Extensions + +inline fun Result.handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) { + when (this) { + is Success -> successBlock(successData) + is Failure -> failureBlock(errorData) + is State -> stateBlock(this) + } +} + +inline fun Result.onSuccess(successBlock: (T) -> Unit): Result { + if (this is Success) + successBlock(successData) + + return this +} + +inline fun Result.onFailure(errorBlock: (Reason) -> Unit): Result { + if (this is Failure) + errorBlock(errorData) + + return this +} + +inline fun Result.onState(stateBlock: (State) -> Unit): Result { + if (this is State) + stateBlock(this) + + return this +} +// endregion diff --git a/repository/src/main/kotlin/com/melih/repository/network/Api.kt b/repository/src/main/kotlin/com/melih/repository/network/Api.kt index 5ce9cad..0b410d8 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/Api.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/Api.kt @@ -5,15 +5,21 @@ import com.melih.repository.entities.LaunchesEntity import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path +import retrofit2.http.Query /** * Retrofit interface for networking */ -interface Api { +internal interface Api { @GET("launch/next/{count}") - suspend fun getNextLaunches(@Path("count") count: Int): Response + suspend fun getNextLaunches( + @Path("count") count: Int, + @Query("offset") offset: Int + ): Response @GET("launch/{id}") - suspend fun getLaunchById(@Path("id") id: Long): Response + suspend fun getLaunchById( + @Path("id") id: Long + ): Response } diff --git a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt index 3a03cac..85ca556 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt @@ -9,9 +9,12 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import java.util.concurrent.TimeUnit import javax.inject.Inject -class ApiImpl @Inject constructor() : Api { +internal const val TIMEOUT_DURATION = 7L + +internal class ApiImpl @Inject constructor() : Api { // region Properties @@ -23,9 +26,12 @@ class ApiImpl @Inject constructor() : Api { Retrofit.Builder() .client( OkHttpClient.Builder() + .connectTimeout(TIMEOUT_DURATION, TimeUnit.SECONDS) + .readTimeout(TIMEOUT_DURATION, TimeUnit.SECONDS) .addInterceptor( - HttpLoggingInterceptor() - .setLevel(HttpLoggingInterceptor.Level.BODY) + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } ).build() ) .addConverterFactory(MoshiConverterFactory.create(moshi)) @@ -35,9 +41,14 @@ class ApiImpl @Inject constructor() : Api { } // endregion - override suspend fun getNextLaunches(count: Int): Response = - service.getNextLaunches(count) + override suspend fun getNextLaunches( + count: Int, + offset: Int + ): Response = + service.getNextLaunches(count, offset) - override suspend fun getLaunchById(id: Long): Response = + override suspend fun getLaunchById( + id: Long + ): Response = service.getLaunchById(id) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt index 304e2a8..7d5f139 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt @@ -1,6 +1,8 @@ package com.melih.repository.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 @@ -24,7 +26,14 @@ const val DB_NAME = "LaunchesDB" RocketConverter::class, MissionConverter::class ) -abstract class LaunchesDatabase : RoomDatabase() { +internal abstract class LaunchesDatabase : RoomDatabase() { - abstract val launchesDao: LaunchesDao + companion object { + + fun getInstance(ctx: Context) = + Room.databaseBuilder(ctx, LaunchesDatabase::class.java, DB_NAME) + .build() + } + + internal abstract val launchesDao: LaunchesDao } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt index a05da1b..6742ecb 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt @@ -1,6 +1,7 @@ package com.melih.repository.persistence.converters import com.melih.repository.entities.LocationEntity +import com.melih.repository.entities.LocationEntityJsonAdapter import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi @@ -9,5 +10,5 @@ import com.squareup.moshi.Moshi */ class LocationConverter : BaseConverter() { override fun getAdapter(moshi: Moshi): JsonAdapter = - moshi.adapter(LocationEntity::class.java) + LocationEntityJsonAdapter(moshi) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt index 980d64d..de2e3b8 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt @@ -1,6 +1,7 @@ package com.melih.repository.persistence.converters import com.melih.repository.entities.RocketEntity +import com.melih.repository.entities.RocketEntityJsonAdapter import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi @@ -9,5 +10,5 @@ import com.squareup.moshi.Moshi */ class RocketConverter : BaseConverter() { override fun getAdapter(moshi: Moshi): JsonAdapter = - moshi.adapter(RocketEntity::class.java) + RocketEntityJsonAdapter(moshi) } 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 index 97f12bb..df4b014 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt @@ -2,20 +2,20 @@ package com.melih.repository.persistence.dao import androidx.room.Dao import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query -import androidx.room.Transaction import com.melih.repository.entities.LaunchEntity /** * DAO for list of [launches][LaunchEntity] */ @Dao -abstract class LaunchesDao { +internal abstract class LaunchesDao { // region Queries - @Query("SELECT * FROM Launches LIMIT :count") - abstract suspend fun getLaunches(count: Int): List + @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? @@ -26,19 +26,10 @@ abstract class LaunchesDao { // region Insertion - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunches(launches: List) - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunch(launch: LaunchEntity) // endregion - - // region Transactions - - @Transaction - open suspend fun updateLaunches(launches: List) { - nukeLaunches() - saveLaunches(launches) - } - // 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 index b64cd5c..8b67b74 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt @@ -4,8 +4,15 @@ 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 @@ -14,9 +21,9 @@ import javax.inject.Provider /** * NetworkSource for fetching results using api and wrapping them as contracted in [repository][Repository], - * returning either [failure][Result.Failure] with proper [reason][Reason] or [success][Result.Success] with data + * returning either [failure][Failure] with proper [reason][Reason] or [success][Success] with data */ -class NetworkSource @Inject constructor( +internal class NetworkSource @Inject constructor( private val apiImpl: ApiImpl, private val networkInfoProvider: Provider ) : Repository() { @@ -31,8 +38,10 @@ class NetworkSource @Inject constructor( // region Functions - override suspend fun getNextLaunches(count: Int): Result> = - safeExecute(apiImpl::getNextLaunches, count) { entity -> + 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( @@ -50,7 +59,9 @@ class NetworkSource @Inject constructor( } override suspend fun getLaunchById(id: Long): Result = - safeExecute(apiImpl::getLaunchById, id) { + safeExecute({ + apiImpl.getLaunchById(id) + }) { if (!it.rocket.imageURL.isNotBlank()) { it.copy( rocket = it.rocket.copy( @@ -62,28 +73,27 @@ class NetworkSource @Inject constructor( } } - private suspend inline fun safeExecute( - block: suspend (param: P) -> Response, - param: P, + private suspend inline fun safeExecute( + block: suspend () -> Response, transform: (T) -> R ) = if (isNetworkConnected) { try { - block(param).extractResponseBody(transform) + block().extractResponseBody(transform) } catch (e: IOException) { - Result.Failure(Reason.TimeoutError()) + Failure(TimeoutError()) } } else { - Result.Failure(Reason.NetworkError()) + Failure(NetworkError()) } private inline fun Response.extractResponseBody(transform: (T) -> R) = if (isSuccessful) { body()?.let { - Result.Success(transform(it)) - } ?: Result.Failure(Reason.EmptyResultError()) + Success(transform(it)) + } ?: Failure(EmptyResultError()) } else { - Result.Failure(Reason.ResponseError()) + Failure(ResponseError()) } private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) = diff --git a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt index 6d6c974..881e45d 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt @@ -1,28 +1,34 @@ 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.Reason 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 */ -class PersistenceSource @Inject constructor( - private val launchesDatabase: LaunchesDatabase +internal class PersistenceSource @Inject constructor( + ctx: Context ) : Repository() { // region Functions - override suspend fun getNextLaunches(count: Int): Result> = + private val launchesDatabase = LaunchesDatabase.getInstance(ctx) + + override suspend fun getNextLaunches(count: Int, page: Int): Result> = launchesDatabase .launchesDao - .getLaunches(count) + .getLaunches(count, page) .takeIf { it.isNotEmpty() } ?.run { - Result.Success(this) - } ?: Result.Failure(Reason.PersistenceEmpty()) + Success(this) + } ?: Failure(PersistenceEmpty()) override suspend fun getLaunchById(id: Long): Result = launchesDatabase @@ -30,11 +36,11 @@ class PersistenceSource @Inject constructor( .getLaunchById(id) .takeIf { it != null } ?.run { - Result.Success(this) - } ?: Result.Failure(Reason.PersistenceEmpty()) + Success(this) + } ?: Failure(PersistenceEmpty()) internal suspend fun saveLaunches(launches: List) { - launchesDatabase.launchesDao.updateLaunches(launches) + launchesDatabase.launchesDao.saveLaunches(launches) } internal suspend fun saveLaunch(launch: LaunchEntity) { diff --git a/repository/src/main/kotlin/com/melih/repository/sources/SourceManager.kt b/repository/src/main/kotlin/com/melih/repository/sources/SourceManager.kt deleted file mode 100644 index 999225b..0000000 --- a/repository/src/main/kotlin/com/melih/repository/sources/SourceManager.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.melih.repository.sources - -import com.melih.repository.Repository -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.Result -import javax.inject.Inject - -/** - * Manages SST by using network & persistance sources - */ -class SourceManager @Inject constructor( - private val networkSource: NetworkSource, - private val persistenceSource: PersistenceSource -) : Repository() { - - // region Functions - - override suspend fun getNextLaunches(count: Int): Result> { - networkSource - .getNextLaunches(count) - .takeIf { it is Result.Success } - ?.let { - persistenceSource.saveLaunches((it as Result.Success).successData) - } - - return persistenceSource - .getNextLaunches(count) - .takeIf { - it is Result.Success && it.successData.isNotEmpty() - } - ?: Result.Failure(Reason.NoNetworkPersistenceEmpty()) - } - - override suspend fun getLaunchById(id: Long): Result { - val result = - persistenceSource - .getLaunchById(id) - - return if (result is Result.Failure) { - networkSource - .getLaunchById(id) - .takeIf { it is Result.Success } - ?.let { - persistenceSource.saveLaunch((it as Result.Success).successData) - } - - persistenceSource - .getLaunchById(id) - } else { - result - } - } - // endregion -} diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt b/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt index f1d6430..35d922c 100644 --- a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt +++ b/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt @@ -12,14 +12,13 @@ import org.amshove.kluent.shouldEqualTo import org.junit.jupiter.api.Test import java.util.* - +@UseExperimental(ExperimentalCoroutinesApi::class) class BaseInteractorTest { val testInteractor = spyk(TestInteractor()) val testParams = TestParams() @Test - @ExperimentalCoroutinesApi fun `BaseInteractor should send states and items emmited by run`() { // Using run blocking due to threading problems in runBlockingTest // See https://github.com/Kotlin/kotlinx.coroutines/issues/1204 @@ -42,24 +41,23 @@ class BaseInteractorTest { resultDeque.size shouldEqualTo 3 // Verify first item is Loading state - resultDeque.poll() shouldBeInstanceOf Result.State.Loading::class + resultDeque.poll() shouldBeInstanceOf State.Loading::class // Verify second item is Success, with default value we set below in TestParams class resultDeque.poll().also { - it shouldBeInstanceOf Result.Success::class - (it as Result.Success).successData shouldEqualTo 10 + it shouldBeInstanceOf Success::class + (it as Success).successData shouldEqualTo 10 } // Verify last item is Loaded state - resultDeque.poll() shouldBeInstanceOf Result.State.Loaded::class + resultDeque.poll() shouldBeInstanceOf State.Loaded::class } } inner class TestInteractor : BaseInteractor() { - @ExperimentalCoroutinesApi - override suspend fun run(collector: FlowCollector>, params: TestParams) { - collector.emit(Result.Success(params.testValue)) + override suspend fun FlowCollector>.run(params: TestParams) { + emit(Success(params.testValue)) } } diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt b/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt index 79cfc02..cc770df 100644 --- a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt +++ b/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt @@ -12,11 +12,11 @@ class ResultTest { private val number = 10 - private val success = Result.Success(number) - private val failure = Result.Failure(Reason.GenericError()) - private val state = Result.State.Loading() + private val success = Success(number) + private val failure = Failure(GenericError()) + private val state = State.Loading() - private val emptyStateBlock = spyk({ _: Result.State -> }) + private val emptyStateBlock = spyk({ _: State -> }) private val emptyFailureBlock = spyk({ _: Reason -> }) private val emptySuccessBlock = spyk({ _: Int -> }) @@ -37,8 +37,8 @@ class ResultTest { @Test fun `Failure should only invoke failureBlock with correct error`() { val actualFailureBlock = spyk({ reason: Reason -> - reason shouldBeInstanceOf Reason.GenericError::class - (reason as Reason.GenericError).messageRes shouldEqualTo R.string.reason_generic + reason shouldBeInstanceOf GenericError::class + (reason as GenericError).messageRes shouldEqualTo R.string.reason_generic Unit }) @@ -51,8 +51,8 @@ class ResultTest { @Test fun `State should only invoke stateBlock with correct state`() { - val actualSuccessBlock = spyk({ state: Result.State -> - state shouldBeInstanceOf Result.State.Loading::class + val actualSuccessBlock = spyk({ state: State -> + state shouldBeInstanceOf State.Loading::class Unit }) diff --git a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt index 55f9528..343aac6 100644 --- a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt +++ b/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt @@ -4,8 +4,13 @@ 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.Reason -import com.melih.repository.interactors.base.Result +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 @@ -19,6 +24,7 @@ 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) @@ -32,66 +38,62 @@ class NetworkSourceTest { inner class GetNextLaunches { @Test - @ExperimentalCoroutinesApi fun `should return network error when internet is not connected`() { every { networkInfoProvider.get().isConnected } returns false runBlockingTest { - val result = source.getNextLaunches(1) + val result = source.getNextLaunches(1, 0) - result shouldBeInstanceOf Result.Failure::class - result.handleFailure { - it shouldBeInstanceOf Reason.NetworkError::class + result shouldBeInstanceOf Failure::class + result.onFailure { + it shouldBeInstanceOf NetworkError::class } } } @Test - @ExperimentalCoroutinesApi fun `should return response error when it is not successful`() { every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any()).isSuccessful } returns false + coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns false runBlockingTest { - val result = source.getNextLaunches(1) + val result = source.getNextLaunches(1, 0) - result shouldBeInstanceOf Result.Failure::class - result.handleFailure { - it shouldBeInstanceOf Reason.ResponseError::class - (it as Reason.ResponseError).messageRes shouldEqualTo R.string.reason_response + result shouldBeInstanceOf Failure::class + result.onFailure { + it shouldBeInstanceOf ResponseError::class + (it as ResponseError).messageRes shouldEqualTo R.string.reason_response } } } @Test - @ExperimentalCoroutinesApi fun `should return empty result error when body is null`() { every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any()).isSuccessful } returns true - coEvery { apiImpl.getNextLaunches(any()).body() } returns null + coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns true + coEvery { apiImpl.getNextLaunches(any(), any()).body() } returns null runBlockingTest { - val result = source.getNextLaunches(1) + val result = source.getNextLaunches(1, 0) - result shouldBeInstanceOf Result.Failure::class - result.handleFailure { - it shouldBeInstanceOf Reason.EmptyResultError::class + result shouldBeInstanceOf Failure::class + result.onFailure { + it shouldBeInstanceOf EmptyResultError::class } } } @Test - @ExperimentalCoroutinesApi fun `should return success with data if execution is successful`() { every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any()).isSuccessful } returns true - coEvery { apiImpl.getNextLaunches(any()).body() } returns LaunchesEntity(launches = listOf(LaunchEntity(id = 1013))) + 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) + val result = source.getNextLaunches(1, 0) - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { + 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 index a423423..e40fa14 100644 --- a/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt +++ b/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt @@ -1,51 +1,68 @@ package com.melih.repository.sources +import android.content.Context import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.Result +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.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest +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(dbImplementation)) + 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 - @ExperimentalCoroutinesApi fun `should return persistance empty error when db is empty`() { - runBlockingTest { - coEvery { dbImplementation.launchesDao.getLaunches(any()) } returns emptyList() + coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns emptyList() - val result = source.getNextLaunches(10) - result shouldBeInstanceOf Result.Failure::class - result.handleFailure { - it shouldBeInstanceOf Reason.PersistenceEmpty::class + scope.launch { + val result = source.getNextLaunches(10, 0) + + result shouldBeInstanceOf Failure::class + result.onFailure { + it shouldBeInstanceOf PersistenceEmpty::class } } } @Test - @ExperimentalCoroutinesApi fun `should return success with data if db is not empty`() { - runBlockingTest { - coEvery { dbImplementation.launchesDao.getLaunches(any()) } returns listOf(LaunchEntity(id = 1013)) + coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns listOf(LaunchEntity(id = 1013)) - val result = source.getNextLaunches(10) - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { + 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/repository/src/test/kotlin/com/melih/repository/sources/SourceManagerTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/SourceManagerTest.kt deleted file mode 100644 index 8415a8f..0000000 --- a/repository/src/test/kotlin/com/melih/repository/sources/SourceManagerTest.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.melih.repository.sources - -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.Result -import io.mockk.called -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.coVerifyOrder -import io.mockk.mockk -import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -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 - -class SourceManagerTest { - - private val networkSource = mockk(relaxed = true) - private val persistenceSource = mockk(relaxed = true) - - private val sourceManager = spyk(SourceManager(networkSource, persistenceSource)) - - @Nested - inner class GetNextLaunches { - - @Test - @ExperimentalCoroutinesApi - fun `should try to fetch, save and return result from persistance`() { - runBlockingTest { - val amount = 10 - coEvery { networkSource.getNextLaunches(any()) } returns Result.Success(listOf(LaunchEntity(id = 1012))) - coEvery { persistenceSource.getNextLaunches(any()) } returns Result.Success(listOf(LaunchEntity(id = 1013))) - - val result = sourceManager.getNextLaunches(amount) - - coVerifyOrder { - networkSource.getNextLaunches(any()) - persistenceSource.saveLaunches(any()) - persistenceSource.getNextLaunches(any()) - } - - coVerify(exactly = 1) { networkSource.getNextLaunches(any()) } - coVerify(exactly = 1) { persistenceSource.saveLaunches(any()) } - coVerify(exactly = 1) { persistenceSource.getNextLaunches(any()) } - - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { - it.size shouldEqualTo 1 - it[0].id shouldEqualTo 1013 - } - } - } - - @Test - @ExperimentalCoroutinesApi - fun `should not save response if fetching was failure and return result from persistance`() { - runBlockingTest { - val amount = 10 - coEvery { networkSource.getNextLaunches(any()) } returns Result.Failure(Reason.NetworkError()) - coEvery { persistenceSource.getNextLaunches(any()) } returns Result.Success(listOf(LaunchEntity(id = 1013))) - - val result = sourceManager.getNextLaunches(amount) - - coVerify { persistenceSource.saveLaunches(any()) wasNot called } - coVerify(exactly = 1) { persistenceSource.getNextLaunches(any()) } - - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { - it.size shouldEqualTo 1 - it[0].id shouldEqualTo 1013 - } - } - } - - @Test - @ExperimentalCoroutinesApi - fun `should return failure if network and persistance fails`() { - runBlockingTest { - val amount = 10 - coEvery { networkSource.getNextLaunches(any()) } returns Result.Failure(Reason.NetworkError()) - coEvery { persistenceSource.getNextLaunches(any()) } returns Result.Failure(Reason.PersistenceEmpty()) - - val result = sourceManager.getNextLaunches(amount) - - coVerify { persistenceSource.saveLaunches(any()) wasNot called } - coVerify(exactly = 1) { persistenceSource.getNextLaunches(any()) } - - result shouldBeInstanceOf Result.Failure::class - result.handleFailure { - it shouldBeInstanceOf Reason.NoNetworkPersistenceEmpty::class - } - } - } - } - - @Nested - inner class GetLaunchDetails { - - @Test - fun `should return result from persistance immediately if it's found`() { - runBlocking { - coEvery { persistenceSource.getLaunchById(any()) } returns Result.Success(LaunchEntity(id = 1013)) - - val result = sourceManager.getLaunchById(1) - - coVerify { networkSource.getLaunchById(any()) wasNot called } - - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { - it.id shouldEqualTo 1013 - } - } - } - - @Test - fun `should fetch result from network if it's not found in persistance`() { - runBlocking { - coEvery { - persistenceSource.getLaunchById(any()) - } returns Result.Failure(Reason.PersistenceEmpty()) andThen Result.Success(LaunchEntity(id = 1013)) - - coEvery { networkSource.getLaunchById(any()) } returns Result.Success(LaunchEntity(id = 1013)) - - val result = sourceManager.getLaunchById(1) - - coVerify(exactly = 1) { networkSource.getLaunchById(any()) } - coVerify(exactly = 1) { persistenceSource.saveLaunch(any()) } - coVerify(exactly = 2) { persistenceSource.getLaunchById(any()) } - - result shouldBeInstanceOf Result.Success::class - result.handleSuccess { - it.id shouldEqualTo 1013 - } - } - } - } -} diff --git a/scripts/default_dependencies.gradle b/scripts/default_dependencies.gradle index a6ae4c6..b9ee1c9 100644 --- a/scripts/default_dependencies.gradle +++ b/scripts/default_dependencies.gradle @@ -1,4 +1,5 @@ apply plugin: "de.mannodermaus.android-junit5" + apply from: "$rootProject.projectDir/scripts/detekt.gradle" apply from: "$rootProject.projectDir/scripts/dokka.gradle" @@ -17,3 +18,57 @@ 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 3bc483d..29a823e 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -8,7 +8,7 @@ ext { supportLibraryVersion : "28.0.0", appCompatVersion : "1.1.0-alpha04", lifecycleVersion : "2.2.0-alpha02", - fragmentVersion : "1.1.0-beta01", + fragmentVersion : "1.2.0-alpha01", workManagerVersion : "2.1.0-alpha03", constraintLayoutVesion: "2.0.0-beta1", cardViewVersion : "1.0.0", @@ -22,7 +22,7 @@ ext { retrofitVersion : "2.6.0", picassoVersion : "2.71828", moshiVersion : "1.8.0", - coroutinesVersion : "1.3.0-M1", + coroutinesVersion : "1.3.0-M2", leakCanaryVersion : "2.0-alpha-2", timberVersion : "4.7.1", jUnitVersion : "5.5.0", @@ -127,6 +127,7 @@ ext { "com.google.dagger:dagger-compiler:${versions.daggerVersion}", "com.google.dagger:dagger-android-processor:${versions.daggerVersion}" ], + moshi : "com.squareup.moshi:moshi-kotlin-codegen:${versions.moshiVersion}" ] testLibraries = [ diff --git a/scripts/feature_module.gradle b/scripts/feature_module.gradle index 146c113..e6ceeb1 100644 --- a/scripts/feature_module.gradle +++ b/scripts/feature_module.gradle @@ -11,6 +11,7 @@ dependencies { implementation libraries.fragment implementation libraries.lifecycle + implementation libraries.liveDataKTX implementation libraries.navigation implementation libraries.constraintLayout } diff --git a/scripts/flavors.gradle b/scripts/flavors.gradle index 49de32e..496ab22 100644 --- a/scripts/flavors.gradle +++ b/scripts/flavors.gradle @@ -8,6 +8,7 @@ android { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + testCoverageEnabled = true } dev { diff --git a/scripts/sources.gradle b/scripts/sources.gradle index d6a4f00..dcbd8fc 100644 --- a/scripts/sources.gradle +++ b/scripts/sources.gradle @@ -12,5 +12,6 @@ android { kotlinOptions { jvmTarget = '1.8' + freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental" } } diff --git a/settings.gradle b/settings.gradle index 6bb6ae3..7a766a4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':app', ':repository', ':core', ':features:list', ':features:detail' +include ':app', ':repository', ':core', ':features:launches', ':features:detail' rootProject.name = 'Rocket Science'