I am trying to implement kotlin stateflow, but not able to know the reason why it is not working.
Current output:
verificatio 34567
Expected Output:
verificatio 34567
verificatio failed
package stateflowDuplicate
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val firebasePhoneVerificationListener = FirebaseOTPVerificationOperation1()
val oTPVerificationViewModal = OTPVerificationViewModal1(firebasePhoneVerificationListener)
oTPVerificationViewModal.fail()
}
class OTPVerificationViewModal1(private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1) {
init {
startPhoneNumberVerification()
setUpListener()
}
suspend fun fail(){
firebasePhoneVerificationListener.fail()
}
private fun startPhoneNumberVerification() {
firebasePhoneVerificationListener.initiatePhoneVerification("34567")
}
private fun setUpListener() {
runBlocking {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
}
}
Second class
package stateflowDuplicate
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
class FirebaseOTPVerificationOperation1 {
private val _phoneVerificationFailed = MutableStateFlow<String?>(null)
val phoneVerificationFailed: StateFlow<String?>
get() = _phoneVerificationFailed
fun initiatePhoneVerification(phoneNumber: String) {
_phoneVerificationFailed.value = phoneNumber
}
suspend fun fail() {
delay(200L)
_phoneVerificationFailed.value = "failed"
}
}
Tried to understand the concept from these links,
Link1
Link2
You have to start a new coroutine to call collect because the coroutine will keep collecting values until its Job gets cancelled. Don't use runBlocking builder for that, use launch builder instead:
private fun setUpListener() = launch {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
Now to make it work you need to implement CoroutineScope interface in your class. You can do it like this:
class OTPVerificationViewModal1(
private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1
): CoroutineScope by CoroutineScope(Dispatchers.Default) {
...
}
If you run it now you will get this output:
verificatio 34567
verificatio failed
Related
I'm trying to write unit testing for my ViewModel but I don't know how to deal with LiveData functions.
Specifically I'm not able to validate all the values that receive the LiveData Observer.
Regarding I have a Flow Use case that emit values and then is pased as a LiveData, what is the best approach to test operation function?
In the code below you can find that I'm only able to read the value "endLoading", but I want to check all the values: "startLoading", "Hello Dummy $input", "endLoading"
MainViewModel.kt
class MainViewModel(val useCase: DummyUseCase = DummyUseCase()): ViewModel() {
fun operation(value: Int): LiveData<String> = useCase.invoke(value)
.transform { response ->
emit(response)
}.onStart {
emit("startLoading")
}.catch {
emit("ERROR")
}.onCompletion {
emit("endLoading")
}.asLiveData(viewModelScope.coroutineContext)
}
MainViewModelTest.kt
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
#ExperimentalCoroutinesApi
class MainViewModelTest {
//region Setup
#get:Rule
val rule = InstantTaskExecutorRule()
private val testDispatcher = TestCoroutineDispatcher()
#MockK private lateinit var stateObserver: Observer<String>
#MockK private lateinit var useCase: DummyUseCase
private lateinit var viewModel: MainViewModel
#Before
fun setup() {
MockKAnnotations.init(this, relaxUnitFun = true)
Dispatchers.setMain(testDispatcher)
viewModel = MainViewModel(useCase)
}
#After
fun teardown() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
//endregion
#Test // AAA testing
fun `when my flow succeeds, return a state String`() {
runBlocking {
//Arrange
val input = 10
coEvery { useCase.invoke(input) }.returns(flow {
emit("Hello Dummy $input")
})
//Act
val actual = viewModel.operation(input).apply {
observeForever(stateObserver)
}
//Assert
// I want to assert here every value received in the observer of the "actual" LiveData
// How? :(
assertNotNull(actual.value) // is always "endLoading"
}
}
}
You can test the LiveData using a custom Observer<T> implementation. Create an observer which records all emmited values and lets you assert against the history.
The Observer which records the values may look like this:
class TestableObserver<T> : Observer<T> {
private val history: MutableList<T> = mutableListOf()
override fun onChanged(value: T) {
history.add(value)
}
fun assertAllEmitted(values: List<T>) {
assertEquals(values.count(), history.count())
history.forEachIndexed { index, t ->
assertEquals(values[index], t)
}
}
}
You can assert if all given values were emitted by the LiveData using the assertAllEmitted(...) function.
The test function will use an instance of the TestableObserver class instead of a mocked one:
#Test // AAA testing
fun `when my flow succeeds, return a state String`() {
runBlocking {
//Arrange
val stateObserver = TestableObserver<String>()
val input = 10
coEvery { useCase.invoke(input) }.returns(flow {
emit("Hello Dummy $input")
})
//Act
val actual = viewModel.operation(input).apply {
observeForever(stateObserver)
}
//Assert
stateObserver.assertAllEmitted(
listOf(
"startLoading",
"Hello Dummy 10",
"endLoading"
)
)
}
}
Asserting the history of LiveData may be possible using mocking frameworks and assertion frameworks however, I think implementing this testable observer is more readable.
I am trying to listen to my ViewModels MutableStateFlow from my FlutterSceneView. But I get the following error when trying to set the listener from the views init:
Suspend function 'listenToBackgroundColor' should be called only from a coroutine or another suspend function
class FlutterSceneView(context: Context, private val viewModel: FlutterSceneViewModelType): PlatformView {
private val context = context
private val sceneView = SceneView(context)
init {
listenToBackgroundColor() // Error here
}
private suspend fun listenToBackgroundColor() {
viewModel.colorFlow.collect {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}
}
}
My ViewModel:
interface FlutterSceneViewModelType {
var colorFlow: MutableStateFlow<String>
}
class FlutterSceneViewModel(private val database: Database): FlutterSceneViewModelType, ViewModel() {
override var colorFlow = MutableStateFlow<String>("#FFFFFF")
init {
listenToBackgroundColorFlow()
}
private fun listenToBackgroundColorFlow() {
database.backgroundColorFlow.watch {
colorFlow.value = it.hex
}
}
}
the .watch call is a helper I have added so that this can be exposed to iOS using Kotlin multi-platform, it looks as follows but I can use collect instead if necessary:
fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
I resolved this by using viewModel context:
private fun listenToBackgroundColor() {
viewModel.colorFlow.onEach {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}.launchIn(viewModel.viewModelScope)
}
I had to import the following into my ViewModel:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
from:
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
I am trying to play with actor builder construct in kotlin. i have written the code below to send and receive a message from actor.
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
class GreetingsMessage(val to: String, val greetings: CompletableDeferred<String>)
fun CoroutineScope.newGreeter(greet: String) = actor<GreetingsMessage> {
channel.consumeEach {
it.greetings.complete("$greet ${it.to}")
}
}
fun main() {
runBlocking {
val greeter = newGreeter("Hello")
val greetingsMessage = GreetingsMessage("World", CompletableDeferred())
launch(Dispatchers.Default) {
greeter.send(greetingsMessage)
}
launch(Dispatchers.Default) {
println(greetingsMessage.greetings.await())
greeter.close()
}
}
}
this code works as expected.
but the code below is not, as it is hanging the program.
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.consumeEach
class GreetingsMessage(val to: String, val greetings: CompletableDeferred<String>)
suspend fun newGreeter(greet: String) = coroutineScope {
actor<GreetingsMessage> {
channel.consumeEach {
it.greetings.complete("$greet ${it.to}")
}
}
}
fun main() {
runBlocking {
val greeter = newGreeter("Hello")
val greetingsMessage = GreetingsMessage("World", CompletableDeferred())
launch(Dispatchers.Default) {
greeter.send(greetingsMessage)
}
launch(Dispatchers.Default) {
println(greetingsMessage.greetings.await())
greeter.close()
}
}
}
with the slight modification to the code by making newGreeter function as suspending function and enclosing the function by coroutineScope the call to newGreeter method is blocking the thread and its hanging the program. I believe newGreeter as an extension function to CoroutineScope and as a suspending function enclosed inside coroutineScope should work exactly the same.
I want to know the difference between the two approach and why the second approach is hanging the program.
I tried the same thing with produce function and here also i found the call to suspend function to get ReceieveChannel is blocking the thread, where as the same produce construct used as an extension function is working as expected
this code is non blocking
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun CoroutineScope.produceIntegers(n: Int) = produce<Int> {
for (i in 1..n)
send(i)
close()
}
fun main() {
runBlocking {
val intChan = produceIntegers(10)
launch {
for (i in intChan)
println(i)
}
}
}
where as this is blocking on the call to produceIntegers method
package com.byteobject.prototype.kotlin
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
suspend fun produceIntegers(n: Int) = coroutineScope {
produce<Int> {
for (i in 1..n)
send(i)
close()
}
}
fun main() {
runBlocking {
val intChan = produceIntegers(10)
launch {
for (i in intChan)
println(i)
}
}
}
The problem is that coroutineScope { } creates a new blocking scope (structured concurrency) - waits until all launched coroutines have completed.
See: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
this function returns as soon as the given block and all its children coroutines are completed.
On the other side, the extension function just uses the coroutinescope that is in the context (receiver).
I'm writing Espresso UI test which mocks viewModel, referring GithubBrowserSample
what is the use of "TaskExecutorWithIdlingResourceRule", declaring Junit Rule will take care of IdlingResource?
Even after referring this "TaskExecutorWithIdlingResourceRule" class in my project whenever I build, compiler doesn't throw any error but when I run the test case it shows the Unresolved Error(s)
TaskExecutorWithIdlingResourceRule.kt
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import org.junit.runner.Description
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
class TaskExecutorWithIdlingResourceRule : CountingTaskExecutorRule() {
// give it a unique id to workaround an espresso bug where you cannot register/unregister
// an idling resource w/ the same name.
private val id = UUID.randomUUID().toString()
private val idlingResource: IdlingResource = object : IdlingResource {
override fun getName(): String {
return "architecture components idling resource $id"
}
override fun isIdleNow(): Boolean {
return this#TaskExecutorWithIdlingResourceRule.isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
callbacks.add(callback)
}
}
private val callbacks = CopyOnWriteArrayList<IdlingResource.ResourceCallback>()
override fun starting(description: Description?) {
IdlingRegistry.getInstance().register(idlingResource)
super.starting(description)
}
override fun finished(description: Description?) {
drainTasks(10, TimeUnit.SECONDS)
callbacks.clear()
IdlingRegistry.getInstance().unregister(idlingResource)
super.finished(description)
}
override fun onIdle() {
super.onIdle()
for (callback in callbacks) {
callback.onTransitionToIdle()
}
}
}
Mocktest
#RunWith(AndroidJUnit4::class)
class MockTest {
#Rule
#JvmField
var activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
private lateinit var viewModel: SeriesFragmentViewModel
private val uiModelList = mutableListOf<SeriesBaseUIModel>()
private val seriesMutableLiveData = MutableLiveData<List<SeriesBaseUIModel>>()
private val seriesFragment = SeriesFragment()
#Before
fun init(){
viewModel = mock(SeriesFragmentViewModel::class.java)
`when`(viewModel.seriesLiveData).thenReturn(seriesMutableLiveData)
ViewModelUtil.createFor(viewModel)
activityRule.activity.setFragment(seriesFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#Test
fun testLoading()
{
//Thread.sleep(3000)
uiModelList.add(ProgressUIModel())
seriesMutableLiveData.postValue(uiModelList.toList())
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText(R.string.pod_series_header_title_text)))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_description), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText("Hello")))
Thread.sleep(5000)
}
}
I am using MVP pattern on a Kotlin Project. I have a Presenter class:
import com.google.gson.Gson
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import org.jetbrains.anko.coroutines.experimental.bg
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
async(UI){
val data = bg {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}
this presenter class working fine on Kotlin 1.2.71, but I can't get it working on Kotlin 1.3.0.
I updated Kotlin version in project's build.gradle, removed "experimental coroutines" and added kotlin coroutine core dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
and this is my current code:
import com.google.gson.Gson
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
async(UI){
val data = bg {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}
Error mainly on async, UI, and bg function:
unresolved reference: async
unresolved reference: UI
unresolved reference: bg
How can I get this to work on Kotlin 1.3.0? for any help, thanks in advance.
you must use GlobalScope.launch instead of launch ,GlobalScope.async instead of async
Dispatcher.main instead of UI
coroutineBasics
Your code has several layers of problems:
You're using async, but you don't await on it. You should be using launch instead.
You're using the pre-coroutines facility of bg, equivalent to async
You immediately await on bg, which means you should be using withContext(Default) instead
(new with Kotlin 1.3) You aren't applying structured concurrency
This is how your code should look in Kotlin 1.3:
fun CoroutineScope.getTeamList(league: String?) {
view.showLoading()
this.launch {
val data = withContext(Dispatchers.IO) {
gson.fromJson(apiRepository.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.teams)
view.hideLoading()
}
}
You should call your function with the coroutine scope appropriate to your situation. A typical approach is tying it to your activity:
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterJob = Job()
}
override fun onDestroy() {
super.onDestroy()
masterJob.cancel()
}
}
Also add
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
Here is a fix ( I don't know if it is the best way to do it) :
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
CoroutineScope(Dispatchers.Main).launch {
val data = async {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}