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

Commit f09f4c6

Browse files
author
Manuel Vivo
committed
Migrate MainActivity to Flow
Fixes: b/187703234 Change-Id: I60df7e2f211f5dab6534a7187ffbc4a6e615e6a8
1 parent 27e5e0f commit f09f4c6

File tree

11 files changed

+128
-194
lines changed

11 files changed

+128
-194
lines changed

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

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import android.view.Menu
2424
import androidx.activity.viewModels
2525
import androidx.appcompat.app.AppCompatActivity
2626
import androidx.appcompat.widget.Toolbar
27-
import androidx.lifecycle.Observer
27+
import androidx.lifecycle.Lifecycle
28+
import androidx.lifecycle.lifecycleScope
29+
import androidx.lifecycle.repeatOnLifecycle
2830
import androidx.navigation.NavController
2931
import androidx.navigation.fragment.NavHostFragment
3032
import androidx.navigation.ui.AppBarConfiguration
@@ -39,7 +41,6 @@ import com.google.samples.apps.iosched.shared.di.CodelabsEnabledFlag
3941
import com.google.samples.apps.iosched.shared.di.ExploreArEnabledFlag
4042
import com.google.samples.apps.iosched.shared.di.MapFeatureEnabledFlag
4143
import com.google.samples.apps.iosched.shared.domain.ar.ArConstants
42-
import com.google.samples.apps.iosched.shared.result.EventObserver
4344
import com.google.samples.apps.iosched.ui.messages.SnackbarMessage
4445
import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager
4546
import com.google.samples.apps.iosched.ui.signin.SignInDialogFragment
@@ -48,6 +49,8 @@ import com.google.samples.apps.iosched.util.HeightTopWindowInsetsListener
4849
import com.google.samples.apps.iosched.util.signin.FirebaseAuthErrorCodeConverter
4950
import com.google.samples.apps.iosched.util.updateForTheme
5051
import dagger.hilt.android.AndroidEntryPoint
52+
import kotlinx.coroutines.flow.collect
53+
import kotlinx.coroutines.launch
5154
import timber.log.Timber
5255
import java.util.UUID
5356
import javax.inject.Inject
@@ -107,10 +110,6 @@ class MainActivity : AppCompatActivity(), NavigationHost {
107110
private lateinit var navHostFragment: NavHostFragment
108111
private var currentNavId = NAV_ID_NONE
109112

110-
// For sending pinned sessions as JSON to the AR module
111-
private var pinnedSessionsJson: String? = null
112-
private var canSignedInUserDemoAr: Boolean = false
113-
114113
override fun onCreate(savedInstanceState: Bundle?) {
115114
super.onCreate(savedInstanceState)
116115

@@ -149,42 +148,36 @@ class MainActivity : AppCompatActivity(), NavigationHost {
149148
navigateTo(requestedNavId)
150149
}
151150

152-
viewModel.theme.observe(this, Observer(::updateForTheme))
153-
154-
viewModel.navigateToSignInDialogAction.observe(
155-
this,
156-
EventObserver {
157-
openSignInDialog()
158-
}
159-
)
160-
161-
viewModel.navigateToSignOutDialogAction.observe(
162-
this,
163-
EventObserver {
164-
openSignOutDialog()
165-
}
166-
)
167-
viewModel.arCoreAvailability.observe(
168-
this,
169-
Observer {
170-
// Start observing ArCoreAvailability otherwise the value isn't updated
171-
Timber.d("ArCoreAvailability = $it")
172-
}
173-
)
174-
viewModel.pinnedSessionsJson.observe(
175-
this,
176-
Observer {
177-
// Need to observe the pinnedSessions otherwise it's considered as inactive
178-
pinnedSessionsJson = it
179-
}
180-
)
181-
viewModel.canSignedInUserDemoAr.observe(
182-
this,
183-
Observer {
184-
Timber.d("Signed in user can demo ar = $it")
185-
canSignedInUserDemoAr = it
151+
lifecycleScope.launch {
152+
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
153+
launch {
154+
viewModel.navigationActions.collect { action ->
155+
when (action) {
156+
MainNavigationAction.OpenSignIn -> openSignInDialog()
157+
MainNavigationAction.OpenSignOut -> openSignOutDialog()
158+
}
159+
}
160+
}
161+
launch {
162+
viewModel.theme.collect { theme ->
163+
updateForTheme(theme)
164+
}
165+
}
166+
// AR-related Flows
167+
launch {
168+
viewModel.arCoreAvailability.collect { result ->
169+
// Do nothing - activate flow
170+
Timber.d("ArCoreAvailability = $result")
171+
}
172+
}
173+
launch {
174+
viewModel.pinnedSessionsJson.collect { /* Do nothing - activate flow */ }
175+
}
176+
launch {
177+
viewModel.canSignedInUserDemoAr.collect { /* Do nothing - activate flow */ }
178+
}
186179
}
187-
)
180+
}
188181
}
189182

190183
private fun configureNavMenu(menu: Menu) {
@@ -275,8 +268,8 @@ class MainActivity : AppCompatActivity(), NavigationHost {
275268
ArActivity::class.java
276269
).apply {
277270
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
278-
putExtra(ArConstants.CAN_SIGNED_IN_USER_DEMO_AR, canSignedInUserDemoAr)
279-
putExtra(ArConstants.PINNED_SESSIONS_JSON_KEY, pinnedSessionsJson)
271+
putExtra(ArConstants.CAN_SIGNED_IN_USER_DEMO_AR, viewModel.canSignedInUserDemoAr.value)
272+
putExtra(ArConstants.PINNED_SESSIONS_JSON_KEY, viewModel.pinnedSessionsJson.value)
280273
}
281274
startActivity(intent)
282275
}

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

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

1919
import android.content.Context
20-
import androidx.lifecycle.LiveData
21-
import androidx.lifecycle.MutableLiveData
2220
import androidx.lifecycle.ViewModel
23-
import androidx.lifecycle.liveData
24-
import androidx.lifecycle.switchMap
21+
import androidx.lifecycle.viewModelScope
22+
import com.google.ar.core.ArCoreApk
2523
import com.google.samples.apps.iosched.shared.domain.ar.LoadArDebugFlagUseCase
2624
import com.google.samples.apps.iosched.shared.domain.sessions.LoadPinnedSessionsJsonUseCase
27-
import com.google.samples.apps.iosched.shared.result.Event
2825
import com.google.samples.apps.iosched.shared.result.Result
29-
import com.google.samples.apps.iosched.ui.ar.ArCoreAvailabilityLiveData
26+
import com.google.samples.apps.iosched.shared.util.tryOffer
3027
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
3128
import com.google.samples.apps.iosched.ui.theme.ThemedActivityDelegate
29+
import com.google.samples.apps.iosched.util.WhileViewSubscribed
3230
import dagger.hilt.android.lifecycle.HiltViewModel
3331
import dagger.hilt.android.qualifiers.ApplicationContext
32+
import kotlinx.coroutines.channels.Channel
33+
import kotlinx.coroutines.delay
34+
import kotlinx.coroutines.flow.StateFlow
3435
import kotlinx.coroutines.flow.collect
36+
import kotlinx.coroutines.flow.flow
37+
import kotlinx.coroutines.flow.receiveAsFlow
38+
import kotlinx.coroutines.flow.stateIn
39+
import kotlinx.coroutines.flow.transformLatest
3540
import javax.inject.Inject
3641

3742
@HiltViewModel
@@ -45,47 +50,54 @@ class MainActivityViewModel @Inject constructor(
4550
SignInViewModelDelegate by signInViewModelDelegate,
4651
ThemedActivityDelegate by themedActivityDelegate {
4752

48-
private val _navigateToSignInDialogAction = MutableLiveData<Event<Unit>>()
49-
val navigateToSignInDialogAction: LiveData<Event<Unit>>
50-
get() = _navigateToSignInDialogAction
53+
private val _navigationActions = Channel<MainNavigationAction>(Channel.CONFLATED)
54+
val navigationActions = _navigationActions.receiveAsFlow()
5155

52-
private val _navigateToSignOutDialogAction = MutableLiveData<Event<Unit>>()
53-
val navigateToSignOutDialogAction: LiveData<Event<Unit>>
54-
get() = _navigateToSignOutDialogAction
55-
56-
val pinnedSessionsJson: LiveData<String> = currentUserInfo.switchMap { user ->
56+
val pinnedSessionsJson: StateFlow<String> = currentUserInfoFlow.transformLatest { user ->
5757
val uid = user?.getUid()
58-
liveData {
59-
if (uid != null) {
60-
loadPinnedSessionsUseCase(uid).collect { result ->
61-
if (result is Result.Success) {
62-
emit(result.data)
63-
}
64-
}
65-
} else {
66-
emit("")
67-
}
68-
}
69-
}
70-
71-
val canSignedInUserDemoAr: LiveData<Boolean> = currentUserInfo.switchMap {
72-
liveData {
73-
emit(false)
74-
loadArDebugFlagUseCase(Unit).collect { result ->
58+
if (uid != null) {
59+
loadPinnedSessionsUseCase(uid).collect { result ->
7560
if (result is Result.Success) {
7661
emit(result.data)
7762
}
7863
}
64+
} else {
65+
emit("")
7966
}
80-
}
67+
}.stateIn(viewModelScope, WhileViewSubscribed, "")
68+
69+
val canSignedInUserDemoAr: StateFlow<Boolean> = currentUserInfoFlow.transformLatest {
70+
val result = loadArDebugFlagUseCase(Unit)
71+
if (result is Result.Success) {
72+
emit(result.data)
73+
}
74+
}.stateIn(viewModelScope, WhileViewSubscribed, false)
8175

82-
val arCoreAvailability = ArCoreAvailabilityLiveData(context)
76+
val arCoreAvailability: StateFlow<ArCoreApk.Availability?> = flow<ArCoreApk.Availability> {
77+
var result: ArCoreApk.Availability? = null
78+
while (result == null) {
79+
val availability = ArCoreApk.getInstance().checkAvailability(context)
80+
// If the availability is transient, we need to call availability check again
81+
// as in https://developers.google.com/ar/develop/java/enable-arcore#check_supported
82+
if (availability.isTransient) {
83+
delay(1000)
84+
} else {
85+
result = availability
86+
emit(result)
87+
}
88+
}
89+
}.stateIn(viewModelScope, WhileViewSubscribed, null)
8390

8491
fun onProfileClicked() {
8592
if (isSignedIn()) {
86-
_navigateToSignOutDialogAction.value = Event(Unit)
93+
_navigationActions.tryOffer(MainNavigationAction.OpenSignOut)
8794
} else {
88-
_navigateToSignInDialogAction.value = Event(Unit)
95+
_navigationActions.tryOffer(MainNavigationAction.OpenSignIn)
8996
}
9097
}
9198
}
99+
100+
sealed class MainNavigationAction {
101+
object OpenSignIn : MainNavigationAction()
102+
object OpenSignOut : MainNavigationAction()
103+
}

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

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

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import com.google.samples.apps.iosched.model.Moment.Companion.CTA_SIGNIN
3434
import com.google.samples.apps.iosched.model.Theme
3535
import com.google.samples.apps.iosched.shared.data.signin.AuthenticatedUserInfo
3636
import com.google.samples.apps.iosched.util.executeAfter
37+
import kotlinx.coroutines.flow.StateFlow
3738

3839
// Countdown is shown before the Keynote
3940
object CountdownItem
@@ -63,7 +64,7 @@ class CountdownViewBinder : FeedItemViewBinder<CountdownItem, CountdownViewHolde
6364
class MomentViewBinder(
6465
private val eventListener: FeedEventListener,
6566
private val userInfoLiveData: LiveData<AuthenticatedUserInfo?>,
66-
private val themeLiveData: LiveData<Theme>
67+
private val themeLiveData: StateFlow<Theme>
6768
) : FeedItemViewBinder<Moment, MomentViewHolder>(Moment::class.java) {
6869

6970
override fun createViewHolder(parent: ViewGroup): MomentViewHolder {
@@ -88,7 +89,7 @@ class MomentViewHolder(
8889
private val binding: ItemFeedMomentBinding,
8990
private val eventListener: FeedEventListener,
9091
private val userInfoLiveData: LiveData<AuthenticatedUserInfo?>,
91-
private val themeLiveData: LiveData<Theme>
92+
private val themeLiveData: StateFlow<Theme>
9293
) : ViewHolder(binding.root) {
9394

9495
fun bind(item: Moment) {

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

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

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

19-
import androidx.lifecycle.LiveData
20-
import androidx.lifecycle.liveData
2119
import com.google.samples.apps.iosched.model.Theme
20+
import com.google.samples.apps.iosched.shared.di.ApplicationScope
2221
import com.google.samples.apps.iosched.shared.domain.settings.GetThemeUseCase
2322
import com.google.samples.apps.iosched.shared.domain.settings.ObserveThemeModeUseCase
2423
import com.google.samples.apps.iosched.shared.result.Result.Success
2524
import com.google.samples.apps.iosched.shared.result.successOr
26-
import kotlinx.coroutines.ExperimentalCoroutinesApi
27-
import kotlinx.coroutines.flow.collect
25+
import kotlinx.coroutines.CoroutineScope
26+
import kotlinx.coroutines.flow.SharingStarted
27+
import kotlinx.coroutines.flow.StateFlow
28+
import kotlinx.coroutines.flow.map
29+
import kotlinx.coroutines.flow.stateIn
2830
import kotlinx.coroutines.runBlocking
2931
import javax.inject.Inject
3032

@@ -45,7 +47,7 @@ interface ThemedActivityDelegate {
4547
/**
4648
* Allows observing of the current theme
4749
*/
48-
val theme: LiveData<Theme>
50+
val theme: StateFlow<Theme>
4951

5052
/**
5153
* Allows querying of the current theme synchronously
@@ -54,16 +56,14 @@ interface ThemedActivityDelegate {
5456
}
5557

5658
class ThemedActivityDelegateImpl @Inject constructor(
57-
private val observeThemeUseCase: ObserveThemeModeUseCase,
59+
@ApplicationScope externalScope: CoroutineScope,
60+
observeThemeUseCase: ObserveThemeModeUseCase,
5861
private val getThemeUseCase: GetThemeUseCase
5962
) : ThemedActivityDelegate {
6063

61-
@ExperimentalCoroutinesApi
62-
override val theme: LiveData<Theme> = liveData {
63-
observeThemeUseCase(Unit).collect {
64-
emit(it.successOr(Theme.SYSTEM))
65-
}
66-
}
64+
override val theme: StateFlow<Theme> = observeThemeUseCase(Unit).map {
65+
it.successOr(Theme.SYSTEM)
66+
}.stateIn(externalScope, SharingStarted.Eagerly, Theme.SYSTEM)
6767

6868
override val currentTheme: Theme
6969
get() = runBlocking { // Using runBlocking to execute this coroutine synchronously

mobile/src/main/res/layout/item_feed_moment.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<data>
2323

2424
<import type="androidx.lifecycle.LiveData" />
25+
<import type="kotlinx.coroutines.flow.StateFlow" />
2526

2627
<variable
2728
name="moment"
@@ -33,7 +34,7 @@
3334

3435
<variable
3536
name="theme"
36-
type="LiveData&lt;com.google.samples.apps.iosched.model.Theme&gt;" />
37+
type="StateFlow&lt;com.google.samples.apps.iosched.model.Theme&gt;" />
3738

3839
<variable
3940
name="eventListener"

0 commit comments

Comments
 (0)