How can i restart livedata builder function? - android

I'm trying to use livedata builder functions. Indeed, it's so easy in use, but actually I can't understand how I can restart my coroutine. Below my part of code:
val topStoriesResult : LiveData<UIState<TopStoryWrapper>> = liveData(Dispatchers.IO) {
topStoriesRepository.getTopStoriesSetWrapper().apply {
emit(UIState.Loading)
onFailure { emit(UIState.NoData) }
onSuccess { emit(UIState.HasData(it)) }
}
}

liveData builder can't be restarted, docs say:
The liveData building block serves as a structured concurrency primitive between coroutines and LiveData. The code block starts executing when LiveData becomes active and is automatically canceled after a configurable timeout when the LiveData becomes inactive. If it is canceled before completion, it is restarted if the LiveData becomes active again. If it completed successfully in a previous run, it doesn't restart. Note that it is restarted only if canceled automatically. If the block is canceled for any other reason (e.g. throwing a CancelationException), it is not restarted.
To make code run a couple of times I can suggest to create a function and call it when you need, e.g. on button click:
class MainViewModel : ViewModel() {
val topStoriesResult: LiveData<UIState<TopStoryWrapper>> = MutableLiveData<UIState<TopStoryWrapper>>()
fun loadTopStories() = viewModelScope.launch(Dispatchers.IO) { // start a coroutine
topStoriesRepository.getTopStoriesSetWrapper().apply {
val mutableLiveData = loginResponse as MutableLiveData
// post value to LiveData
mutableLiveData.postValue(UIState.Loading)
onFailure { mutableLiveData.postValue(UIState.NoData) }
onSuccess { mutableLiveData.postValue(UIState.HasData(it)) }
}
}
}
To use viewModelScope in MainViewModel class add dependency to build.gradle file:
final LIFECYCLE_VERSION = "2.2.0-rc03" // add most recent version
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"

Related

ViewModel + Room test coverage in UnitTest

I have an unit test like this:
...
subj.mintToken(to, value, uri)
advanceUntilIdle()
...
val pendingTxFinalState = subj.uiState.value.pendingTx.count()
assertThat("Model should have a single pending tx, but has $pendingTxFinalState", pendingTxFinalState == 1)
...
The model field in ViewModel is populated by the request to cache in the init {} block. Each change in table would trigger this coroutine flow. This piece of unit test checks correctness of this functionality.
The current issue is this Flow in init {} block is triggered only on the test start when ViewModel instance is created. It does not respond on update in table.
It is important to note I don't use in test a room database neither test database, but FakeCacheRepository where behaviour of methods are emulated by flow with mocked data. However the behaviour of flow should be the same as there is still in change in underlying data.
val txPool = ConcurrentLinkedQueue<ITransaction>()
override fun createChainTx(tx: ITransaction): Flow<ITransaction> {
return flow {
txPool.add(tx)
emit(tx)
}
}
override fun getAllChainTransactions(): Flow<List<ITransaction>> {
return flow {
emit(txPool.toList())
}
}
Do you see the issue here or better way to test this?
My guess is you’re writing you’re own FakeCacheRepo and in the update function you are calling createChainTx. The value of the flow isn’t updating though because the create function doesn’t just update the value it creates a new flow instead of updating the old one. You can modify the set up to emit continuously in a loop (with some buffer delay) based on a variable. Then when you change the variable it will change what the current flow is emiting as expected.
The code example here is roughly doing that: https://developer.android.com/kotlin/flow#create
override fun createChainTx(): Flow<ITransaction> {
return flow {
while(true) {
val tx = getLatestTxValue() // Get the latest updated value from an outside source
txPool.add(tx)
emit(tx)
delay(refreshIntervalMs) // Suspends the coroutine for some time
}
}
}

Kotlin Flow not collected anymore after working initially

