Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds getFragmentIndexInStackBySameType function to Navigator #53

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion medusalib/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'

android {
namespace 'com.trendyol.medusalib'
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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 <a href="https://github.com/Trendyol/medusa/wiki/Fragment-Lifecycle">Fragment Lifecycle</a>
*/
fun getNavigatorTransaction(): NavigatorTransaction
}
}



Original file line number Diff line number Diff line change
@@ -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<StackItem> {
override fun createFromParcel(parcel: Parcel): StackItem {
return StackItem(parcel)
}

override fun newArray(size: Int): Array<StackItem?> {
return arrayOfNulls(size)
}
}

}
@Parcelize
data class StackItem(val fragmentTag: String, val groupName: String = "") : Parcelable
Original file line number Diff line number Diff line change
@@ -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<TestParentFragment>().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<TestParentFragment>().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<TestParentFragment>().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<TestParentFragment>().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<Fragment>()
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<TestParentFragment>().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<Fragment>()
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<TestParentFragment>().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<Fragment>()
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)
}
}
}
Loading