diff --git a/medusalib/build.gradle b/medusalib/build.gradle
index 66bae21..4dd938f 100755
--- a/medusalib/build.gradle
+++ b/medusalib/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-parcelize'
android {
namespace 'com.trendyol.medusalib'
@@ -29,7 +30,7 @@ android {
ext {
PUBLISH_GROUP_ID = 'com.trendyol'
- PUBLISH_VERSION = '0.10.4'
+ PUBLISH_VERSION = '0.11.0'
PUBLISH_ARTIFACT_ID = 'medusa'
PUBLISH_DESCRIPTION = "Android Fragment Stack Controller"
PUBLISH_URL = "https://github.com/Trendyol/medusa"
diff --git a/medusalib/src/main/java/com/trendyol/medusalib/navigator/MultipleStackNavigator.kt b/medusalib/src/main/java/com/trendyol/medusalib/navigator/MultipleStackNavigator.kt
index f6f78d2..3c122e6 100755
--- a/medusalib/src/main/java/com/trendyol/medusalib/navigator/MultipleStackNavigator.kt
+++ b/medusalib/src/main/java/com/trendyol/medusalib/navigator/MultipleStackNavigator.kt
@@ -6,7 +6,6 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
import com.trendyol.medusalib.navigator.controller.FragmentManagerController
import com.trendyol.medusalib.navigator.data.FragmentData
import com.trendyol.medusalib.navigator.data.StackItem
@@ -212,14 +211,23 @@ open class MultipleStackNavigator(
lifecycleOwner: LifecycleOwner,
destinationChangedListener: (Fragment) -> Unit
) {
- destinationChangeLiveData.observe(
- lifecycleOwner,
- Observer { fragment ->
- if (fragment != null) {
- destinationChangedListener(fragment)
+ destinationChangeLiveData.observe(lifecycleOwner) { fragment ->
+ if (fragment != null) {
+ destinationChangedListener(fragment)
+ }
+ }
+ }
+
+ override fun getFragmentIndexInStackBySameType(tag: String?): Int {
+ if (tag.isNullOrEmpty()) return -1
+ fragmentStackState.fragmentTagStack.forEach { stack ->
+ stack.forEachIndexed { index, stackItem ->
+ if (stackItem.fragmentTag == tag) {
+ return stack.size - index - 1
}
}
- )
+ }
+ return -1
}
private fun initializeStackState() {
diff --git a/medusalib/src/main/java/com/trendyol/medusalib/navigator/Navigator.kt b/medusalib/src/main/java/com/trendyol/medusalib/navigator/Navigator.kt
index 940b1a4..ff5515e 100755
--- a/medusalib/src/main/java/com/trendyol/medusalib/navigator/Navigator.kt
+++ b/medusalib/src/main/java/com/trendyol/medusalib/navigator/Navigator.kt
@@ -152,30 +152,46 @@ interface Navigator {
*/
fun onSaveInstanceState(outState: Bundle)
- /*
- * Initializes fragment stack state and adds related root fragments to your
- * container if savedState is null. Otherwise reads and deserialize
- * fragment stack state from given bundle.
- * @param outState savedInstanceState parameter of onCreate method in
+ /**
+ * Initializes fragment stack state and adds related root fragments to your
+ * container if savedState is null. Otherwise reads and deserialize
+ * fragment stack state from given bundle.
+ *
+ * @param savedState savedInstanceState parameter of onCreate method in
* your fragments or activities
- */
+ */
fun initialize(savedState: Bundle?)
/**
* Listeners
*/
- /*
- Observes any changes made in fragment back stack with the given lifecycle.
- All implementation of Navigator interface must guarantee following points:
- - View lifecycle of the fragments that is observed by the listener must be at least in
- STARTED state.
+ /**
+ * Observes any changes made in fragment back stack with the given lifecycle.
+ * All implementation of Navigator interface must guarantee following points:
+ *
+ * - View lifecycle of the fragments that is observed by the listener must be at least in
+ * STARTED state.
+ *
+ * - destinationChangedListener must be removed when the given lifecycle owner is reached
+ * DESTROYED state
+ */
+ fun observeDestinationChanges(
+ lifecycleOwner: LifecycleOwner,
+ destinationChangedListener: (Fragment) -> Unit,
+ )
- - destinationChangedListener must be removed when the given lifecycle owner is reached
- DESTROYED state
+ /**
+ * Retrieves the index of a [Fragment] within the fragment stack based on the specified tag.
+ * If the tag is null or empty, returns -1.
+ * Iterates through the fragment stack to find the specified tag.
+ * Returns the index of the [Fragment] relative to the top of its stack if found; otherwise,
+ * returns -1.
+ *
+ * @param tag The tag of the [Fragment] to search for within the stack.
+ * @return The index of the [Fragment] within its stack if found; otherwise, -1.
*/
- fun observeDestinationChanges(lifecycleOwner: LifecycleOwner,
- destinationChangedListener: (Fragment) -> Unit)
+ fun getFragmentIndexInStackBySameType(tag: String?): Int
interface NavigatorListener {
@@ -214,11 +230,8 @@ interface Navigator {
* fragment.
* @return NavigatorTransaction type (ATTACH_DETACH or SHOW_HIDE)
*
- * @see https://github.com/Trendyol/medusa/wiki/Fragment-Lifecycle
+ * @see Fragment Lifecycle
*/
fun getNavigatorTransaction(): NavigatorTransaction
}
}
-
-
-
diff --git a/medusalib/src/main/java/com/trendyol/medusalib/navigator/data/StackItem.kt b/medusalib/src/main/java/com/trendyol/medusalib/navigator/data/StackItem.kt
index 2c142b9..02b47ef 100755
--- a/medusalib/src/main/java/com/trendyol/medusalib/navigator/data/StackItem.kt
+++ b/medusalib/src/main/java/com/trendyol/medusalib/navigator/data/StackItem.kt
@@ -1,31 +1,7 @@
package com.trendyol.medusalib.navigator.data
-import android.os.Parcel
import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
-data class StackItem(val fragmentTag: String, val groupName: String = "") : Parcelable {
- constructor(parcel: Parcel) : this(
- requireNotNull(parcel.readString()),
- requireNotNull(parcel.readString())
- )
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(fragmentTag)
- parcel.writeString(groupName)
- }
-
- override fun describeContents(): Int {
- return 0
- }
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): StackItem {
- return StackItem(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
-
-}
\ No newline at end of file
+@Parcelize
+data class StackItem(val fragmentTag: String, val groupName: String = "") : Parcelable
diff --git a/medusalib/src/test/java/com/trendyol/medusalib/navigator/MultipleStackNavigatorBackstackOrderTest.kt b/medusalib/src/test/java/com/trendyol/medusalib/navigator/MultipleStackNavigatorBackstackOrderTest.kt
new file mode 100644
index 0000000..2481ab0
--- /dev/null
+++ b/medusalib/src/test/java/com/trendyol/medusalib/navigator/MultipleStackNavigatorBackstackOrderTest.kt
@@ -0,0 +1,167 @@
+package com.trendyol.medusalib.navigator
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import com.google.common.truth.Truth.assertThat
+import com.trendyol.medusalib.TestChildFragment
+import com.trendyol.medusalib.TestParentFragment
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class MultipleStackNavigatorBackstackOrderTest {
+
+ @Test
+ fun `given MultipleStackNavigator with empty stack and null as tag, when calling getFragmentIndexInStackBySameType, should return -1`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
+ )
+ sut.initialize(null)
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType(null)
+
+ // Then
+ assertThat(actual).isEqualTo(-1)
+ }
+ }
+
+ @Test
+ fun `given MultipleStackNavigator with empty stack and nonnull tag, when calling getFragmentIndexInStackBySameType, should return -1`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
+ )
+ sut.initialize(null)
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType("random-tag")
+
+ // Then
+ assertThat(actual).isEqualTo(-1)
+ }
+ }
+
+ @Test
+ fun `given MultipleStackNavigator with stack with single fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for current fragment, should return 0`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
+ )
+ sut.initialize(null)
+
+ sut.start(TestChildFragment.newInstance("child fragment"))
+
+ fragment.childFragmentManager.executePendingTransactions()
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType(sut.getCurrentFragment()?.tag)
+
+ // Then
+ assertThat(actual).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun `given MultipleStackNavigator with stack with multiple fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for first child fragment, should return 2`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
+ )
+ sut.initialize(null)
+
+ val fragments = mutableListOf()
+ sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
+ fragments.add(it)
+ }
+
+ sut.start(TestChildFragment.newInstance("child fragment 1"))
+ sut.start(TestChildFragment.newInstance("child fragment 2"))
+ sut.start(TestChildFragment.newInstance("child fragment 3"))
+ fragment.childFragmentManager.executePendingTransactions()
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType(fragments[1].tag)
+
+ // Then
+ assertThat(actual).isEqualTo(2)
+ }
+ }
+
+ @Test
+ fun `given MultipleStackNavigator with stack with multiple fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for last child fragment, should return 0`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
+ )
+ sut.initialize(null)
+
+ val fragments = mutableListOf()
+ sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
+ fragments.add(it)
+ }
+
+ sut.start(TestChildFragment.newInstance("child fragment 1"))
+ sut.start(TestChildFragment.newInstance("child fragment 2"))
+ fragment.childFragmentManager.executePendingTransactions()
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType(fragments[2].tag)
+
+ // Then
+ assertThat(actual).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun `given MultipleStackNavigator with stack with multiple root fragments and nonnull tag and switch tab, when calling getFragmentIndexInStackBySameType for first child in switched tab, should return 1`() {
+ launchFragmentInContainer().onFragment { fragment ->
+ // Given
+ val sut = MultipleStackNavigator(
+ fragmentManager = fragment.childFragmentManager,
+ containerId = TestParentFragment.CONTAINER_ID,
+ rootFragmentProvider = listOf(
+ { TestChildFragment.newInstance("root 1") },
+ { TestChildFragment.newInstance("root 2") },
+ ),
+ )
+ sut.initialize(null)
+
+ val fragments = mutableListOf()
+ sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
+ fragments.add(it)
+ }
+
+ sut.start(TestChildFragment.newInstance("child fragment 1"))
+ sut.start(TestChildFragment.newInstance("child fragment 2"))
+ sut.switchTab(1)
+ sut.start(TestChildFragment.newInstance("child fragment 1"))
+ sut.start(TestChildFragment.newInstance("child fragment 1"))
+
+ fragment.childFragmentManager.executePendingTransactions()
+
+ // When
+ val actual = sut.getFragmentIndexInStackBySameType(fragments[4].tag)
+
+ // Then
+ assertThat(actual).isEqualTo(1)
+ }
+ }
+}