Basically I want to make a network request when initiated by the user, collect the Flow returned by the repository and run some code depending on the result. My current setup looks like this:
Viewmodel
private val _requestResult = MutableSharedFlow<Result<Data>>()
val requestResult = _requestResult.filterNotNull().shareIn(
scope = viewModelScope,
started = SharingStarted.WhileViewSubscribed,
replay = 0
)
fun makeRequest() {
viewModelScope.launch {
repository.makeRequest().collect { _requestResult.emit(it) }
}
}
Fragment
buttonLayout.listener = object : BottomButtonLayout.Listener {
override fun onButtonClick() {
viewModel.makeRequest()
}
}
lifecycleScope.launchWhenCreated {
viewModel.requestResult.collect { result ->
when (result) {
Result.Loading -> {
doStuff()
}
is Result.Success -> {
doDifferentStuff(result.data)
}
is Result.Failure -> {
handleError()
}
}
}
}
The first time the request is made everything seems to work. But starting with the second time the collect block in the fragment does not run anymore. The request is still made, the repository returns the flow as expected, the collect block in the viewmodel runs and emit() also seems to be executed successfully.
So what could be the problem here? Something about the coroutine scopes? Admittedly I lack any sort of deeper understanding of the matter at hand.
Also is there a more efficient way of accomplishing what I'm attempting using Kotlin Flows in general? Collecting a flow and then emitting the same flow again seems a bit counterintuitive.
Thanks in advance:)
According to the documentation there are two recommended alternatives:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
//your thing
}
}
I rather the other alternative:
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeReques().flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
I like the flowWithLifecycle shorter syntax and less boiler plate. Be carefull thar is bloking so you cant have anything after that.
The oficial docs
https://developer.android.com/topic/libraries/architecture/coroutines
Please be aware you need the lifecycle aware library.

Observe livedata with an initial timeout

I've a livedata which emits everytime there is a update in the database. When the particular screen opens, this livedata emits immediately with whatever value is there in the database. Then, a network call is made to update the database. After the database is updated, the livedata emits again. This leads to two emissions in very quick succession. Subsequent updates to the database work properly cz there is only one emission whenever the database is updated. Only the first time, there are 2 updates in very quick succession. I want to avoid that.
An idea to avoid that would be something like this. When the livedata emits, wait for Xs. If there is another emission in those Xs, discard the data from old emission and use the new one. Wait for Xs again. If there is no emission in those Xs, use the latest data.
This looks very similiar to throttling but only once. I was wondering if there's a simple way to do something like using LiveData or MediatorLiveData.
You can post delayed Runnable with timeout you want after first LiveData event.
Every LiveData update remove posted Runnable and post it again.
You can use MediatorLiveData and a boolean val for achieving this.
Create a mDbLiveData, mediator livedata mFinalLiveData and boolean mLoadedFromAPI when data from API is loaded.
On API success or failure, set mLoadedFromAPI to true;
Observe mFinalLiveData in your Activity/Fragment
LiveData<Model> mDbLiveData;
MediatorLiveData<Model> mFinalLiveData = new MediatorLiveData();
private boolean mLoadedFromAPI = false;
// Load db data in mDbLiveData
mDbLiveData = // Data from DB
// Add mDbLiveData as source in mFinaliveData
mFinalLiveData.addSource(mDbLiveData, dbData -> {
if (mLoadedFromAPI) mFinalLiveData.postValue(dbData);
});
This post helped. https://medium.com/#guilherme.devel/throttle-operator-with-livedata-and-kotlin-coroutines-ec42f8cbc0b0
I modified the solution a bit to fit my usecase:
fun <T> LiveData<T>.debounceOnce(duration: Long,
coroutineContextProvider: CoroutineContextProvider): LiveData<T> {
return MediatorLiveData<T>().also { mediatorLivedata ->
var shouldDebounce = true
var job: Job? = null
val source = this
mediatorLivedata.addSource(source) {
if (shouldDebounce) {
job?.cancel()
job = CoroutineScope(coroutineContextProvider.IO).launch {
delay(duration)
withContext(coroutineContextProvider.Main) {
mediatorLivedata.value = source.value
shouldDebounce = false
}
}
} else {
job?.cancel()
mediatorLivedata.value = source.value
}
}
}
}
open class CoroutineContextProvider #Inject constructor() {
open val Main: CoroutineContext by lazy { Dispatchers.Main }
open val IO: CoroutineContext by lazy { Dispatchers.Default }
}

Make part of coroutine continue past cancellation

