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

Commit edcd3b4

Browse files
author
Manuel Vivo
committed
Migrate reservations to Flows
Fixes: b/186632852 Change-Id: I8dce181abb24a36ce3f8153a223c77de4d9815f2
1 parent 4a50c1f commit edcd3b4

File tree

5 files changed

+155
-79
lines changed

5 files changed

+155
-79
lines changed

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
@@ -88,13 +88,13 @@ class MomentViewHolder(
8888
private val binding: ItemFeedMomentBinding,
8989
private val eventListener: FeedEventListener,
9090
private val userInfo: StateFlow<AuthenticatedUserInfo?>,
91-
private val themeLiveData: StateFlow<Theme>
91+
private val theme: StateFlow<Theme>
9292
) : ViewHolder(binding.root) {
9393

9494
fun bind(item: Moment) {
9595
binding.executeAfter {
9696
moment = item
97-
theme = themeLiveData
97+
theme = theme
9898
userInfo = userInfo
9999
eventListener = this@MomentViewHolder.eventListener
100100
}

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ import androidx.fragment.app.viewModels
2828
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2929
import com.google.samples.apps.iosched.R
3030
import com.google.samples.apps.iosched.model.SessionId
31-
import com.google.samples.apps.iosched.shared.result.EventObserver
31+
import com.google.samples.apps.iosched.util.launchAndRepeatWithViewLifecycle
3232
import com.google.samples.apps.iosched.util.makeBold
3333
import dagger.hilt.android.AndroidEntryPoint
34+
import kotlinx.coroutines.flow.collect
3435

3536
/**
3637
* Dialog that confirms the user really wants to cancel their reservation
@@ -81,24 +82,22 @@ class RemoveReservationDialogFragment : AppCompatDialogFragment() {
8182
val sessionId = arguments?.getString(SESSION_ID_KEY)
8283
if (sessionId == null) {
8384
dismiss()
84-
} else {
85-
viewModel.setSessionId(sessionId)
8685
}
8786
return super.onCreateView(inflater, container, savedInstanceState)
8887
}
8988

9089
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
9190
super.onViewCreated(view, savedInstanceState)
92-
viewModel.snackBarMessage.observe(
93-
viewLifecycleOwner,
94-
EventObserver {
91+
92+
launchAndRepeatWithViewLifecycle {
93+
viewModel.snackbarMessages.collect {
9594
// Using Toast instead of Snackbar as it's easier for DialogFragment
9695
Toast.makeText(
9796
view.context, it.messageId,
9897
if (it.longDuration) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
9998
).show()
10099
}
101-
)
100+
}
102101
}
103102

104103
private fun formatRemoveReservationMessage(

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

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

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

19-
import androidx.lifecycle.MutableLiveData
19+
import androidx.lifecycle.SavedStateHandle
2020
import androidx.lifecycle.ViewModel
2121
import androidx.lifecycle.viewModelScope
2222
import com.google.samples.apps.iosched.R
@@ -26,49 +26,54 @@ import com.google.samples.apps.iosched.shared.domain.sessions.LoadUserSessionUse
2626
import com.google.samples.apps.iosched.shared.domain.users.ReservationActionUseCase
2727
import com.google.samples.apps.iosched.shared.domain.users.ReservationRequestAction
2828
import com.google.samples.apps.iosched.shared.domain.users.ReservationRequestParameters
29-
import com.google.samples.apps.iosched.shared.result.Event
3029
import com.google.samples.apps.iosched.shared.result.Result.Error
3130
import com.google.samples.apps.iosched.shared.result.data
32-
import com.google.samples.apps.iosched.shared.util.cancelIfActive
3331
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
3432
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
33+
import com.google.samples.apps.iosched.util.WhileViewSubscribed
3534
import dagger.hilt.android.lifecycle.HiltViewModel
36-
import kotlinx.coroutines.Job
35+
import kotlinx.coroutines.channels.BufferOverflow
36+
import kotlinx.coroutines.channels.Channel
37+
import kotlinx.coroutines.flow.Flow
38+
import kotlinx.coroutines.flow.StateFlow
3739
import kotlinx.coroutines.flow.collect
40+
import kotlinx.coroutines.flow.receiveAsFlow
41+
import kotlinx.coroutines.flow.shareIn
42+
import kotlinx.coroutines.flow.stateIn
43+
import kotlinx.coroutines.flow.transform
3844
import kotlinx.coroutines.launch
3945
import javax.inject.Inject
4046

4147
@HiltViewModel
4248
class RemoveReservationViewModel @Inject constructor(
49+
savedStateHandle: SavedStateHandle,
4350
signInViewModelDelegate: SignInViewModelDelegate,
4451
private val loadUserSessionUseCase: LoadUserSessionUseCase,
4552
private val reservationActionUseCase: ReservationActionUseCase
4653
) : ViewModel(), SignInViewModelDelegate by signInViewModelDelegate {
4754

48-
private var loadUserSessionJob: Job? = null
49-
private val _sessionId = MutableLiveData<SessionId>()
55+
private val sessionId: SessionId? = savedStateHandle.get<SessionId>("session_id")
5056

51-
private val _userSession = MutableLiveData<UserSession>()
57+
private val userIdStateFlow: StateFlow<String?> =
58+
userId.stateIn(viewModelScope, WhileViewSubscribed, null)
5259

53-
private val _snackBarMessage = MutableLiveData<Event<SnackbarMessage>>()
54-
val snackBarMessage = _snackBarMessage
55-
56-
fun setSessionId(sessionId: SessionId) {
57-
_sessionId.value = sessionId
58-
loadUserSessionJob.cancelIfActive()
59-
loadUserSessionJob = viewModelScope.launch {
60-
loadUserSessionUseCase(userIdValue to sessionId).collect { loadResult ->
61-
loadResult.data?.userSession?.let {
62-
_userSession.value = it
63-
}
60+
val userSession: StateFlow<UserSession?> = userIdStateFlow.transform { userId ->
61+
if (userId != null && sessionId != null) {
62+
loadUserSessionUseCase(userId to sessionId).collect { loadResult ->
63+
emit(loadResult.data?.userSession)
6464
}
6565
}
66-
}
66+
}.stateIn(viewModelScope, WhileViewSubscribed, null)
67+
68+
private val _snackbarMessages = Channel<SnackbarMessage>(3, BufferOverflow.DROP_LATEST)
69+
val snackbarMessages: Flow<SnackbarMessage> =
70+
_snackbarMessages.receiveAsFlow().shareIn(viewModelScope, WhileViewSubscribed)
6771

6872
fun removeReservation() {
69-
val userId = userIdValue ?: return
70-
val sessionId = _sessionId.value ?: return
71-
val userSession = _userSession.value
73+
if (sessionId == null) return
74+
val userId = userIdStateFlow.value ?: return
75+
val userSession = userSession.value
76+
7277
viewModelScope.launch {
7378
val result = reservationActionUseCase(
7479
ReservationRequestParameters(
@@ -79,13 +84,12 @@ class RemoveReservationViewModel @Inject constructor(
7984
)
8085
)
8186
if (result is Error) {
82-
_snackBarMessage.value =
83-
Event(
84-
SnackbarMessage(
85-
messageId = R.string.reservation_error,
86-
longDuration = true
87-
)
87+
_snackbarMessages.send(
88+
SnackbarMessage(
89+
messageId = R.string.reservation_error,
90+
longDuration = true
8891
)
92+
)
8993
}
9094
}
9195
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.samples.apps.iosched.ui.reservation
18+
19+
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
20+
import androidx.lifecycle.SavedStateHandle
21+
import com.google.samples.apps.iosched.model.SessionId
22+
import com.google.samples.apps.iosched.model.TestDataRepository
23+
import com.google.samples.apps.iosched.shared.data.session.DefaultSessionRepository
24+
import com.google.samples.apps.iosched.shared.data.userevent.DefaultSessionAndUserEventRepository
25+
import com.google.samples.apps.iosched.shared.data.userevent.UserEventDataSource
26+
import com.google.samples.apps.iosched.shared.domain.sessions.LoadUserSessionUseCase
27+
import com.google.samples.apps.iosched.shared.domain.sessions.StarReserveNotificationAlarmUpdater
28+
import com.google.samples.apps.iosched.shared.domain.users.ReservationActionUseCase
29+
import com.google.samples.apps.iosched.test.data.MainCoroutineRule
30+
import com.google.samples.apps.iosched.test.data.TestData
31+
import com.google.samples.apps.iosched.test.data.runBlockingTest
32+
import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate
33+
import com.google.samples.apps.iosched.ui.schedule.TestUserEventDataSource
34+
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
35+
import com.nhaarman.mockito_kotlin.any
36+
import com.nhaarman.mockito_kotlin.mock
37+
import com.nhaarman.mockito_kotlin.verify
38+
import kotlinx.coroutines.flow.first
39+
import org.junit.Assert.assertEquals
40+
import org.junit.Before
41+
import org.junit.Rule
42+
import org.junit.Test
43+
44+
/**
45+
* Unit tests for the [RemoveReservationViewModel].
46+
*/
47+
class RemoveReservationViewModelTest {
48+
49+
// Executes tasks in the Architecture Components in the same thread
50+
@get:Rule
51+
var instantTaskExecutorRule = InstantTaskExecutorRule()
52+
53+
// Overrides Dispatchers.Main used in Coroutines
54+
@get:Rule
55+
var coroutineRule = MainCoroutineRule()
56+
57+
private val signInViewModelDelegate = FakeSignInViewModelDelegate()
58+
private val alarmUpdater: StarReserveNotificationAlarmUpdater = mock()
59+
60+
@Before
61+
fun setUp() {
62+
signInViewModelDelegate.loadUser("123")
63+
}
64+
65+
@Test
66+
fun dataIsLoaded() = coroutineRule.runBlockingTest {
67+
val removeReservationViewModel = createRemoveReservationViewModel(TestData.session0.id)
68+
69+
val userSession = removeReservationViewModel.userSession.first()
70+
assertEquals("0", userSession?.session?.id)
71+
}
72+
73+
@Test
74+
fun reservationIsPlaced() = coroutineRule.runBlockingTest {
75+
val removeReservationViewModel = createRemoveReservationViewModel(TestData.session0.id)
76+
77+
val userSession = removeReservationViewModel.userSession.first()
78+
assertEquals("0", userSession?.session?.id)
79+
80+
removeReservationViewModel.removeReservation()
81+
82+
verify(alarmUpdater).updateSession(any(), any())
83+
}
84+
85+
private fun createRemoveReservationViewModel(
86+
sessionId: SessionId = TestData.session0.id,
87+
signInViewModelDelegate: SignInViewModelDelegate = this.signInViewModelDelegate,
88+
loadUserSessionUseCase: LoadUserSessionUseCase = createTestLoadUserSessionUseCase(),
89+
reservationActionUseCase: ReservationActionUseCase = createReservationActionUseCase()
90+
) = RemoveReservationViewModel(
91+
savedStateHandle = SavedStateHandle(mapOf("session_id" to sessionId)),
92+
signInViewModelDelegate = signInViewModelDelegate,
93+
loadUserSessionUseCase = loadUserSessionUseCase,
94+
reservationActionUseCase = reservationActionUseCase
95+
)
96+
97+
private fun createTestLoadUserSessionUseCase(
98+
userEventDataSource: UserEventDataSource = TestUserEventDataSource()
99+
): LoadUserSessionUseCase {
100+
val sessionRepository = DefaultSessionRepository(TestDataRepository)
101+
val userEventRepository = DefaultSessionAndUserEventRepository(
102+
userEventDataSource,
103+
sessionRepository
104+
)
105+
return LoadUserSessionUseCase(userEventRepository, coroutineRule.testDispatcher)
106+
}
107+
108+
private fun createReservationActionUseCase() = object : ReservationActionUseCase(
109+
DefaultSessionAndUserEventRepository(
110+
TestUserEventDataSource(), DefaultSessionRepository(TestDataRepository)
111+
),
112+
alarmUpdater,
113+
coroutineRule.testDispatcher
114+
) {}
115+
}

shared/src/main/java/com/google/samples/apps/iosched/shared/di/IOSchedViewModelFactory.kt

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)