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

Commit 6f2cd9d

Browse files
committed
Migrate various usages of LiveData to Flow
Change-Id: I1f502003973fe2147253babf58398daa68a8568b
1 parent 12d9fa8 commit 6f2cd9d

File tree

31 files changed

+293
-725
lines changed

31 files changed

+293
-725
lines changed

mobile/lint.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,4 @@
4545
<!-- Map markers are referenced by name at runtime -->
4646
<ignore regexp="res/drawable/map_marker_.+\.xml" />
4747
</issue>
48-
49-
<!-- TODO: remove this check (b/181732250) -->
50-
<issue id="NullSafeMutableLiveData" severity="warning" />
5148
</lint>

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package com.google.samples.apps.iosched.ui
1818

1919
import androidx.lifecycle.ViewModel
20+
import androidx.lifecycle.viewModelScope
2021
import com.google.samples.apps.iosched.shared.domain.prefs.OnboardingCompletedUseCase
22+
import com.google.samples.apps.iosched.shared.result.Result.Loading
2123
import com.google.samples.apps.iosched.shared.result.data
2224
import com.google.samples.apps.iosched.ui.LaunchNavigatonAction.NavigateToMainActivityAction
2325
import com.google.samples.apps.iosched.ui.LaunchNavigatonAction.NavigateToOnboardingAction
2426
import dagger.hilt.android.lifecycle.HiltViewModel
27+
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
2528
import kotlinx.coroutines.flow.map
29+
import kotlinx.coroutines.flow.stateIn
2630
import javax.inject.Inject
2731

