Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 443da8b

Browse files
author
Manuel Vivo
committed
Migrate OnboardingViewModel to Flows
Fixes: b/186632953 Change-Id: I49aa5d2f0c48521b039c3883fc327c144c1abdcf
1 parent 6defa64 commit 443da8b

File tree

11 files changed

+86
-39
lines changed

11 files changed

+86
-39
lines changed

buildSrc/src/main/java/Libs.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ object Libs {
6363
const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
6464
const val LIFECYCLE_COMPILER = "androidx.lifecycle:lifecycle-compiler"
6565
const val LIFECYCLE_LIVE_DATA_KTX = "androidx.lifecycle:lifecycle-livedata-ktx"
66+
const val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx"
6667
const val LIFECYCLE_VIEW_MODEL_KTX = "androidx.lifecycle:lifecycle-viewmodel-ktx"
6768
const val LOTTIE = "com.airbnb.android:lottie"
6869
const val MATERIAL = "com.google.android.material:material"

depconstraints/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ val hamcrest = "1.3"
5252
val hilt = Versions.HILT_AGP
5353
val junit = "4.13"
5454
val junitExt = "1.1.1"
55-
val lifecycle = "2.3.0"
55+
val lifecycle = "2.4.0-alpha01"
5656
val lottie = "3.0.0"
5757
val material = "1.3.0"
5858
val mockito = "3.3.1"
@@ -118,6 +118,7 @@ dependencies {
118118
api("${Libs.KOTLIN_STDLIB}:${Versions.KOTLIN}")
119119
api("${Libs.LIFECYCLE_COMPILER}:$lifecycle")
120120
api("${Libs.LIFECYCLE_LIVE_DATA_KTX}:$lifecycle")
121+
api("${Libs.LIFECYCLE_RUNTIME_KTX}:$lifecycle")
121122
api("${Libs.LIFECYCLE_VIEW_MODEL_KTX}:$lifecycle")
122123
api("${Libs.LOTTIE}:$lottie")
123124
api("${Libs.MATERIAL}:$material")

mobile/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ android {
139139
// Running lint over the debug variant is enough
140140
isCheckReleaseBuilds = false
141141
// See lint.xml for rules configuration
142+
143+
// TODO: Remove when upgrading lifecycle from `2.4.0-alpha01`.
144+
// Fix: https://android-review.googlesource.com/c/platform/frameworks/support/+/1697465
145+
// Bug: https://issuetracker.google.com/184830263
146+
disable("NullSafeMutableLiveData")
142147
}
143148

144149
testBuildType = "staging"
@@ -189,6 +194,7 @@ dependencies {
189194

190195
// Architecture Components
191196
implementation(Libs.LIFECYCLE_LIVE_DATA_KTX)
197+
implementation(Libs.LIFECYCLE_RUNTIME_KTX)
192198
kapt(Libs.LIFECYCLE_COMPILER)
193199
testImplementation(Libs.ARCH_TESTING)
194200
implementation(Libs.NAVIGATION_FRAGMENT_KTX)

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingActivity.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ import android.widget.FrameLayout
2222
import androidx.activity.viewModels
2323
import androidx.appcompat.app.AppCompatActivity
2424
import androidx.core.view.updatePadding
25+
import androidx.lifecycle.Lifecycle
26+
import androidx.lifecycle.lifecycleScope
27+
import androidx.lifecycle.repeatOnLifecycle
2528
import com.google.samples.apps.iosched.R
26-
import com.google.samples.apps.iosched.shared.result.EventObserver
2729
import com.google.samples.apps.iosched.shared.util.inTransaction
2830
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
2931
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
3032
import dagger.hilt.android.AndroidEntryPoint
33+
import kotlinx.coroutines.flow.collect
34+
import kotlinx.coroutines.launch
3135

3236
@AndroidEntryPoint
3337
class OnboardingActivity : AppCompatActivity() {
@@ -56,12 +60,15 @@ class OnboardingActivity : AppCompatActivity() {
5660
}
5761
}
5862

59-
viewModel.navigateToSignInDialogAction.observe(
60-
this,
61-
EventObserver {
62-
openSignInDialog()
63+
lifecycleScope.launch {
64+
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
65+
viewModel.navigationActions.collect { action ->
66+
if (action == OnboardingNavigationAction.NavigateToSignInDialog) {
67+
openSignInDialog()
68+
}
69+
}
6370
}
64-
)
71+
}
6572
}
6673

6774
private fun openSignInDialog() {

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingFragment.kt

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ import androidx.fragment.app.FragmentManager
2929
import androidx.fragment.app.FragmentPagerAdapter
3030
import androidx.fragment.app.viewModels
3131
import com.google.samples.apps.iosched.databinding.FragmentOnboardingBinding
32-
import com.google.samples.apps.iosched.shared.result.EventObserver
3332
import com.google.samples.apps.iosched.shared.util.TimeUtils
3433
import com.google.samples.apps.iosched.ui.MainActivity
34+
import com.google.samples.apps.iosched.util.launchAndRepeatWithViewLifecycle
3535
import dagger.hilt.android.AndroidEntryPoint
36+
import kotlinx.coroutines.flow.collect
3637

3738
private const val AUTO_ADVANCE_DELAY = 6_000L
3839
private const val INITIAL_ADVANCE_DELAY = 3_000L
@@ -64,7 +65,7 @@ class OnboardingFragment : Fragment() {
6465
inflater: LayoutInflater,
6566
container: ViewGroup?,
6667
savedInstanceState: Bundle?
67-
): View? {
68+
): View {
6869
binding = FragmentOnboardingBinding.inflate(inflater, container, false).apply {
6970
viewModel = onboardingViewModel
7071
lifecycleOwner = viewLifecycleOwner
@@ -76,18 +77,22 @@ class OnboardingFragment : Fragment() {
7677
false
7778
}
7879
}
80+
return binding.root
81+
}
7982

80-
onboardingViewModel.navigateToMainActivity.observe(
81-
viewLifecycleOwner,
82-
EventObserver {
83-
requireActivity().run {
84-
startActivity(Intent(this, MainActivity::class.java))
85-
finish()
83+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
84+
super.onViewCreated(view, savedInstanceState)
85+
86+
launchAndRepeatWithViewLifecycle {
87+
onboardingViewModel.navigationActions.collect { action ->
88+
if (action == OnboardingNavigationAction.NavigateToMainScreen) {
89+
requireActivity().run {
90+
startActivity(Intent(this, MainActivity::class.java))
91+
finish()
92+
}
8693
}
8794
}
88-
)
89-
90-
return binding.root
95+
}
9196
}
9297

9398
override fun onAttach(context: Context) {

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingSignInFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class OnboardingSignInFragment : Fragment() {
3636
inflater: LayoutInflater,
3737
container: ViewGroup?,
3838
savedInstanceState: Bundle?
39-
): View? {
39+
): View {
4040
binding = FragmentOnboardingSigninBinding.inflate(inflater, container, false).apply {
4141
lifecycleOwner = viewLifecycleOwner
4242
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingViewModel.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
package com.google.samples.apps.iosched.ui.onboarding
1818

19-
import androidx.lifecycle.LiveData
20-
import androidx.lifecycle.MutableLiveData
2119
import androidx.lifecycle.ViewModel
2220
import androidx.lifecycle.viewModelScope
2321
import com.google.samples.apps.iosched.shared.domain.prefs.OnboardingCompleteActionUseCase
24-
import com.google.samples.apps.iosched.shared.result.Event
22+
import com.google.samples.apps.iosched.shared.util.tryOffer
2523
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
2624
import dagger.hilt.android.lifecycle.HiltViewModel
25+
import kotlinx.coroutines.channels.Channel
26+
import kotlinx.coroutines.flow.receiveAsFlow
2727
import kotlinx.coroutines.launch
2828
import javax.inject.Inject
2929

@@ -36,20 +36,25 @@ class OnboardingViewModel @Inject constructor(
3636
signInViewModelDelegate: SignInViewModelDelegate
3737
) : ViewModel(), SignInViewModelDelegate by signInViewModelDelegate {
3838

39-
private val _navigateToMainActivity = MutableLiveData<Event<Unit>>()
40-
val navigateToMainActivity: LiveData<Event<Unit>> = _navigateToMainActivity
41-
42-
private val _navigateToSignInDialogAction = MutableLiveData<Event<Unit>>()
43-
val navigateToSignInDialogAction: LiveData<Event<Unit>> = _navigateToSignInDialogAction
39+
private val _navigationActions = Channel<OnboardingNavigationAction>(Channel.CONFLATED)
40+
// OnboardingViewModel is a shared ViewModel. Therefore, the navigation actions could be
41+
// received by multiple collectors at the same time. With `receiveAsFlow`, we make sure only
42+
// one collector will process the navigation event to avoid multiple back stack entries.
43+
val navigationActions = _navigationActions.receiveAsFlow()
4444

4545
fun getStartedClick() {
4646
viewModelScope.launch {
4747
onboardingCompleteActionUseCase(true)
48-
_navigateToMainActivity.postValue(Event(Unit))
48+
_navigationActions.send(OnboardingNavigationAction.NavigateToMainScreen)
4949
}
5050
}
5151

5252
fun onSigninClicked() {
53-
_navigateToSignInDialogAction.value = Event(Unit)
53+
_navigationActions.tryOffer(OnboardingNavigationAction.NavigateToSignInDialog)
5454
}
5555
}
56+
57+
sealed class OnboardingNavigationAction {
58+
object NavigateToMainScreen : OnboardingNavigationAction()
59+
object NavigateToSignInDialog : OnboardingNavigationAction()
60+
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/WelcomeDuringConferenceFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class WelcomeDuringConferenceFragment : Fragment() {
4040
inflater: LayoutInflater,
4141
container: ViewGroup?,
4242
savedInstanceState: Bundle?
43-
): View? {
43+
): View {
4444
binding = FragmentOnboardingWelcomeDuringBinding.inflate(inflater, container, false).apply {
4545
lifecycleOwner = viewLifecycleOwner
4646
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/onboarding/WelcomePreConferenceFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class WelcomePreConferenceFragment : Fragment() {
4040
inflater: LayoutInflater,
4141
container: ViewGroup?,
4242
savedInstanceState: Bundle?
43-
): View? {
43+
): View {
4444
binding = FragmentOnboardingWelcomePreBinding.inflate(inflater, container, false).apply {
4545
lifecycleOwner = viewLifecycleOwner
4646
}

mobile/src/main/java/com/google/samples/apps/iosched/util/UiUtils.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ import android.content.Context
2020
import android.graphics.drawable.Drawable
2121
import androidx.appcompat.content.res.AppCompatResources
2222
import androidx.core.graphics.drawable.DrawableCompat
23+
import androidx.fragment.app.Fragment
24+
import androidx.lifecycle.Lifecycle
25+
import androidx.lifecycle.lifecycleScope
26+
import androidx.lifecycle.repeatOnLifecycle
2327
import com.google.samples.apps.iosched.R
28+
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.launch
2430

2531
fun navigationItemBackground(context: Context): Drawable? {
2632
// Need to inflate the drawable and CSL via AppCompatResources to work on Lollipop
@@ -44,3 +50,18 @@ fun navigationItemBackground(context: Context): Drawable? {
4450
fun slideOffsetToAlpha(value: Float, rangeMin: Float, rangeMax: Float): Float {
4551
return ((value - rangeMin) / (rangeMax - rangeMin)).coerceIn(0f, 1f)
4652
}
53+
54+
/**
55+
* Launches a new coroutine and repeats `block` every time the Fragment's viewLifecycleOwner
56+
* is in and out of `minActiveState` lifecycle state.
57+
*/
58+
inline fun Fragment.launchAndRepeatWithViewLifecycle(
59+
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
60+
crossinline block: suspend CoroutineScope.() -> Unit
61+
) {
62+
viewLifecycleOwner.lifecycleScope.launch {
63+
viewLifecycleOwner.lifecycle.repeatOnLifecycle(minActiveState) {
64+
block()
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)