Merge pull request #6316 from vector-im/fix/mna/crash-offline-lls

[Location sharing] Fix crash when starting/stopping a live when offline (PSF-1124)
This commit is contained in:
Maxime NATUREL 2022-06-20 14:00:56 +02:00 committed by GitHub
commit 32c6281dd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 268 additions and 54 deletions

1
changelog.d/6315.bugfix Normal file
View file

@ -0,0 +1 @@
[Location sharing] Fix crash when starting/stopping a live when offline

View file

@ -46,14 +46,15 @@ interface LocationSharingService {
/**
* Starts sharing live location in the room.
* @param timeoutMillis timeout of the live in milliseconds
* @return the id of the created beacon info event
* @return the result of the update of the live
*/
suspend fun startLiveLocationShare(timeoutMillis: Long): String
suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult
/**
* Stops sharing live location in the room.
* @return the result of the update of the live
*/
suspend fun stopLiveLocationShare()
suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult
/**
* Returns a LiveData on the list of current running live location shares.

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.location
/**
* Represents the result of an update of live location share like a start or a stop.
*/
sealed interface UpdateLiveLocationShareResult {
data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult
data class Failure(val error: Throwable) : UpdateLiveLocationShareResult
}

View file

@ -22,6 +22,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.session.room.location.LocationSharingService
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
@ -66,7 +67,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
return sendLiveLocationTask.execute(params)
}
override suspend fun startLiveLocationShare(timeoutMillis: Long): String {
override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult {
val params = StartLiveLocationShareTask.Params(
roomId = roomId,
timeoutMillis = timeoutMillis
@ -74,7 +75,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
return startLiveLocationShareTask.execute(params)
}
override suspend fun stopLiveLocationShare() {
override suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult {
val params = StopLiveLocationShareTask.Params(
roomId = roomId,
)

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.location
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
@ -25,7 +26,7 @@ import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject
internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, String> {
internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
data class Params(
val roomId: String,
val timeoutMillis: Long,
@ -38,7 +39,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
private val sendStateTask: SendStateTask,
) : StartLiveLocationShareTask {
override suspend fun execute(params: StartLiveLocationShareTask.Params): String {
override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
val beaconContent = MessageBeaconInfoContent(
timeout = params.timeoutMillis,
isLive = true,
@ -51,6 +52,15 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
eventType = eventType,
body = beaconContent
)
return sendStateTask.executeRetry(sendStateTaskParams, 3)
return try {
val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3)
if (eventId.isNotEmpty()) {
UpdateLiveLocationShareResult.Success(eventId)
} else {
UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event"))
}
} catch (error: Throwable) {
UpdateLiveLocationShareResult.Failure(error)
}
}
}

View file

@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, Unit> {
internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
data class Params(
val roomId: String,
)
@ -41,10 +42,10 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
private val stateEventDataSource: StateEventDataSource,
) : StopLiveLocationShareTask {
override suspend fun execute(params: StopLiveLocationShareTask.Params) {
val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return
val stateKey = beaconInfoStateEvent.stateKey ?: return
val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: return
override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return getResultForIncorrectBeaconInfoEvent()
val stateKey = beaconInfoStateEvent.stateKey ?: return getResultForIncorrectBeaconInfoEvent()
val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: return getResultForIncorrectBeaconInfoEvent()
val updatedContent = content.copy(isLive = false).toContent()
val sendStateTaskParams = SendStateTask.Params(
roomId = params.roomId,
@ -52,9 +53,21 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
body = updatedContent
)
sendStateTask.executeRetry(sendStateTaskParams, 3)
return try {
val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3)
if (eventId.isNotEmpty()) {
UpdateLiveLocationShareResult.Success(eventId)
} else {
UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event"))
}
} catch (error: Throwable) {
UpdateLiveLocationShareResult.Failure(error)
}
}
private fun getResultForIncorrectBeaconInfoEvent() =
UpdateLiveLocationShareResult.Failure(Exception("incorrect last beacon info event"))
private fun getLiveLocationBeaconInfoForUser(userId: String, roomId: String): Event? {
return EventType.STATE_ROOM_BEACON_INFO
.mapNotNull {

View file

@ -18,15 +18,14 @@ package org.matrix.android.sdk.internal.session.room.location
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.After
import org.junit.Test
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
@ -119,11 +118,11 @@ internal class DefaultLocationSharingServiceTest {
@Test
fun `live location share can be started with a given timeout`() = runTest {
coEvery { startLiveLocationShareTask.execute(any()) } returns AN_EVENT_ID
coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
val eventId = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT)
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT)
eventId shouldBeEqualTo AN_EVENT_ID
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
val expectedParams = StartLiveLocationShareTask.Params(
roomId = A_ROOM_ID,
timeoutMillis = A_TIMEOUT
@ -133,10 +132,11 @@ internal class DefaultLocationSharingServiceTest {
@Test
fun `live location share can be stopped`() = runTest {
coEvery { stopLiveLocationShareTask.execute(any()) } just runs
coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
defaultLocationSharingService.stopLiveLocationShare()
val result = defaultLocationSharingService.stopLiveLocationShare()
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
val expectedParams = StopLiveLocationShareTask.Params(
roomId = A_ROOM_ID
)

View file

@ -20,10 +20,12 @@ import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInstanceOf
import org.junit.After
import org.junit.Test
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
import org.matrix.android.sdk.test.fakes.FakeClock
@ -53,7 +55,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
}
@Test
fun `given parameters when calling the task then it is correctly executed`() = runTest {
fun `given parameters and no error when calling the task then result is success`() = runTest {
val params = StartLiveLocationShareTask.Params(
roomId = A_ROOM_ID,
timeoutMillis = A_TIMEOUT
@ -63,7 +65,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
val result = defaultStartLiveLocationShareTask.execute(params)
result shouldBeEqualTo AN_EVENT_ID
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
val expectedBeaconContent = MessageBeaconInfoContent(
timeout = params.timeoutMillis,
isLive = true,
@ -80,4 +82,33 @@ internal class DefaultStartLiveLocationShareTaskTest {
remainingRetry = 3
)
}
@Test
fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest {
val params = StartLiveLocationShareTask.Params(
roomId = A_ROOM_ID,
timeoutMillis = A_TIMEOUT
)
fakeClock.givenEpoch(AN_EPOCH)
fakeSendStateTask.givenExecuteRetryReturns("")
val result = defaultStartLiveLocationShareTask.execute(params)
result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class
}
@Test
fun `given parameters and error during event sending when calling the task then result is failure`() = runTest {
val params = StartLiveLocationShareTask.Params(
roomId = A_ROOM_ID,
timeoutMillis = A_TIMEOUT
)
fakeClock.givenEpoch(AN_EPOCH)
val error = Throwable()
fakeSendStateTask.givenExecuteRetryThrows(error)
val result = defaultStartLiveLocationShareTask.execute(params)
result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error)
}
}

View file

@ -19,11 +19,15 @@ package org.matrix.android.sdk.internal.session.room.location
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInstanceOf
import org.junit.After
import org.junit.Test
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
import org.matrix.android.sdk.test.fakes.FakeSendStateTask
@ -53,7 +57,7 @@ class DefaultStopLiveLocationShareTaskTest {
}
@Test
fun `given parameters when calling the task then it is correctly executed`() = runTest {
fun `given parameters and no error when calling the task then result is success`() = runTest {
val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID)
val currentStateEvent = Event(
stateKey = A_USER_ID,
@ -66,8 +70,9 @@ class DefaultStopLiveLocationShareTaskTest {
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID)
defaultStopLiveLocationShareTask.execute(params)
val result = defaultStopLiveLocationShareTask.execute(params)
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
val expectedBeaconContent = MessageBeaconInfoContent(
timeout = A_TIMEOUT,
isLive = false,
@ -89,4 +94,78 @@ class DefaultStopLiveLocationShareTaskTest {
stateKey = A_USER_ID
)
}
@Test
fun `given parameters and an incorrect current state event when calling the task then result is failure`() = runTest {
val incorrectCurrentStateEvents = listOf(
// no event
null,
// no stateKey
Event(
stateKey = null,
content = MessageBeaconInfoContent(
timeout = A_TIMEOUT,
isLive = true,
unstableTimestampMillis = AN_EPOCH
).toContent()
),
// incorrect content
Event(
stateKey = A_USER_ID,
content = MessageAudioContent(
msgType = "",
body = ""
).toContent()
)
)
incorrectCurrentStateEvents.forEach { currentStateEvent ->
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID)
val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID)
val result = defaultStopLiveLocationShareTask.execute(params)
result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class
}
}
@Test
fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest {
val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID)
val currentStateEvent = Event(
stateKey = A_USER_ID,
content = MessageBeaconInfoContent(
timeout = A_TIMEOUT,
isLive = true,
unstableTimestampMillis = AN_EPOCH
).toContent()
)
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
fakeSendStateTask.givenExecuteRetryReturns("")
val result = defaultStopLiveLocationShareTask.execute(params)
result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class
}
@Test
fun `given parameters and error during event sending when calling the task then result is failure`() = runTest {
val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID)
val currentStateEvent = Event(
stateKey = A_USER_ID,
content = MessageBeaconInfoContent(
timeout = A_TIMEOUT,
isLive = true,
unstableTimestampMillis = AN_EPOCH
).toContent()
)
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
val error = Throwable()
fakeSendStateTask.givenExecuteRetryThrows(error)
val result = defaultStopLiveLocationShareTask.execute(params)
result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error)
}
}

View file

@ -27,6 +27,10 @@ internal class FakeSendStateTask : SendStateTask by mockk() {
coEvery { executeRetry(any(), any()) } returns eventId
}
fun givenExecuteRetryThrows(error: Throwable) {
coEvery { executeRetry(any(), any()) } throws error
}
fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) {
coVerify { executeRetry(params, remainingRetry) }
}

View file

@ -27,7 +27,7 @@ internal class FakeStateEventDataSource {
val instance: StateEventDataSource = mockk()
fun givenGetStateEventReturns(event: Event) {
fun givenGetStateEventReturns(event: Event?) {
every {
instance.getStateEvent(
roomId = any(),

View file

@ -1293,6 +1293,10 @@ class TimelineViewModel @AssistedInject constructor(
locationSharingServiceConnection.bind(this)
}
override fun onLocationServiceError(error: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(throwable = error, showInDialog = true))
}
override fun onCleared() {
timeline.dispose()
timeline.removeAllListeners()

View file

@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import timber.log.Timber
import java.util.Timer
import java.util.TimerTask
@ -54,8 +55,9 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
/**
* Keep track of a map between beacon event Id starting the live and RoomArgs.
*/
private var roomArgsMap = mutableMapOf<String, RoomArgs>()
private var timers = mutableListOf<Timer>()
private val roomArgsMap = mutableMapOf<String, RoomArgs>()
private val timers = mutableListOf<Timer>()
var callback: Callback? = null
override fun onCreate() {
super.onCreate()
@ -89,19 +91,27 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
}
private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) {
val beaconEventId = session
val updateLiveResult = session
.getRoom(roomArgs.roomId)
?.locationSharingService()
?.startLiveLocationShare(timeoutMillis = roomArgs.durationMillis)
beaconEventId
?.takeUnless { it.isEmpty() }
?.let {
roomArgsMap[it] = roomArgs
locationTracker.requestLastKnownLocation()
updateLiveResult
?.let { result ->
when (result) {
is UpdateLiveLocationShareResult.Success -> {
roomArgsMap[result.beaconEventId] = roomArgs
locationTracker.requestLastKnownLocation()
}
is UpdateLiveLocationShareResult.Failure -> {
callback?.onServiceError(result.error)
tryToDestroyMe()
}
}
}
?: run {
Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id")
tryToDestroyMe()
}
}
@ -123,28 +133,28 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
fun stopSharingLocation(roomId: String) {
Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
// Send a new beacon info state by setting live field as false
sendStoppedBeaconInfo(roomId)
launchInIO { session ->
when (val result = sendStoppedBeaconInfo(session, roomId)) {
is UpdateLiveLocationShareResult.Success -> {
synchronized(roomArgsMap) {
val beaconIds = roomArgsMap
.filter { it.value.roomId == roomId }
.map { it.key }
beaconIds.forEach { roomArgsMap.remove(it) }
synchronized(roomArgsMap) {
val beaconIds = roomArgsMap
.filter { it.value.roomId == roomId }
.map { it.key }
beaconIds.forEach { roomArgsMap.remove(it) }
if (roomArgsMap.isEmpty()) {
Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
destroyMe()
tryToDestroyMe()
}
}
is UpdateLiveLocationShareResult.Failure -> callback?.onServiceError(result.error)
else -> Unit
}
}
}
private fun sendStoppedBeaconInfo(roomId: String) {
launchInIO { session ->
session.getRoom(roomId)
?.locationSharingService()
?.stopLiveLocationShare()
}
private suspend fun sendStoppedBeaconInfo(session: Session, roomId: String): UpdateLiveLocationShareResult? {
return session.getRoom(roomId)
?.locationSharingService()
?.stopLiveLocationShare()
}
override fun onLocationUpdate(locationData: LocationData) {
@ -178,6 +188,13 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
stopSelf()
}
private fun tryToDestroyMe() {
if (roomArgsMap.isEmpty()) {
Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
destroyMe()
}
}
private fun destroyMe() {
locationTracker.removeCallback(this)
timers.forEach { it.cancel() }
@ -209,6 +226,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
fun getService(): LocationSharingService = this@LocationSharingService
}
interface Callback {
fun onServiceError(error: Throwable)
}
companion object {
const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
}

View file

@ -25,11 +25,12 @@ import javax.inject.Inject
class LocationSharingServiceConnection @Inject constructor(
private val context: Context
) : ServiceConnection {
) : ServiceConnection, LocationSharingService.Callback {
interface Callback {
fun onLocationServiceRunning()
fun onLocationServiceStopped()
fun onLocationServiceError(error: Throwable)
}
private var callback: Callback? = null
@ -57,14 +58,21 @@ class LocationSharingServiceConnection @Inject constructor(
}
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
locationSharingService = (binder as LocationSharingService.LocalBinder).getService()
locationSharingService = (binder as LocationSharingService.LocalBinder).getService().also {
it.callback = this
}
isBound = true
callback?.onLocationServiceRunning()
}
override fun onServiceDisconnected(className: ComponentName) {
isBound = false
locationSharingService?.callback = null
locationSharingService = null
callback?.onLocationServiceStopped()
}
override fun onServiceError(error: Throwable) {
callback?.onLocationServiceError(error)
}
}

View file

@ -18,4 +18,6 @@ package im.vector.app.features.location.live.map
import im.vector.app.core.platform.VectorViewEvents
sealed interface LocationLiveMapViewEvents : VectorViewEvents
sealed interface LocationLiveMapViewEvents : VectorViewEvents {
data class Error(val error: Throwable) : LocationLiveMapViewEvents
}

View file

@ -76,6 +76,8 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeViewEvents()
views.liveLocationBottomSheetRecyclerView.configureWith(bottomSheetController, hasFixedSize = false, disableItemAnimation = true)
bottomSheetController.callback = object : LiveLocationBottomSheetController.Callback {
@ -89,6 +91,14 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
}
}
private fun observeViewEvents() {
viewModel.observeViewEvents { viewEvent ->
when (viewEvent) {
is LocationLiveMapViewEvents.Error -> displayErrorDialog(viewEvent.error)
}
}
}
override fun onResume() {
super.onResume()
setupMap()

View file

@ -80,4 +80,8 @@ class LocationLiveMapViewModel @AssistedInject constructor(
override fun onLocationServiceStopped() {
// NOOP
}
override fun onLocationServiceError(error: Throwable) {
_viewEvents.post(LocationLiveMapViewEvents.Error(error))
}
}