2832
/**
@@ -38,7 +42,7 @@ class LaunchViewModel @Inject constructor(
3842
} else {
3943
NavigateToMainActivityAction
4044
}
41-
}
45+
}.stateIn(viewModelScope, Eagerly, Loading)
4246
}
4347

4448
sealed class LaunchNavigatonAction {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
package com.google.samples.apps.iosched.ui
1818

1919
import android.content.Intent
20-
import androidx.lifecycle.repeatOnLifecycle
2120
import android.os.Bundle
2221
import androidx.activity.viewModels
23-
import androidx.lifecycle.Lifecycle
2422
import androidx.appcompat.app.AppCompatActivity
23+
import androidx.lifecycle.Lifecycle
2524
import androidx.lifecycle.lifecycleScope
25+
import androidx.lifecycle.repeatOnLifecycle
2626
import com.google.samples.apps.iosched.ui.LaunchNavigatonAction.NavigateToMainActivityAction
2727
import com.google.samples.apps.iosched.ui.LaunchNavigatonAction.NavigateToOnboardingAction
2828
import com.google.samples.apps.iosched.ui.onboarding.OnboardingActivity

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/AnnouncementsFragment.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ import androidx.core.view.isVisible
2525
import androidx.core.view.updatePadding
2626
import androidx.fragment.app.activityViewModels
2727
import androidx.fragment.app.viewModels
28-
import androidx.lifecycle.Observer
2928
import com.google.common.collect.ImmutableMap
3029
import com.google.samples.apps.iosched.databinding.FragmentAnnouncementsBinding
3130
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
3231
import com.google.samples.apps.iosched.ui.MainActivityViewModel
3332
import com.google.samples.apps.iosched.ui.MainNavigationFragment
3433
import com.google.samples.apps.iosched.ui.signin.setupProfileMenuItem
3534
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
35+
import com.google.samples.apps.iosched.util.launchAndRepeatWithViewLifecycle
3636
import dagger.hilt.android.AndroidEntryPoint
37+
import kotlinx.coroutines.flow.collect
3738
import javax.inject.Inject
3839

3940
@AndroidEntryPoint
@@ -85,12 +86,11 @@ class AnnouncementsFragment : MainNavigationFragment() {
8586
v.updatePadding(bottom = padding.bottom + systemInsets.bottom)
8687
}
8788

88-
model.announcements.observe(
89-
viewLifecycleOwner,
90-
Observer {
89+
launchAndRepeatWithViewLifecycle {
90+
model.announcements.collect {
9191
(binding.recyclerView.adapter as FeedAdapter).submitList(it)
9292
}
93-
)
93+
}
9494
}
9595

9696
private fun createAdapter(): FeedAdapter {

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/AnnouncementsViewModel.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@
1616

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

19-
import androidx.lifecycle.LiveData
2019
import androidx.lifecycle.ViewModel
21-
import androidx.lifecycle.liveData
20+
import androidx.lifecycle.viewModelScope
2221
import com.google.samples.apps.iosched.shared.domain.feed.LoadAnnouncementsUseCase
2322
import com.google.samples.apps.iosched.shared.domain.settings.GetTimeZoneUseCase
2423
import com.google.samples.apps.iosched.shared.result.Result.Loading
2524
import com.google.samples.apps.iosched.shared.result.successOr
2625
import com.google.samples.apps.iosched.shared.time.TimeProvider
2726
import com.google.samples.apps.iosched.shared.util.TimeUtils
2827
import dagger.hilt.android.lifecycle.HiltViewModel
28+
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
29+
import kotlinx.coroutines.flow.StateFlow
30+
import kotlinx.coroutines.flow.flow
31+
import kotlinx.coroutines.flow.stateIn
2932
import org.threeten.bp.ZoneId
3033
import javax.inject.Inject
3134

@@ -36,7 +39,7 @@ class AnnouncementsViewModel @Inject constructor(
3639
timeProvider: TimeProvider
3740
) : ViewModel() {
3841

39-
val announcements: LiveData<List<Any>> = liveData {
42+
val announcements: StateFlow<List<Any>> = flow {
4043
val loadAnnouncementsResult = loadAnnouncementsUseCase(timeProvider.now())
4144
if (loadAnnouncementsResult is Loading) {
4245
emit(listOf(LoadingIndicator))
@@ -48,14 +51,14 @@ class AnnouncementsViewModel @Inject constructor(
4851
emit(listOf(AnnouncementsEmpty))
4952
}
5053
}
51-
}
54+
}.stateIn(viewModelScope, Eagerly, emptyList())
5255

53-
val timeZoneId: LiveData<ZoneId> = liveData {
56+
val timeZoneId: StateFlow<ZoneId> = flow {
5457
val timeZoneResult = getTimeZoneUseCase(Unit)
5558
if (timeZoneResult.successOr(true)) {
5659
emit(TimeUtils.CONFERENCE_TIMEZONE)
5760
} else {
5861
emit(ZoneId.systemDefault())
5962
}
60-
}
63+
}.stateIn(viewModelScope, Eagerly, TimeUtils.CONFERENCE_TIMEZONE)
6164
}

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedAnnouncementViewBinders.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import android.view.ViewGroup
2222
import android.widget.TextView
2323
import androidx.databinding.BindingAdapter
2424
import androidx.lifecycle.LifecycleOwner
25-
import androidx.lifecycle.LiveData
2625
import androidx.recyclerview.widget.RecyclerView.ViewHolder
2726
import com.google.samples.apps.iosched.R
2827
import com.google.samples.apps.iosched.databinding.ItemFeedAnnouncementBinding
2928
import com.google.samples.apps.iosched.databinding.ItemFeedAnnouncementsHeaderBinding
3029
import com.google.samples.apps.iosched.model.Announcement
3130
import com.google.samples.apps.iosched.shared.util.TimeUtils
31+
import kotlinx.coroutines.flow.StateFlow
3232
import org.threeten.bp.ZoneId
3333
import org.threeten.bp.ZonedDateTime
3434

@@ -86,7 +86,7 @@ class AnnouncementsPreviewViewHolder(
8686

8787
// For Announcement items
8888
class AnnouncementViewBinder(
89-
private val timeZoneId: LiveData<ZoneId>,
89+
private val timeZoneId: StateFlow<ZoneId>,
9090
private val lifecycleOwner: LifecycleOwner
9191
) : FeedItemViewBinder<Announcement, AnnouncementViewHolder>(
9292
Announcement::class.java
@@ -115,7 +115,7 @@ class AnnouncementViewBinder(
115115
class AnnouncementViewHolder(
116116
private val binding: ItemFeedAnnouncementBinding,
117117
private val lifecycleOwner: LifecycleOwner,
118-
private val timeZoneId: LiveData<ZoneId>
118+
private val timeZoneId: StateFlow<ZoneId>
119119
) : ViewHolder(binding.root) {
120120

121121
fun bind(announcement: Announcement) {

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class FeedFragment : MainNavigationFragment() {
172172
val momentViewBinder = MomentViewBinder(
173173
eventListener = model,
174174
userInfo = model.userInfo,
175-
themeLiveData = model.theme
175+
theme = model.theme
176176
)
177177
val sessionsViewBinder = FeedSessionsViewBinder(model)
178178
val feedAnnouncementsHeaderViewBinder =

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedHeaderViewBinders.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ class CountdownViewBinder : FeedItemViewBinder<CountdownItem, CountdownViewHolde
6363
class MomentViewBinder(
6464
private val eventListener: FeedEventListener,
6565
private val userInfo: StateFlow<AuthenticatedUserInfo?>,
66-
private val themeLiveData: StateFlow<Theme>
66+
private val theme: StateFlow<Theme>
6767
) : FeedItemViewBinder<Moment, MomentViewHolder>(Moment::class.java) {
6868

6969
override fun createViewHolder(parent: ViewGroup): MomentViewHolder {
7070
val binding =
7171
ItemFeedMomentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
72-
return MomentViewHolder(binding, eventListener, userInfo, themeLiveData)
72+
return MomentViewHolder(binding, eventListener, userInfo, theme)
7373
}
7474

7575
override fun bindViewHolder(model: Moment, viewHolder: MomentViewHolder) {

mobile/src/main/java/com/google/samples/apps/iosched/ui/feed/FeedViewModel.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.google.samples.apps.iosched.ui.feed
1818

1919
import androidx.lifecycle.ViewModel
20-
import androidx.lifecycle.asLiveData
2120
import androidx.lifecycle.viewModelScope
2221
import androidx.navigation.NavDirections
2322
import com.google.samples.apps.iosched.R
@@ -55,6 +54,7 @@ import com.google.samples.apps.iosched.util.WhileViewSubscribed
5554
import dagger.hilt.android.lifecycle.HiltViewModel
5655
import kotlinx.coroutines.channels.Channel
5756
import kotlinx.coroutines.flow.Flow
57+
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
5858
import kotlinx.coroutines.flow.StateFlow
5959
import kotlinx.coroutines.flow.combine
6060
import kotlinx.coroutines.flow.flatMapLatest
@@ -94,9 +94,7 @@ class FeedViewModel @Inject constructor(
9494
// View All sessions and go to schedule to view the full list
9595
private const val MAX_SESSIONS = 10
9696

97-
// Indicates there is no header to show at the current time. We need this sentinel value
98-
// because our LiveData.combine extension functions interpret null values to mean the
99-
// LiveData has not returned a result, but null for the current Moment is valid.
97+
// Indicates there is no header to show at the current time.
10098
private object NoHeader
10199

102100
// Indicates there is no sessions related display on the home screen as the conference is
@@ -115,17 +113,13 @@ class FeedViewModel @Inject constructor(
115113
var isMapEnabledByRemoteConfig: Boolean = false
116114

117115
// Exposed to the view as a StateFlow but it's a one-shot operation.
118-
// TODO: Rename with timeZoneId when ScheduleViewModel is migrated
119-
val timeZoneIdFlow = flow<ZoneId> {
116+
val timeZoneId = flow<ZoneId> {
120117
if (getTimeZoneUseCase(Unit).successOr(true)) {
121118
emit(TimeUtils.CONFERENCE_TIMEZONE)
122119
} else {
123120
emit(ZoneId.systemDefault())
124121
}
125-
}.stateIn(viewModelScope, WhileViewSubscribed, TimeUtils.CONFERENCE_TIMEZONE)
126-
127-
// TODO: Replace with timeZoneIdFlow when ScheduleViewModel is migrated
128-
val timeZoneId = timeZoneIdFlow.asLiveData()
122+
}.stateIn(viewModelScope, Eagerly, TimeUtils.CONFERENCE_TIMEZONE)
129123

130124
private val loadSessionsResult: StateFlow<Result<List<UserSession>>> =
131125
signInViewModelDelegate.userId
@@ -177,7 +171,7 @@ class FeedViewModel @Inject constructor(
177171
}.stateIn(viewModelScope, WhileViewSubscribed, emptyList())
178172

179173
private val feedSessionsContainer: Flow<FeedSessions> = loadSessionsResult
180-
.combine(timeZoneIdFlow) { sessions, timeZone ->
174+
.combine(timeZoneId) { sessions, timeZone ->
181175
createFeedSessionsContainer(sessions, timeZone)
182176
}
183177

mobile/src/main/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionFeedbackFragment.kt

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import android.view.LayoutInflater
2222
import android.view.ViewGroup
2323
import androidx.appcompat.app.AppCompatDialogFragment
2424
import androidx.fragment.app.viewModels
25-
import androidx.lifecycle.Observer
2625
import androidx.recyclerview.widget.DiffUtil
2726
import androidx.recyclerview.widget.LinearLayoutManager
2827
import androidx.recyclerview.widget.ListAdapter
@@ -32,9 +31,12 @@ import com.google.samples.apps.iosched.R
3231
import com.google.samples.apps.iosched.databinding.FragmentSessionFeedbackBinding
3332
import com.google.samples.apps.iosched.databinding.ItemQuestionBinding
3433
import com.google.samples.apps.iosched.model.SessionId
34+
import com.google.samples.apps.iosched.shared.result.data
35+
import com.google.samples.apps.iosched.util.launchAndRepeatWithViewLifecycle
3536
import com.google.samples.apps.iosched.widget.SimpleRatingBar
3637
import dagger.hilt.android.AndroidEntryPoint
3738
import kotlinx.coroutines.ExperimentalCoroutinesApi
39+
import kotlinx.coroutines.flow.collect
3840

3941
@ExperimentalCoroutinesApi
4042
@AndroidEntryPoint
@@ -59,21 +61,16 @@ class SessionFeedbackFragment : AppCompatDialogFragment() {
5961
layoutManager = LinearLayoutManager(context)
6062
adapter = questionAdapter
6163
}
62-
// The lifecycle owner has to be the DialogFragment itself here.
63-
viewModel.questions.observe(
64-
this,
65-
Observer { questions ->
66-
if (questions != null && questions.isNotEmpty()) {
67-
questionAdapter.submitList(questions)
64+
65+
questionAdapter.submitList(viewModel.questions)
66+
67+
launchAndRepeatWithViewLifecycle {
68+
viewModel.userSession.collect {
69+
it.data?.let {
70+
dialog?.setTitle(it.session.title)
6871
}
6972
}
70-
)
71-
viewModel.userSession.observe(
72-
this,
73-
Observer { userSession ->
74-
dialog?.setTitle(userSession.session.title)
75-
}
76-
)
73+
}
7774
return MaterialAlertDialogBuilder(requireContext())
7875
// The actual title is set asynchronously, but there has to be some title to
7976
// initialize the view first.

0 commit comments

Comments
 (0)