Add InAppUpdateViewModel
authorDa Risk <da_risk@geekorum.com>
Thu, 01 Aug 2019 16:28:52 -0700
changeset 368 4d59d6446953
parent 367 d1f97d535e68
child 369 b1d8597fd437
Add InAppUpdateViewModel
app/src/main/java/com/geekorum/ttrss/in_app_update/InAppUpdateViewModel.kt
app/src/test/java/com/geekorum/ttrss/in_app_update/InAppUpdateViewModelTest.kt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/main/java/com/geekorum/ttrss/in_app_update/InAppUpdateViewModel.kt	Thu Aug 01 16:28:52 2019 -0700
@@ -0,0 +1,79 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekttrss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekttrss.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.in_app_update
+
+import android.app.Activity
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.liveData
+import androidx.lifecycle.map
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+
+@UseExperimental(ExperimentalCoroutinesApi::class)
+class InAppUpdateViewModel @Inject constructor(
+    private val updateManager: InAppUpdateManager
+) : ViewModel() {
+
+    val isUpdateAvailable: LiveData<Boolean> = liveData {
+        val result = updateManager.getUpdateAvailability()
+        emit(result == UpdateAvailability.UPDATE_AVAILABLE)
+    }
+
+    private val updateState: MutableLiveData<UpdateState> = MutableLiveData<UpdateState>().apply {
+        value = UpdateState(UpdateState.Status.UNKNOWN)
+    }
+
+    val isUpdateReadyToInstall = updateState.map {
+        it.status == UpdateState.Status.DOWNLOADED
+    }
+
+    private var updateJob: Job? = null
+
+    fun startUpdateFlow(activity: Activity, requestCode: Int) {
+        if (updateJob?.isActive == true) {
+            return
+        }
+
+        updateJob = viewModelScope.launch {
+            updateManager.startUpdate(activity, requestCode).collect {
+                updateState.value = it
+            }
+        }
+    }
+
+    fun cancelUpdateFlow() {
+        updateJob?.cancel()
+    }
+
+
+    fun completeUpdate() {
+        updateManager.completeUpdate()
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/test/java/com/geekorum/ttrss/in_app_update/InAppUpdateViewModelTest.kt	Thu Aug 01 16:28:52 2019 -0700
@@ -0,0 +1,106 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekttrss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekttrss.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.in_app_update
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import com.google.android.play.core.install.model.InstallErrorCode
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
+import io.mockk.verifyOrder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.setMain
+import org.junit.Rule
+import java.util.concurrent.Executors
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+
+@UseExperimental(ExperimentalCoroutinesApi::class)
+class InAppUpdateViewModelTest {
+
+    @get:Rule
+    val archRule = InstantTaskExecutorRule()
+
+    private val mainThreadSurrogate =  Executors.newSingleThreadExecutor {
+        Thread(it, "UI Thread")
+    }.asCoroutineDispatcher()
+
+    lateinit var subject: InAppUpdateViewModel
+    lateinit var updateManager: InAppUpdateManager
+
+    @BeforeTest
+    fun setUp() {
+        Dispatchers.setMain(mainThreadSurrogate)
+        updateManager = mockk()
+        subject = InAppUpdateViewModel(updateManager)
+    }
+
+    @Test
+    fun testUpdateAvailable() {
+        val observer: Observer<Boolean> = mockObserver()
+        coEvery { updateManager.getUpdateAvailability() } returns UpdateAvailability.UPDATE_AVAILABLE
+        subject.isUpdateAvailable.observeForever(observer)
+
+        verify { observer.onChanged(true) }
+    }
+
+    @Test
+    fun testNoUpdateAvailable() {
+        val observer: Observer<Boolean> = mockObserver()
+        coEvery { updateManager.getUpdateAvailability() } returns UpdateAvailability.NO_UPDATE
+        subject.isUpdateAvailable.observeForever(observer)
+
+        verify { observer.onChanged(false) }
+    }
+
+    @Test
+    fun testAnUpdateFlowGoingToReadyToInstall() {
+        val observer: Observer<Boolean> = mockObserver()
+        coEvery { updateManager.startUpdate(any(), any()) } returns flowOf(
+            UpdateState(UpdateState.Status.UNKNOWN),
+            UpdateState(UpdateState.Status.PENDING, InstallErrorCode.NO_ERROR),
+            UpdateState(UpdateState.Status.DOWNLOADING, InstallErrorCode.NO_ERROR),
+            UpdateState(UpdateState.Status.DOWNLOADED, InstallErrorCode.NO_ERROR)
+        )
+        subject.isUpdateReadyToInstall.observeForever(observer)
+
+        subject.startUpdateFlow(mockk(), 42)
+
+        verifyOrder {
+            observer.onChanged(false)
+            observer.onChanged(true)
+        }
+    }
+
+    private inline fun <reified T : Observer<K>, reified K : Any> mockObserver(): T {
+        val observer: T = mockk()
+        every { observer.onChanged(any()) } just Runs
+        return observer
+    }
+
+}