Skip to content

Commit 22f05b2

Browse files
Merge pull request #53 from Trendyol/feature/fragment-stack-index-getter-with-tag
Adds `getFragmentIndexInStackBySameType` function to `Navigator`
2 parents dd46a06 + 289a75e commit 22f05b2

File tree

5 files changed

+219
-54
lines changed

5 files changed

+219
-54
lines changed

medusalib/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
apply plugin: 'com.android.library'
22
apply plugin: 'kotlin-android'
3+
apply plugin: 'kotlin-parcelize'
34

45
android {
56
namespace 'com.trendyol.medusalib'
@@ -29,7 +30,7 @@ android {
2930

3031
ext {
3132
PUBLISH_GROUP_ID = 'com.trendyol'
32-
PUBLISH_VERSION = '0.10.4'
33+
PUBLISH_VERSION = '0.11.0'
3334
PUBLISH_ARTIFACT_ID = 'medusa'
3435
PUBLISH_DESCRIPTION = "Android Fragment Stack Controller"
3536
PUBLISH_URL = "https://github.com/Trendyol/medusa"

medusalib/src/main/java/com/trendyol/medusalib/navigator/MultipleStackNavigator.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.fragment.app.FragmentManager
66
import androidx.lifecycle.DefaultLifecycleObserver
77
import androidx.lifecycle.LifecycleOwner
88
import androidx.lifecycle.MutableLiveData
9-
import androidx.lifecycle.Observer
109
import com.trendyol.medusalib.navigator.controller.FragmentManagerController
1110
import com.trendyol.medusalib.navigator.data.FragmentData
1211
import com.trendyol.medusalib.navigator.data.StackItem
@@ -212,14 +211,23 @@ open class MultipleStackNavigator(
212211
lifecycleOwner: LifecycleOwner,
213212
destinationChangedListener: (Fragment) -> Unit
214213
) {
215-
destinationChangeLiveData.observe(
216-
lifecycleOwner,
217-
Observer { fragment ->
218-
if (fragment != null) {
219-
destinationChangedListener(fragment)
214+
destinationChangeLiveData.observe(lifecycleOwner) { fragment ->
215+
if (fragment != null) {
216+
destinationChangedListener(fragment)
217+
}
218+
}
219+
}
220+
221+
override fun getFragmentIndexInStackBySameType(tag: String?): Int {
222+
if (tag.isNullOrEmpty()) return -1
223+
fragmentStackState.fragmentTagStack.forEach { stack ->
224+
stack.forEachIndexed { index, stackItem ->
225+
if (stackItem.fragmentTag == tag) {
226+
return stack.size - index - 1
220227
}
221228
}
222-
)
229+
}
230+
return -1
223231
}
224232

225233
private fun initializeStackState() {

medusalib/src/main/java/com/trendyol/medusalib/navigator/Navigator.kt

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -152,30 +152,46 @@ interface Navigator {
152152
*/
153153
fun onSaveInstanceState(outState: Bundle)
154154

155-
/*
156-
* Initializes fragment stack state and adds related root fragments to your
157-
* container if savedState is null. Otherwise reads and deserialize
158-
* fragment stack state from given bundle.
159-
* @param outState savedInstanceState parameter of onCreate method in
155+
/**
156+
* Initializes fragment stack state and adds related root fragments to your
157+
* container if savedState is null. Otherwise reads and deserialize
158+
* fragment stack state from given bundle.
159+
*
160+
* @param savedState savedInstanceState parameter of onCreate method in
160161
* your fragments or activities
161-
*/
162+
*/
162163
fun initialize(savedState: Bundle?)
163164

164165
/**
165166
* Listeners
166167
*/
167168

168-
/*
169-
Observes any changes made in fragment back stack with the given lifecycle.
170-
All implementation of Navigator interface must guarantee following points:
171-
- View lifecycle of the fragments that is observed by the listener must be at least in
172-
STARTED state.
169+
/**
170+
* Observes any changes made in fragment back stack with the given lifecycle.
171+
* All implementation of Navigator interface must guarantee following points:
172+
*
173+
* - View lifecycle of the fragments that is observed by the listener must be at least in
174+
* STARTED state.
175+
*
176+
* - destinationChangedListener must be removed when the given lifecycle owner is reached
177+
* DESTROYED state
178+
*/
179+
fun observeDestinationChanges(
180+
lifecycleOwner: LifecycleOwner,
181+
destinationChangedListener: (Fragment) -> Unit,
182+
)
173183

174-
- destinationChangedListener must be removed when the given lifecycle owner is reached
175-
DESTROYED state
184+
/**
185+
* Retrieves the index of a [Fragment] within the fragment stack based on the specified tag.
186+
* If the tag is null or empty, returns -1.
187+
* Iterates through the fragment stack to find the specified tag.
188+
* Returns the index of the [Fragment] relative to the top of its stack if found; otherwise,
189+
* returns -1.
190+
*
191+
* @param tag The tag of the [Fragment] to search for within the stack.
192+
* @return The index of the [Fragment] within its stack if found; otherwise, -1.
176193
*/
177-
fun observeDestinationChanges(lifecycleOwner: LifecycleOwner,
178-
destinationChangedListener: (Fragment) -> Unit)
194+
fun getFragmentIndexInStackBySameType(tag: String?): Int
179195

180196
interface NavigatorListener {
181197

@@ -214,11 +230,8 @@ interface Navigator {
214230
* fragment.
215231
* @return NavigatorTransaction type (ATTACH_DETACH or SHOW_HIDE)
216232
*
217-
* @see https://github.com/Trendyol/medusa/wiki/Fragment-Lifecycle
233+
* @see <a href="https://github.com/Trendyol/medusa/wiki/Fragment-Lifecycle">Fragment Lifecycle</a>
218234
*/
219235
fun getNavigatorTransaction(): NavigatorTransaction
220236
}
221237
}
222-
223-
224-
Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
11
package com.trendyol.medusalib.navigator.data
22

3-
import android.os.Parcel
43
import android.os.Parcelable
4+
import kotlinx.parcelize.Parcelize
55

6-
data class StackItem(val fragmentTag: String, val groupName: String = "") : Parcelable {
7-
constructor(parcel: Parcel) : this(
8-
requireNotNull(parcel.readString()),
9-
requireNotNull(parcel.readString())
10-
)
11-
12-
override fun writeToParcel(parcel: Parcel, flags: Int) {
13-
parcel.writeString(fragmentTag)
14-
parcel.writeString(groupName)
15-
}
16-
17-
override fun describeContents(): Int {
18-
return 0
19-
}
20-
21-
companion object CREATOR : Parcelable.Creator<StackItem> {
22-
override fun createFromParcel(parcel: Parcel): StackItem {
23-
return StackItem(parcel)
24-
}
25-
26-
override fun newArray(size: Int): Array<StackItem?> {
27-
return arrayOfNulls(size)
28-
}
29-
}
30-
31-
}
6+
@Parcelize
7+
data class StackItem(val fragmentTag: String, val groupName: String = "") : Parcelable
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package com.trendyol.medusalib.navigator
2+
3+
import androidx.fragment.app.Fragment
4+
import androidx.fragment.app.testing.launchFragmentInContainer
5+
import com.google.common.truth.Truth.assertThat
6+
import com.trendyol.medusalib.TestChildFragment
7+
import com.trendyol.medusalib.TestParentFragment
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
import org.robolectric.RobolectricTestRunner
11+
12+
@RunWith(RobolectricTestRunner::class)
13+
class MultipleStackNavigatorBackstackOrderTest {
14+
15+
@Test
16+
fun `given MultipleStackNavigator with empty stack and null as tag, when calling getFragmentIndexInStackBySameType, should return -1`() {
17+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
18+
// Given
19+
val sut = MultipleStackNavigator(
20+
fragmentManager = fragment.childFragmentManager,
21+
containerId = TestParentFragment.CONTAINER_ID,
22+
rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
23+
)
24+
sut.initialize(null)
25+
26+
// When
27+
val actual = sut.getFragmentIndexInStackBySameType(null)
28+
29+
// Then
30+
assertThat(actual).isEqualTo(-1)
31+
}
32+
}
33+
34+
@Test
35+
fun `given MultipleStackNavigator with empty stack and nonnull tag, when calling getFragmentIndexInStackBySameType, should return -1`() {
36+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
37+
// Given
38+
val sut = MultipleStackNavigator(
39+
fragmentManager = fragment.childFragmentManager,
40+
containerId = TestParentFragment.CONTAINER_ID,
41+
rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
42+
)
43+
sut.initialize(null)
44+
45+
// When
46+
val actual = sut.getFragmentIndexInStackBySameType("random-tag")
47+
48+
// Then
49+
assertThat(actual).isEqualTo(-1)
50+
}
51+
}
52+
53+
@Test
54+
fun `given MultipleStackNavigator with stack with single fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for current fragment, should return 0`() {
55+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
56+
// Given
57+
val sut = MultipleStackNavigator(
58+
fragmentManager = fragment.childFragmentManager,
59+
containerId = TestParentFragment.CONTAINER_ID,
60+
rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
61+
)
62+
sut.initialize(null)
63+
64+
sut.start(TestChildFragment.newInstance("child fragment"))
65+
66+
fragment.childFragmentManager.executePendingTransactions()
67+
68+
// When
69+
val actual = sut.getFragmentIndexInStackBySameType(sut.getCurrentFragment()?.tag)
70+
71+
// Then
72+
assertThat(actual).isEqualTo(0)
73+
}
74+
}
75+
76+
@Test
77+
fun `given MultipleStackNavigator with stack with multiple fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for first child fragment, should return 2`() {
78+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
79+
// Given
80+
val sut = MultipleStackNavigator(
81+
fragmentManager = fragment.childFragmentManager,
82+
containerId = TestParentFragment.CONTAINER_ID,
83+
rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
84+
)
85+
sut.initialize(null)
86+
87+
val fragments = mutableListOf<Fragment>()
88+
sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
89+
fragments.add(it)
90+
}
91+
92+
sut.start(TestChildFragment.newInstance("child fragment 1"))
93+
sut.start(TestChildFragment.newInstance("child fragment 2"))
94+
sut.start(TestChildFragment.newInstance("child fragment 3"))
95+
fragment.childFragmentManager.executePendingTransactions()
96+
97+
// When
98+
val actual = sut.getFragmentIndexInStackBySameType(fragments[1].tag)
99+
100+
// Then
101+
assertThat(actual).isEqualTo(2)
102+
}
103+
}
104+
105+
@Test
106+
fun `given MultipleStackNavigator with stack with multiple fragment and nonnull tag, when calling getFragmentIndexInStackBySameType for last child fragment, should return 0`() {
107+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
108+
// Given
109+
val sut = MultipleStackNavigator(
110+
fragmentManager = fragment.childFragmentManager,
111+
containerId = TestParentFragment.CONTAINER_ID,
112+
rootFragmentProvider = listOf({ TestChildFragment.newInstance("root 1") }),
113+
)
114+
sut.initialize(null)
115+
116+
val fragments = mutableListOf<Fragment>()
117+
sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
118+
fragments.add(it)
119+
}
120+
121+
sut.start(TestChildFragment.newInstance("child fragment 1"))
122+
sut.start(TestChildFragment.newInstance("child fragment 2"))
123+
fragment.childFragmentManager.executePendingTransactions()
124+
125+
// When
126+
val actual = sut.getFragmentIndexInStackBySameType(fragments[2].tag)
127+
128+
// Then
129+
assertThat(actual).isEqualTo(0)
130+
}
131+
}
132+
133+
@Test
134+
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`() {
135+
launchFragmentInContainer<TestParentFragment>().onFragment { fragment ->
136+
// Given
137+
val sut = MultipleStackNavigator(
138+
fragmentManager = fragment.childFragmentManager,
139+
containerId = TestParentFragment.CONTAINER_ID,
140+
rootFragmentProvider = listOf(
141+
{ TestChildFragment.newInstance("root 1") },
142+
{ TestChildFragment.newInstance("root 2") },
143+
),
144+
)
145+
sut.initialize(null)
146+
147+
val fragments = mutableListOf<Fragment>()
148+
sut.observeDestinationChanges(fragment.viewLifecycleOwner) {
149+
fragments.add(it)
150+
}
151+
152+
sut.start(TestChildFragment.newInstance("child fragment 1"))
153+
sut.start(TestChildFragment.newInstance("child fragment 2"))
154+
sut.switchTab(1)
155+
sut.start(TestChildFragment.newInstance("child fragment 1"))
156+
sut.start(TestChildFragment.newInstance("child fragment 1"))
157+
158+
fragment.childFragmentManager.executePendingTransactions()
159+
160+
// When
161+
val actual = sut.getFragmentIndexInStackBySameType(fragments[4].tag)
162+
163+
// Then
164+
assertThat(actual).isEqualTo(1)
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)