`I am having a hard time getting the middle? value of a stateFlow and checking using assertTrue
Here is my code for testing
fun initData() = viewModelScope.launch {
modifiableUiState.emit(UiState.Loading)
try {
testSomeThing()
modifiableUiState.emit(UiState.Loaded)
} catch (e: Exception) {
modifiableUiState.emit(UiState.Error(e))
} finally {
modifiableUiState.emit(UiState.Idle)
}
}
and my test code
#Test
fun load_store_data_succeed() = runTest {
// given
var uiState: PaySettingBalanceNotificationViewModel.UiState? = null
val collectJob = launch {
viewModel.uiState.collect { uiState = it }
}
// when
viewModel.initMandatoryData()
runCurrent()
// then
assertTrue(uiState is PaySettingBalanceNotificationViewModel.UiState.InitialDataLoaded)
collectJob.cancel()
}
since in finally block it will emit Idle, I can't check if the value has changed to InitialDataLoaded. Is there a way I can check the history? of a value for testing?`
Related
I am trying to get list of todos from database with livedata however, while debugging it always shows null for value. I have provided my files below.
My Dao:
#Query("SELECT * FROM todo_table WHERE IIF(:isCompleted IS NULL, 1, isCompleted = :isCompleted)")
fun getTodos(isCompleted: Boolean?): LiveData<List<Todo>>
My ViewModel:
private var _allTodoList = MutableLiveData<List<Todo>>()
var allTodoList: LiveData<List<Todo>> = _allTodoList
init {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
fun onFilterClick(todoType: Constants.TodoType) {
when (todoType) {
Constants.TodoType.ALL -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.COMPLETED -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(true)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.INCOMPLETE -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(false)
_allTodoList.postValue(list.value)
}
}
}
}
My MainActivity:
val allTodoList = viewModel.allTodoList.observeAsState()
allTodoList.value?.run {//value is always null
if (!isNullOrEmpty()) {
...
} else {
...
}
}
While debugging I found that allTodoList.value is always null however, when I manually run same query in app inspection I the get the desired results.
You can simplify your code, see if it works.
ViewModel only needs this:
val allTodoList: LiveData<List<Todo>> = todoRepository.getTodos(null)
MainActivity:
val allTodoList by viewModel.allTodoList.observeAsState()
if (!allTodoList.isNullOrEmpty()) {
...
} else {
...
}
You are not observing the LiveData you get from Room.
YourDao.getTodos() and LiveData.getValue() are not suspend functions, so you get the current value, which is null because Room has not yet fetched the values from SQLite.
A possible solution would be to set the todo type as a live data itself and use a switchMap transformation in the ViewModel :
private val todoType = MutableLiveData<Constants.TodoType>(Constants.TodoType.ALL)
val allTodoList: LiveData<List<Todo>> = androidx.lifecycle.Transformations.switchMap(todoType) { newType ->
val typeAsBoolean = when(newType) {
Constants.TodoType.ALL -> null
Constants.TodoType.COMPLETED -> true
Constants.TodoType.INCOMPLETE -> false
else -> throw IllegalArgumentException("Not a possible value")
}
// create the new wrapped LiveData
// the transformation takes care of subscribing to it
// (and unsubscribing to the old one)
todoRepository.getTodos(typeAsBoolean)
}
fun onFilterClick(todoType: Constants.TodoType) {
// triggers the transformation
todoType.setValue(todoType)
}
This is in fact the exact use case demonstrated in the reference doc
I have the following setup
Service
// ItunesService
suspend fun searchItunesPodcast(#Query("term") term: String): Response<PodcastResponse>
Repository
// ItunesRepo
override suspend fun searchByTerm(term: String) = withContext(ioDispatcher) {
return#withContext itunesService.searchItunesPodcast(term)
}
ViewModel
fun searchPodcasts(term: String) {
viewModelScope.launch {
_res.value = Result.loading()
try {
val response = itunesRepo.searchByTerm(term)
if (response.isSuccessful) { // Nothing from here when no internet
_res.value = Result.success(response.body())
} else {
_res.value = Result.error(response.errorBody().toString())
}
} catch (e: Exception) {
_res.value = Result.exception(e)
}
}
}
Everything works great until i turn off mobile data/internet on my testing device. _res value stuck on Loading state. I have tried adding break point at if (response.isSuccessful) when there is no internet and it seams like val response = itunesRepo.searchByTerm(term) never returns how can I fix this
I switched to using Flow api on my Repository
override suspend fun searchPodcasts(term: String) = flow {
emit(Result.Loading)
try {
val res = itunesService.searchItunesPodcast(term)
if (res.isSuccessful)
emit(Result.Success(res.body()))
else
emit(Result.Error("Generic error: ${res.code()}"))
} catch (e: Exception) {
emit(Result.Error("Unexpected error", e))
}
}.flowOn(ioDispatcher)
Then collect the results on my ViewModels
I've got the following class:
class SomeApiRemoteSource (private var someApi: SomeApi)
{
private val someData = MutableLiveData<Map<String, String>>()
init
{
GlobalScope.launch(Dispatchers.IO)
{
try
{
val response = someApi.getSomeData(SomeApi.API_KEY).awaitResponse()
if(response.isSuccessful)
someData.postValue(response.body()?.data)
else
someData.postValue(emptyMap())
}
catch (e: Exception)
{
Log.e("ds error", e.message!!)
someData.postValue(emptyMap())
}
}
}
fun getSomeData(): MutableLiveData<Map<String, String>>
{
return someData
}
}
Everything works fine, but is there a way to get the same result without using the init? What if I need to run another API getter call? Should I just run another coroutine right below that 1st one? That seems inefficient.
You can create a function containing your coroutine and you can call it any number of times.
class SomeApiRemoteSource (private var someApi: SomeApi)
{
private val someData = MutableLiveData<Map<String, String>>()
private var apiJob: Job? = null
init
{
getDataFromAPI()
}
fun getDataFromAPI(){
// Cancel previous job if already running
apiJob?.cancel()
apiJob = GlobalScope.launch(Dispatchers.IO)
{
try
{
val response = someApi.getSomeData(SomeApi.API_KEY).awaitResponse()
if(response.isSuccessful)
someData.postValue(response.body()?.data)
else
someData.postValue(emptyMap())
}
catch (e: Exception)
{
Log.e("ds error", e.message!!)
someData.postValue(emptyMap())
}
}
}
fun getSomeData(): MutableLiveData<Map<String, String>>
{
return someData
}
}
I got it. Took some inspiration from previous answers.
class FixerRemoteSource (private var fixerApi: FixerApi)
{
suspend fun getAllSupportedSymbols(): Map<String, String>
{
val response = fixerApi.getAllSupportedSymbols(FixerApi.API_KEY)
return if(response.isSuccessful) response.body()?.symbols!! else emptyMap()
}
}
The problem that was blocking me was that the app kept crashing while I debugged the coroutine stuff. Removed the breaking points out of the coroutines and everything was fine.
I am try to cancel to api request if user calls api to fast then only the latest api should return the result all previous requests should be discarded but this isn't working anyone knows the solution please help thanks
class CartViewModel(val store: Account) : BaseViewModel() {
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation.let {
if (it != null) {
if (it.isActive) {
requestCalculation!!.cancel()
}
}
}
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
isLoading.postValue(true)
try {
val order = CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
refreshOrder()
} catch (e: Exception) {
exception.postValue(e.localizedMessage ?: e.toString())
}
}
}
}
The order of cancellation and execution is wrong. When the function starts, requestCalculation is null, so it cannot be canceled. Make sure you start first the coroutine and cancel it later. For example:
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
delay(10_000)
// do your work...
}
// now the job can be canceled
requestCalculation?.cancel()
}
Adding a check after api call this.isActive {return#launch} finally worked for me...
fun recalculate() {
calculationRequest?.cancel()
isLoading.postValue(true)
calculationRequest = viewModelScope.launch(Dispatchers.IO) {
try {
val order =
CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
// this check is the solution *******
if (!this.isActive) {return#launch}
val catalog = CatalogManager.shared().catalog
} catch (e: Exception) {
}
}
}
Is there anything similar in Kotlin that provides same ability as the Swift keyword 'defer' ?
What the defer key word does is, it ensure that the code inside a defer block get executed before returning from a function.
Below is an example imagining that defer keyword existed in Kotlin.
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean {
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
In the case above, regardless of at what point the function returns, the block inside defer will get executed and timestamp's value will get updated, just before the function ends.
I know Java there is the finally {} keyword used along with try{} catch{}, but it's is not exactly what defer offers.
There's no such keyword in Kotlin, but you can make a construct yourself that will work quite similarly. Something like this (note that this does not handle exceptions in the deferred blocks):
class Deferrable {
private val actions: MutableList<() -> Unit> = mutableListOf()
fun defer(f: () -> Unit) {
actions.add(f)
}
fun execute() {
actions.forEach { it() }
}
}
fun <T> defer(f: (Deferrable) -> T): T {
val deferrable = Deferrable()
try {
return f(deferrable)
} finally {
deferrable.execute()
}
}
Then you can use it like this:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = defer { d ->
d.defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
The closest equivalent is try/finally. catch is not necessary if there's no exceptions thrown.
try {
println("do something")
// ... the rest of your method body here
}
finally {
println("Don't forget about me!");
}
In Swift, defer is usually used to ensure you don't forget to clean up some kind of resource or another (file handle, database connection, shared memory map, etc.). For this purpose, Kotlin use with, which takes a closure, to which the resource is passed as an argument. The resource is valid for the lifetime of the closure, and is automatically closed at the end.
FileWriter("test.txt")
.use { it.write("something") }
// File is closed by now
Solution with exception handling:
class DeferContext {
private val list = mutableListOf<() -> Unit>()
fun defer(payload: () -> Unit) {
list += payload
}
/** lombok `#Cleanup` analog */
fun AutoCloseable.deferClose() = apply {
defer { close() }
}
fun executeDeferred(blockError: Throwable?) {
var error: Throwable? = blockError
for (element in list.reversed()) {
try {
element()
} catch (e: Throwable) {
if (error == null) {
error = e
} else {
error.addSuppressed(e)
}
}
}
error?.let { throw it }
}
}
inline fun <T> deferBlock(payload: DeferContext.() -> T): T {
val context = DeferContext()
var error: Throwable? = null
var result: T? = null
try {
result = context.payload()
} catch (e: Throwable) {
error = e
} finally {
context.executeDeferred(error)
}
return result as T
}
IMHO, main point of defer functionality is execution of deferred actions regardless of previously thrown exceptions.
usage:
deferBlock {
defer { println("block exited") }
val stream = FileInputStream("/tmp/a").deferClose()
}
I came across the same question today.
While I think the answer provided by marstran is good, I decided to refactor it a little bit.
fun <T> deferred(f: ((() -> Unit) -> Unit) -> T): T {
val actions: MutableList<() -> Unit> = mutableListOf()
try {
return f(actions::add)
} finally {
actions.asReversed().forEach { it() }
}
}
I got rid of the Deferrable class by using the list directly in the deffered function. This also solves the fact that the whole Deferrable object was passed to the calling code needing to call it.defer/d.defer. In this version the add method of the mutable list is directly passed into the lambda allowing to have a code that is closer to its go/swift version.
To address the suggestion given by mvndaai to use Stack I decided to call .asReversed() on the list. Maybe there is a LI-FO type in kotlin that is also available in non JVM variants, but if not I think this is a good solution.
the given sample would look like:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = deferred { defer ->
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
If the class is Closeable you can use use block:
class MyClass : Closeable {
var timeStamp = 0L
override fun close() {
timeStamp = System.currentTimeMillis()
}
fun test(): Boolean {
this.use {
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
}