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

Fixes a bug that cannot use custom scope viewModel in mvrx-compose. #712

Merged
merged 3 commits into from
Mar 19, 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
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ inline fun <reified VM : MavericksViewModel<S>, reified S : MavericksState> mave

if (parentFragment != null) {
val args = argsFactory?.invoke() ?: parentFragment.arguments?.get(Mavericks.KEY_ARG)
FragmentViewModelContext(activity, args, parentFragment)
FragmentViewModelContext(activity, args, parentFragment, viewModelStoreOwner, savedStateRegistry)
} else {
val args = argsFactory?.invoke() ?: activity.intent.extras?.get(Mavericks.KEY_ARG)
ActivityViewModelContext(activity, args, viewModelStoreOwner, savedStateRegistry)
Expand Down
7 changes: 7 additions & 0 deletions mvrx-compose/src/test/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.airbnb.mvrx.compose.CustomLifecycleOwnerScopeTestActivity" android:exported="false" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.airbnb.mvrx.compose.FragmentArgsTestActivity" android:exported="false" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package com.airbnb.mvrx.compose

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import com.airbnb.mvrx.Mavericks
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class CustomLifecycleOwnerScopeTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<CustomLifecycleOwnerScopeTestActivity>()

@Before
fun setUp() {
Mavericks.initialize(composeTestRule.activity)
}

@Test
fun `activity_customScope_viewModel1 and activity_customScope_viewModel2 are different`() {
assertNotNull(composeTestRule.activity.fragment.viewModel1)
assertNotNull(composeTestRule.activity.fragment.viewModel2)
assert(composeTestRule.activity.fragment.viewModel1 !== composeTestRule.activity.fragment.viewModel2)
}

@Test
fun `fragment_customScope_viewModel1 and fragment_customScope_viewModel2 are different`() {
assertNotNull(composeTestRule.activity.viewModel1)
assertNotNull(composeTestRule.activity.viewModel2)
assert(composeTestRule.activity.viewModel1 !== composeTestRule.activity.viewModel2)
}
}

@Composable
private fun CustomViewModelScope(content: @Composable (LifecycleOwner) -> Unit) {
val originLifecycleOwner = LocalLifecycleOwner.current
val customLifecycleRegistry = remember { LifecycleRegistry(originLifecycleOwner) }
val customScope = remember {
CustomLifecycleOwner(
customLifecycleRegistry,
ViewModelStore(),
(originLifecycleOwner as SavedStateRegistryOwner).savedStateRegistry
)
}

customLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)

content(customScope)

DisposableEffect(Unit) {
onDispose {
customLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
}

class CustomLifecycleOwner(
override val lifecycle: Lifecycle,
override val viewModelStore: ViewModelStore,
override val savedStateRegistry: SavedStateRegistry,
) : LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner

class CustomLifecycleOwnerScopeTestActivity : AppCompatActivity() {
lateinit var fragment: CustomLifecycleOwnerScopeTestFragment
lateinit var viewModel1: CounterViewModel
lateinit var viewModel2: CounterViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val fragmentId = 123
val fragmentContainerView = FragmentContainerView(this).apply {
id = fragmentId
}
val composeView = ComposeView(this).apply {
setContent {
CustomViewModelScope { scope ->
[email protected] = mavericksViewModel<CounterViewModel, CounterState>(scope = scope)
}
CustomViewModelScope { scope ->
[email protected] = mavericksViewModel<CounterViewModel, CounterState>(scope = scope)
}
}
}

setContentView(
LinearLayout(this).apply {
addView(fragmentContainerView)
addView(composeView)
}
)

fragment = CustomLifecycleOwnerScopeTestFragment()

supportFragmentManager.beginTransaction()
.add(
fragmentId,
fragment
)
.commit()
}
}

class CustomLifecycleOwnerScopeTestFragment : Fragment() {
lateinit var viewModel1: CounterViewModel
lateinit var viewModel2: CounterViewModel

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(requireContext()).apply {
setContent {
CustomViewModelScope { scope ->
[email protected] = mavericksViewModel<CounterViewModel, CounterState>(scope = scope)
}
CustomViewModelScope { scope ->
[email protected] = mavericksViewModel<CounterViewModel, CounterState>(scope = scope)
}
}
}
}
}
Loading