diff --git a/.github/workflows/pull-request-jobs.yml b/.github/workflows/pull-request-jobs.yml
index e1a402e..6b087d8 100644
--- a/.github/workflows/pull-request-jobs.yml
+++ b/.github/workflows/pull-request-jobs.yml
@@ -40,7 +40,9 @@ jobs:
if: always()
with:
name: ktLint Results
- path: ./**/build/reports/ktlint/**/*ktlint*Check.txt
+ path: |
+ ./**/build/reports/ktlint/**/*ktlint*Check.txt
+ ./**/**/build/reports/ktlint/**/*ktlint*Check.txt
retention-days: 1
- name: Run Lint
run: ./gradlew lint
@@ -49,7 +51,9 @@ jobs:
if: always()
with:
name: Lint Results
- path: ./**/build/reports/*lint-results*.html
+ path: |
+ ./**/build/reports/*lint-results*.html
+ ./**/**/build/reports/*lint-results*.html
retention-days: 1
run-tests:
diff --git a/app/build.gradle b/app/build.gradle
index 3ed80a0..7d3b0e4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -114,6 +114,9 @@ dependencies {
testImplementation testFixtures(project(':core'))
androidTestImplementation testFixtures(project(':core'))
+
+ // case specific
+ implementation project(":examplecase:example-navcontroller")
}
apply from: '../gradlescripts/pull-screenshots.gradle'
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b40d313..d055725 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@
buildscript {
ext.kotlin_version = "1.6.10"
ext.detekt_version = "1.19.0"
+ ext.navigation_version = "2.4.2"
repositories {
mavenCentral()
google()
@@ -11,6 +12,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
}
}
diff --git a/examplecase/example-navcontroller/.gitignore b/examplecase/example-navcontroller/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/examplecase/example-navcontroller/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/build.gradle b/examplecase/example-navcontroller/build.gradle
new file mode 100644
index 0000000..914af41
--- /dev/null
+++ b/examplecase/example-navcontroller/build.gradle
@@ -0,0 +1,67 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+ id 'androidx.navigation.safeargs.kotlin'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 31
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ sourceSets {
+ androidTest {
+ java.srcDirs += "src/sharedTest/java"
+ assets.srcDirs += files("$projectDir/schemas".toString())
+ }
+ test {
+ java.srcDirs += "src/sharedTest/java"
+ java.srcDirs += "src/robolectricTest/java"
+ resources.srcDirs += files("$projectDir/schemas".toString())
+ }
+ }
+
+ // needed for androidTest
+ packagingOptions {
+ exclude 'META-INF/LGPL2.1'
+ exclude 'META-INF/AL2.0'
+ exclude 'META-INF/LICENSE.md'
+ exclude 'META-INF/LICENSE-notice.md'
+ }
+}
+
+dependencies {
+ implementation "androidx.core:core-ktx:$androidx_core_version"
+ implementation "androidx.appcompat:appcompat:$androidx_appcompat_version"
+ implementation "com.google.android.material:material:$androidx_material_version"
+ implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
+ implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
+
+ debugImplementation "androidx.fragment:fragment-testing:1.5.3"
+
+ applyAppTestDependenciesTo(this)
+
+ testImplementation "androidx.navigation:navigation-testing:$navigation_version"
+ testImplementation project(':test-util-android')
+ androidTestImplementation project(':test-util-android')
+ androidTestImplementation "androidx.navigation:navigation-testing:$navigation_version"
+}
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/consumer-rules.pro b/examplecase/example-navcontroller/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/examplecase/example-navcontroller/proguard-rules.pro b/examplecase/example-navcontroller/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/examplecase/example-navcontroller/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/AndroidManifest.xml b/examplecase/example-navcontroller/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..84f7369
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/DetailFragment.kt b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/DetailFragment.kt
new file mode 100644
index 0000000..8cd5618
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/DetailFragment.kt
@@ -0,0 +1,17 @@
+package org.fnives.test.showcase.examplecase.navcontroller
+
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+
+class DetailFragment : Fragment(R.layout.fragment_detail) {
+
+ private val args by navArgs()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (view as TextView).text = getString(R.string.home_item, args.position)
+ }
+}
diff --git a/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/HomeFragment.kt b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/HomeFragment.kt
new file mode 100644
index 0000000..1b255cb
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/HomeFragment.kt
@@ -0,0 +1,44 @@
+package org.fnives.test.showcase.examplecase.navcontroller
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class HomeFragment : Fragment(R.layout.fragment_home) {
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val recycler = view.findViewById(R.id.recycler)
+ recycler.layoutManager = LinearLayoutManager(view.context)
+ recycler.adapter = Adapter(onClick = {
+ if (findNavController().currentDestination?.id != R.id.homeFragment) return@Adapter
+ findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToDetailFragment(it))
+ })
+ }
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ constructor(parent: ViewGroup) : this(LayoutInflater.from(parent.context).inflate(R.layout.item_home, parent, false))
+ }
+
+ class Adapter(
+ private val count: Int = 30,
+ private val onClick: (Int) -> Unit,
+ ) : RecyclerView.Adapter() {
+ override fun getItemCount(): Int = count
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
+ ViewHolder(parent)
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val context = holder.itemView.context
+ (holder.itemView as Button).text = context.getString(R.string.home_item, position)
+ holder.itemView.setOnClickListener { onClick(position) }
+ }
+ }
+}
diff --git a/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/NavControllerActivity.kt b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/NavControllerActivity.kt
new file mode 100644
index 0000000..0f038b8
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/java/org/fnives/test/showcase/examplecase/navcontroller/NavControllerActivity.kt
@@ -0,0 +1,15 @@
+package org.fnives.test.showcase.examplecase.navcontroller
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+// to see the actual screen, not just in test use:
+// adb shell am start -n org.fnives.test.showcase/org.fnives.test.showcase.examplecase.navcontroller.NavControllerActivity
+// after installing the apk
+class NavControllerActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_nav_controller)
+ }
+}
diff --git a/examplecase/example-navcontroller/src/main/res/layout/activity_nav_controller.xml b/examplecase/example-navcontroller/src/main/res/layout/activity_nav_controller.xml
new file mode 100644
index 0000000..1c1ef79
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/layout/activity_nav_controller.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/res/layout/fragment_detail.xml b/examplecase/example-navcontroller/src/main/res/layout/fragment_detail.xml
new file mode 100644
index 0000000..c4fe6a9
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/layout/fragment_detail.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/res/layout/fragment_home.xml b/examplecase/example-navcontroller/src/main/res/layout/fragment_home.xml
new file mode 100644
index 0000000..ea78406
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/res/layout/item_home.xml b/examplecase/example-navcontroller/src/main/res/layout/item_home.xml
new file mode 100644
index 0000000..580482b
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/layout/item_home.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/res/navigation/nav_example.xml b/examplecase/example-navcontroller/src/main/res/navigation/nav_example.xml
new file mode 100644
index 0000000..5fe7e0a
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/navigation/nav_example.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/main/res/values/strings.xml b/examplecase/example-navcontroller/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8a7a7fa
--- /dev/null
+++ b/examplecase/example-navcontroller/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Item %1$d
+ Nav Controller Example
+
\ No newline at end of file
diff --git a/examplecase/example-navcontroller/src/sharedTest/java/org/fnives/test/showcase/examplecase/navcontroller/HomeNavigationTest.kt b/examplecase/example-navcontroller/src/sharedTest/java/org/fnives/test/showcase/examplecase/navcontroller/HomeNavigationTest.kt
new file mode 100644
index 0000000..6943f0f
--- /dev/null
+++ b/examplecase/example-navcontroller/src/sharedTest/java/org/fnives/test/showcase/examplecase/navcontroller/HomeNavigationTest.kt
@@ -0,0 +1,114 @@
+package org.fnives.test.showcase.examplecase.navcontroller
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.navigation.Navigation
+import androidx.navigation.testing.TestNavHostController
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.fnives.test.showcase.android.testutil.viewaction.recycler.RemoveItemAnimations
+import org.hamcrest.Matchers
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Shows basic navigation test from a ListScreen to a Detail Screen.
+ *
+ * Sets up TestNavController and shows how it can be used to verify proper arguments, destination.
+ *
+ * For more info check out https://developer.android.com/guide/navigation/navigation-testing
+ */
+@RunWith(AndroidJUnit4::class)
+class HomeNavigationTest {
+
+ private lateinit var fragmentScenario: FragmentScenario
+ private lateinit var testNavController: TestNavHostController
+
+ @Before
+ fun setup() {
+ testNavController = TestNavHostController(ApplicationProvider.getApplicationContext())
+ fragmentScenario = launchFragmentInContainer()
+ fragmentScenario.runOnMain { testNavController.setGraph(R.navigation.nav_example) }
+
+ fragmentScenario.onFragment { fragment ->
+ Navigation.setViewNavController(fragment.requireView(), testNavController)
+ }
+
+ Espresso.onView(ViewMatchers.withId(R.id.recycler))
+ .perform(RemoveItemAnimations())
+ }
+
+ @Test
+ fun clickingOnItemNavigatesProperlyAndBackstackIsCorrect() {
+ val position = 25
+ Espresso.onView(ViewMatchers.withId(R.id.recycler))
+ .perform(RecyclerViewActions.scrollToPosition(position))
+
+ Espresso.onView(
+ Matchers.allOf(
+ ViewMatchers.withText("Item $position"), ViewMatchers.withId(R.id.item_cta),
+ ViewMatchers.withParent(ViewMatchers.withId(R.id.recycler))
+ )
+ )
+ .perform(ViewActions.click())
+
+ Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
+ }
+
+ @Test
+ fun clickingOnItemTwiceNavigatesProperly() {
+ val position = 16
+ Espresso.onView(ViewMatchers.withId(R.id.recycler))
+ .perform(RecyclerViewActions.scrollToPosition(position))
+
+ Espresso.onView(
+ Matchers.allOf(
+ ViewMatchers.withText("Item $position"), ViewMatchers.withId(R.id.item_cta),
+ ViewMatchers.withParent(ViewMatchers.withId(R.id.recycler))
+ )
+ )
+ .perform(ViewActions.click())
+ .perform(ViewActions.click())
+
+ Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
+ Assert.assertEquals(listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment), testNavController.backStack.map { it.destination.id })
+ testNavController.backStack.map { it.arguments }
+ }
+
+ @Test
+ fun clickingOnTwoItemsOpensOnlyTheFirst() {
+ val position1 = 16
+ val position2 = 15
+ Espresso.onView(ViewMatchers.withId(R.id.recycler))
+ .perform(RecyclerViewActions.scrollToPosition(position1))
+
+ Espresso.onView(itemViewMatcher(position1)).perform(ViewActions.click())
+ Espresso.onView(itemViewMatcher(position2)).perform(ViewActions.click())
+
+ Assert.assertEquals(R.id.detailFragment, testNavController.currentDestination?.id)
+ Assert.assertEquals(listOf(R.id.nav_example_xml, R.id.homeFragment, R.id.detailFragment), testNavController.backStack.map { it.destination.id })
+ val actualArgs = DetailFragmentArgs.fromBundle(testNavController.backStack.last().arguments ?: Bundle())
+ Assert.assertEquals(position1, actualArgs.position)
+ }
+
+ private fun itemViewMatcher(position: Int) =
+ Matchers.allOf(
+ ViewMatchers.withText("Item $position"), ViewMatchers.withId(R.id.item_cta),
+ ViewMatchers.withParent(ViewMatchers.withId(R.id.recycler))
+ )
+
+ companion object {
+ inline fun FragmentScenario.runOnMain(crossinline action: () -> Unit) {
+ onFragment { action() }
+ }
+ }
+}
diff --git a/examplecase/example-navcontroller/src/test/resources/robolectric.properties b/examplecase/example-navcontroller/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..4984e4b
--- /dev/null
+++ b/examplecase/example-navcontroller/src/test/resources/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=22,28
+instrumentedPackages=androidx.loader.content
diff --git a/gradlescripts/detekt.config.gradle b/gradlescripts/detekt.config.gradle
index 0e47c48..2214998 100644
--- a/gradlescripts/detekt.config.gradle
+++ b/gradlescripts/detekt.config.gradle
@@ -4,8 +4,14 @@ detekt {
def projectPaths = subprojects.collect {
def projectName = it.name
- "$projectDir/$projectName/src/main/java"
- }
+ if (it.subprojects.isEmpty()) {
+ "$projectDir/$projectName/src/main/java"
+ } else {
+ it.subprojects.collect { sub ->
+ "$sub.projectDir//src/main/java"
+ }
+ }
+ }.flatten()
source = files(*projectPaths)
config = files("$projectDir/detekt/detekt.yml")
diff --git a/gradlescripts/lint.gradle b/gradlescripts/lint.gradle
index d906414..ce0fe30 100644
--- a/gradlescripts/lint.gradle
+++ b/gradlescripts/lint.gradle
@@ -6,7 +6,7 @@ subprojects { module ->
warningsAsErrors true
abortOnError true
textReport true
- ignore 'Overdraw'
+ ignore 'Overdraw', 'NewerVersionAvailable', 'GradleDependency'
textOutput "stdout"
}
}
@@ -17,7 +17,7 @@ subprojects { module ->
warningsAsErrors true
abortOnError true
textReport true
- ignore 'Overdraw'
+ ignore 'Overdraw', 'NewerVersionAvailable', 'GradleDependency'
textOutput "stdout"
}
}
@@ -27,7 +27,7 @@ subprojects { module ->
warningsAsErrors true
abortOnError true
textReport true
- ignore 'Overdraw'
+ ignore 'Overdraw', 'NewerVersionAvailable', 'GradleDependency'
textOutput "stdout"
}
}
diff --git a/gradlescripts/test.tasks.gradle b/gradlescripts/test.tasks.gradle
index c8f1744..48829df 100644
--- a/gradlescripts/test.tasks.gradle
+++ b/gradlescripts/test.tasks.gradle
@@ -1,4 +1,4 @@
-task jvmTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]) {
+task jvmTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test", "examplecase:example-navcontroller:testDebugUnitTest"]) {
group = 'Tests'
description = 'Run all JVM tests'
}
@@ -10,7 +10,7 @@ task robolectricTests(type: Exec) {
commandLine 'sh', './gradlew', 'testDebugUnitTest', '--tests', 'org.fnives.test.*InstrumentedTest'
}
-task androidTests(dependsOn: ["app:connectedDebugAndroidTest"]) {
+task androidTests(dependsOn: ["app:connectedDebugAndroidTest", "examplecase:example-navcontroller:connectedDebugAndroidTest"]) {
group = 'Tests'
description = 'Run Android tests'
}
\ No newline at end of file
diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle
index 6859a4e..b393ad4 100644
--- a/gradlescripts/versions.gradle
+++ b/gradlescripts/versions.gradle
@@ -5,7 +5,7 @@ project.ext {
androidx_constraintlayout_version = "2.1.3"
androidx_livedata_version = "2.4.0"
androidx_swiperefreshlayout_version = "1.1.0"
- room_version = "2.4.2"
+ room_version = "2.4.3"
activity_ktx_version = "1.4.0"
androidx_navigation = "2.4.0"
diff --git a/settings.gradle b/settings.gradle
index 6b11768..d372230 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,3 +8,4 @@ include ':test-util-shared-android'
include ':test-util-shared-robolectric'
include ':test-util-android'
include ':test-util-junit5-android'
+include ':examplecase:example-navcontroller'