16
16
17
17
package com.google.samples.apps.iosched.ui.map
18
18
19
- import androidx.lifecycle.LiveData
20
- import androidx.lifecycle.MediatorLiveData
21
- import androidx.lifecycle.MutableLiveData
22
- import androidx.lifecycle.Transformations
23
19
import androidx.lifecycle.ViewModel
24
20
import androidx.lifecycle.viewModelScope
25
21
import com.google.android.gms.maps.CameraUpdate
@@ -34,14 +30,22 @@ import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions
34
30
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
35
31
import com.google.samples.apps.iosched.shared.domain.prefs.MyLocationOptedInUseCase
36
32
import com.google.samples.apps.iosched.shared.domain.prefs.OptIntoMyLocationUseCase
37
- import com.google.samples.apps.iosched.shared.result.Event
38
33
import com.google.samples.apps.iosched.shared.result.successOr
39
34
import com.google.samples.apps.iosched.shared.result.updateOnSuccess
35
+ import com.google.samples.apps.iosched.shared.util.tryOffer
40
36
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
37
+ import com.google.samples.apps.iosched.util.WhileViewSubscribed
41
38
import com.google.samples.apps.iosched.widget.BottomSheetBehavior
42
39
import dagger.hilt.android.lifecycle.HiltViewModel
40
+ import kotlinx.coroutines.channels.Channel
43
41
import kotlinx.coroutines.flow.MutableStateFlow
42
+ import kotlinx.coroutines.flow.StateFlow
43
+ import kotlinx.coroutines.flow.collect
44
44
import kotlinx.coroutines.flow.combine
45
+ import kotlinx.coroutines.flow.map
46
+ import kotlinx.coroutines.flow.onEach
47
+ import kotlinx.coroutines.flow.receiveAsFlow
48
+ import kotlinx.coroutines.flow.stateIn
45
49
import kotlinx.coroutines.launch
46
50
import javax.inject.Inject
47
51
@@ -62,18 +66,24 @@ class MapViewModel @Inject constructor(
62
66
BuildConfig .MAP_VIEWPORT_BOUND_NE
63
67
)
64
68
65
- private val _mapVariant = MutableLiveData <MapVariant >( )
66
- val mapVariant = Transformations .distinctUntilChanged( _mapVariant )
69
+ private val _mapVariant = MutableStateFlow <MapVariant ?>( null )
70
+ val mapVariant: StateFlow < MapVariant ?> = _mapVariant
67
71
68
- private val _mapCenterEvent = MutableLiveData <Event <CameraUpdate >>()
69
- val mapCenterEvent: LiveData <Event <CameraUpdate >>
70
- get() = _mapCenterEvent
72
+ private val _mapCenterEvent = Channel <CameraUpdate >(Channel .CONFLATED )
73
+ val mapCenterEvent = _mapCenterEvent .receiveAsFlow()
71
74
72
- private val loadGeoJsonResult = MutableLiveData <GeoJsonData >( )
75
+ private val loadGeoJsonResult = MutableStateFlow <GeoJsonData ?>( null )
73
76
74
- private val _geoJsonLayer = MediatorLiveData <GeoJsonLayer ?>()
75
- val geoJsonLayer: LiveData <GeoJsonLayer ?>
76
- get() = _geoJsonLayer
77
+ val geoJsonLayer: StateFlow <GeoJsonLayer ?> = loadGeoJsonResult
78
+ .onEach { data ->
79
+ if (data != null ) {
80
+ // TODO: Remove side effects and make these reactive
81
+ hasLoadedFeatures = true
82
+ setMapFeatures(data.featureMap)
83
+ }
84
+ }.map { data ->
85
+ data?.geoJsonLayer
86
+ }.stateIn(viewModelScope, WhileViewSubscribed , null )
77
87
78
88
private val featureLookup: MutableMap <String , GeoJsonFeature > = mutableMapOf ()
79
89
private var hasLoadedFeatures = false
@@ -82,14 +92,23 @@ class MapViewModel @Inject constructor(
82
92
private val focusZoomLevel = BuildConfig .MAP_CAMERA_FOCUS_ZOOM
83
93
private var currentZoomLevel = 16 // min zoom level supported
84
94
85
- private val _bottomSheetStateEvent = MediatorLiveData <Event <Int >>()
86
- val bottomSheetStateEvent: LiveData <Event <Int >>
87
- get() = _bottomSheetStateEvent
88
- private val _selectedMarkerInfo = MutableLiveData <MarkerInfo ?>()
89
- val selectedMarkerInfo: LiveData <MarkerInfo ?>
90
- get() = _selectedMarkerInfo
95
+ private val _bottomSheetStateEvent = Channel <Int >(Channel .CONFLATED )
96
+ val bottomSheetStateEvent = _bottomSheetStateEvent .receiveAsFlow()
91
97
92
- private val myLocationOptedIn = MutableStateFlow <Boolean >(false )
98
+ init {
99
+ viewModelScope.launch {
100
+ mapVariant.collect {
101
+ // When the map variant changes, the selected feature might not be present in the
102
+ // new variant, so hide the feature detail.
103
+ dismissFeatureDetails()
104
+ }
105
+ }
106
+ }
107
+
108
+ private val _selectedMarkerInfo = MutableStateFlow <MarkerInfo ?>(null )
109
+ val selectedMarkerInfo: StateFlow <MarkerInfo ?> = _selectedMarkerInfo
110
+
111
+ private val myLocationOptedIn = MutableStateFlow (false )
93
112
94
113
val showMyLocationOption = userInfo.combine(myLocationOptedIn) { info, optedIn ->
95
114
// Show the button to enable "My Location" when the user is an on-site attendee and he/she
@@ -101,17 +120,6 @@ class MapViewModel @Inject constructor(
101
120
viewModelScope.launch {
102
121
myLocationOptedIn.value = myLocationOptedInUseCase(Unit ).successOr(false )
103
122
}
104
- _geoJsonLayer .addSource(loadGeoJsonResult) { data ->
105
- hasLoadedFeatures = true
106
- setMapFeatures(data.featureMap)
107
- _geoJsonLayer .value = data.geoJsonLayer
108
- }
109
-
110
- // When the map variant changes, the selected feature might not be present in the new
111
- // variant, so hide the feature detail.
112
- _bottomSheetStateEvent .addSource(mapVariant) {
113
- dismissFeatureDetails()
114
- }
115
123
}
116
124
117
125
fun optIntoMyLocation (optIn : Boolean = true) {
@@ -128,7 +136,7 @@ class MapViewModel @Inject constructor(
128
136
// The geo json layer is tied to the GoogleMap, so we should release it.
129
137
hasLoadedFeatures = false
130
138
featureLookup.clear()
131
- _geoJsonLayer .value = null
139
+ loadGeoJsonResult .value = null
132
140
}
133
141
134
142
fun loadMapFeatures (googleMap : GoogleMap ) {
@@ -144,7 +152,7 @@ class MapViewModel @Inject constructor(
144
152
private fun setMapFeatures (features : Map <String , GeoJsonFeature >) {
145
153
featureLookup.clear()
146
154
featureLookup.putAll(features)
147
- updateFeaturesVisiblity (currentZoomLevel.toFloat())
155
+ updateFeaturesVisibility (currentZoomLevel.toFloat())
148
156
// if we have a pending request to highlight a feature, resolve it now
149
157
val featureId = requestedFeatureId ? : return
150
158
requestedFeatureId = null
@@ -156,11 +164,11 @@ class MapViewModel @Inject constructor(
156
164
val zoomInt = zoom.toInt()
157
165
if (currentZoomLevel != zoomInt) {
158
166
currentZoomLevel = zoomInt
159
- updateFeaturesVisiblity (zoom)
167
+ updateFeaturesVisibility (zoom)
160
168
}
161
169
}
162
170
163
- private fun updateFeaturesVisiblity (zoom : Float ) {
171
+ private fun updateFeaturesVisibility (zoom : Float ) {
164
172
// Don't hide the marker if it's currently being focused on by the user
165
173
val selectedId = selectedMarkerInfo.value?.id
166
174
featureLookup.values.forEach { feature ->
@@ -185,7 +193,7 @@ class MapViewModel @Inject constructor(
185
193
val geometry = feature.geometry as ? GeoJsonPoint ? : return
186
194
// center map on the requested feature.
187
195
val update = CameraUpdateFactory .newLatLngZoom(geometry.coordinates, focusZoomLevel)
188
- _mapCenterEvent .value = Event (update)
196
+ _mapCenterEvent .tryOffer (update)
189
197
190
198
// publish feature data
191
199
val title = feature.getProperty(" title" )
@@ -198,14 +206,14 @@ class MapViewModel @Inject constructor(
198
206
)
199
207
200
208
// bring bottom sheet into view
201
- _bottomSheetStateEvent .value = Event (BottomSheetBehavior .STATE_COLLAPSED )
209
+ _bottomSheetStateEvent .tryOffer (BottomSheetBehavior .STATE_COLLAPSED )
202
210
203
211
// Analytics
204
212
analyticsHelper.logUiEvent(title, AnalyticsActions .MAP_MARKER_SELECT )
205
213
}
206
214
207
215
fun dismissFeatureDetails () {
208
- _bottomSheetStateEvent .value = Event (BottomSheetBehavior .STATE_HIDDEN )
216
+ _bottomSheetStateEvent .tryOffer (BottomSheetBehavior .STATE_HIDDEN )
209
217
_selectedMarkerInfo .value = null
210
218
}
211
219
0 commit comments