I have a file managing class that can save a big file. The file manager class is an application singleton, so it outlives my UI classes. My Activity/Fragment can call the save suspend function of the file manager from a coroutine and then show success or failure in the UI. For example:
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
//In MyFileManager
suspend fun saveBigFile() {
//Set up the parameters
//...
withContext(Dispatchers.IO) {
//Save the file
//...
}
}
The problem with this approach is that I don't want the save operation to be aborted if the Activity is finished. If the activity is destroyed before the withContext block gets going, or if the withContext block has any suspension points in it, then saving will not be completed because the coroutine will be canceled.
What I want to happen is that the file is always saved. If the Activity is still around, then we can show UI updates on completion.
I thought one way to do it might be to start a new coroutineScope from the suspend function like this, but this scope still seems to get cancelled when its parent job is cancelled.
suspend fun saveBigFile() = coroutineScope {
//...
}
I thought another alternative might be to make this a regular function that updates some LiveData when it's finished. The Activity could observe the live data for the result, and since LiveData automatically removes lifecycle observers when they're destroyed, the Activity is not leaked to the FileManager. I'd like to avoid this pattern if the something less convoluted like the above can be done instead.
//In MyActivity:
private fun saveTheFile() {
val result = myFileManager.saveBigFile()
result.observe(this#MyActivity) {
myTextView.text = when (it) {
true -> "Successfully saved file"
else -> "Failed to save file"
}
}
}
//In MyFileManager
fun saveBigFile(): LiveData<Boolean> {
//Set up the parameters
//...
val liveData = MutableLiveData<Boolean>()
MainScope().launch {
val success = withContext(Dispatchers.IO) {
//Save the file
//...
}
liveData.value = success
}
return liveData
}
You can wrap the bit that you don't want to be cancelled with NonCancellable.
// May cancel here.
withContext(Dispatchers.IO + NonCancellable) {
// Will complete, even if cancelled.
}
// May cancel here.
If you have code whose lifetime is scoped to the lifetime of the whole application, then this is a use case for the GlobalScope. However, just saying GlobalScope.launch is not a good strategy because you could launch several concurrent file operations that may be in conflict (this depends on your app's details). The recommended way is to use a globally-scoped actor, in the role of an executor service.
Basically, you can say
#ObsoleteCoroutinesApi
val executor = GlobalScope.actor<() -> Unit>(Dispatchers.IO) {
for (task in channel) {
task()
}
}
And use it like this:
private fun saveTheFile() = lifecycleScope.launch {
executor.send {
try {
myFileManager.saveBigFile()
withContext(Main) {
myTextView.text = "Successfully saved file"
}
} catch (e: IOException) {
withContext(Main) {
myTextView.text = "Failed to save file"
}
}
}
}
Note that this is still not a great solution, it retains myTextView beyond its lifetime. Decoupling the UI notifications from the view is another topic, though.
actor is labeled as "obsolete coroutines API", but that's just an advance notice that it will be replaced with a more powerful alternative in a future version of Kotlin. It doesn't mean it's broken or unsupported.
I tried this, and it appears to do what I described that I wanted. The FileManager class has its own scope, though I suppose it could also be GlobalScope since it's a singleton class.
We launch a new job in its own scope from the coroutine. This is done from a separate function to remove any ambiguity about the scope of the job. I use async
for this other job so I can bubble up exceptions that the UI should respond to.
Then after launch, we await the async job back in the original scope. await() suspends until the job is completed and passes along any throws (in my case I want IOExceptions to bubble up for the UI to show an error message). So if the original scope is cancelled, its coroutine never waits for the result, but the launched job keeps rolling along until it completes normally. Any exceptions that we want to ensure are always handled should be handled within the async function. Otherwise, they won't bubble up if the original job is cancelled.
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
class MyFileManager private constructor(app: Application):
CoroutineScope by MainScope() {
suspend fun saveBigFile() {
//Set up the parameters
//...
val deferred = saveBigFileAsync()
deferred.await()
}
private fun saveBigFileAsync() = async(Dispatchers.IO) {
//Save the file
//...
}
}

Testing LiveData Transformations?

I've built a Splash Screen using Android Architecture Components and Reactive approach.
I return from Preferences LiveData object fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean>.
I have ViewModel that passes LiveData to the view and updates Preferences
val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
if (isFirstLaunch) {
preferences.isFirstLaunch = false
}
isFirstLaunch
}
In my Fragment, I observe LiveData from ViewModel
viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
if (isFirstLaunch) {
animationView.playAnimation()
} else {
navigateNext()
}
})
I would like to test my ViewModel now to see if isFirstLaunch is updated properly. How can I test it? Have I separated all layers correctly? What kind of tests would you write on this sample code?
Have I separated all layers correctly?
The layers seem reasonably separated. The logic is in the ViewModel and you're not referring to storing Android Views/Fragments/Activities in the ViewModel.
What kind of tests would you write on this sample code?
When testing your ViewModel you can write instrumentation or pure unit tests on this code. For unit testing, you might need to figure out how to make a test double for preferences, so that you can focus on the isFirstLaunch/map behavior. An easy way to do that is passing a fake preference test double into the ViewModel.
How can I test it?
I wrote a little blurb on testing LiveData Transformations, read on!
Testing LiveData Transformations
Tl;DR You can test LiveData transformation, you just need to make sure the result LiveData of the Transformation is observed.
Fact 1: LiveData doesn't emit data if it's not observed. LiveData's "lifecycle awareness" is all about avoiding extra work. LiveData knows what lifecycle state it's observers (usually Activities/Fragments) are in. This allows LiveData to know if it's being observed by anything actually on-screen. If LiveData aren't observed or if their observers are off-screen, the observers are not triggered (an observer's onChanged method isn't called). This is useful because it keeps you from doing extra work "updating/displaying" an off-screen Fragment, for example.
Fact 2: LiveData generated by Transformations must be observed for the transformation to trigger. For Transformation to be triggered, the result LiveData (in this case, isFirstLaunch) must be observed. Again, without observation, the LiveData observers aren't triggered, and neither are the transformations.
When you're unit testing a ViewModel, you shouldn't have or need access to a Fragment/Activity. If you can't set up an observer the normal way, how do you unit test?
Fact 3: In your tests, you don't need a LifecycleOwner to observe LiveData, you can use observeForever You do not need a lifecycle observer to be able to test LiveData. This is confusing because generally outside of tests (ie in your production code), you'll use a LifecycleObserver like an Activity or Fragment.
In tests you can use the LiveData method observeForever() to observer without a lifecycle owner. This observer is "always" observing and doesn't have a concept of on/off screen since there's no LifecycleOwner. You must therefore manually remove the observer using removeObserver(observer).
Putting this all together, you can use observeForever to test your Transformations code:
class ViewModelTest {
// Executes each task synchronously using Architecture Components.
// For tests and required for LiveData to function deterministically!
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
// Create observer - no need for it to do anything!
val observer = Observer<Boolean> {}
try {
// Sets up the state you're testing for in the VM
// This affects the INPUT LiveData of the transformation
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// Observe the OUTPUT LiveData forever
// Even though the observer itself doesn't do anything
// it ensures any map functions needed to calculate
// isFirstLaunch will be run.
viewModel.isFirstLaunch.observeForever(observer)
assertEquals(viewModel.isFirstLaunch.value, true)
} finally {
// Whatever happens, don't forget to remove the observer!
viewModel.isFirstLaunch.removeObserver(observer)
}
}
}
A few notes:
You need to use InstantTaskExecutorRule() to get your LiveData updates to execute synchronously. You'll need the androidx.arch.core:core-testing:<current-version> to use this rule.
While you'll often see observeForever in test code, it also sometimes makes its way into production code. Just keep in mind that when you're using observeForever in production code, you lose the benefits of lifecycle awareness. You must also make sure not to forget to remove the observer!
Finally, if you're writing a lot of these tests, the try, observe-catch-remove-code can get tedious. If you're using Kotlin, you can make an extension function that will simplify the code and avoid the possibility of forgetting to remove the observer. There are two options:
Option 1
/**
* Observes a [LiveData] until the `block` is done executing.
*/
fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
val observer = Observer<T> { }
try {
observeForever(observer)
block()
} finally {
removeObserver(observer)
}
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// observeForTesting using the OUTPUT livedata
viewModel.isFirstLaunch.observeForTesting {
assertEquals(viewModel.isFirstLaunch.value, true)
}
}
}
Option 2
#VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this#getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
#Suppress("UNCHECKED_CAST")
return data as T
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)
}
}
These options were both taken from the reactive branch of Architecture Blueprints.
It depends on what your SharedPreferencesLiveData does.
If the SharedPreferencesLiveData contains Android specific classes, you won't be able to test this correctly because JUnit won't have access to the Android specific classes.
The other issue is that to be able to observe LiveData, you need some kind of Lifecycle owner. (The this in the original post code.)
In the Unit test, the 'this' can simply be replaced with something like the following:
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
And then used in the following way:
#RunWith(MockitoJUnitRunner::class)
class ViewModelTest {
#Rule
#JvmField
val liveDataImmediateRule = InstantTaskExecutorRule()
#Test
fun viewModelShouldLoadAttributeForConsent() {
var isLaunchedEvent: Boolean = False
// Pseudo code - Create ViewModel
viewModel.isFirstLaunch.observe(lifecycle(), Observer { isLaunchedEvent = it } )
assertEquals(true, isLaunchedEvent)
}
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
}
Note: You have to have the Rule present so that the LiveData executes instantly instead of whenever it wants to.

Categories

Resources