I'm using the Preference APIs that return flows.
There are cases when I need to combine values from different preferences. I know I can use
flow.combine.
Since the method where I need to combine is a suspend function and the value from the flows do not need to flow anywhere anymore, I could also define something like this:
suspend fun <T> Flow<T>.currentValue(): T {
var res: T? = null
this.collect {
res = it
}
return res!!
}
and then just read one value at at time:
val val1 = flow1.currentValue()
val val2 = flow2.currentValue()
val val3 = flow3.currentValue()
Is there a better way to do it? Or should I just use the combine method?
You don't need to define a currentValue function. There is already a Flow.first() function that returns the first value of the Flow. Since these Flows are designed to always emit an initial value with the current value of the preference, this will work fine.
Side note, your currentValue() function will never return because collect doesn't return until the Flow is complete.
Whether you want to individually call first() on each Flow or combine all of them and call first() on the combined Flow is up to you. I don't think it's any cleaner either way.
Related
I have an object where I wish to create hot StateFlow objects from a filtered cold SharedFlow. The intent is that the SharedFlow is an event channel of data changes, but all data can be retrieved to get the current state. This means for a given field, I can find the current state, and then monitor the SharedFlow to get state changes.
I would like to provide an API that (as an example) converts the SharedFlow into a StateFlow in a manner as follows:
var myVariable = DEFAULT_VALUE
val mySharedFlow = MutableSharedFlow<Int>()
val myStateFlow = mySharedFlow
.filter { it < 42 }
.asStateFlow(myVariable)// <- Convert to a StateFlow given a default value
This is obviously an overly simplified example, but my situation is more complex, and currently I have to invoke a function when ever a field changes, but currently I do the following:
myObj.onChange.collect(handler)
handler(myObj.getCurrentValue)
fun handler(data: Int) {
// Handle data change
}
But I would prefer to use a Hot StateFlow and remove the need for the second function call. Especially since many consumers of this are small bits of code (mostly just a single expression) that do not need to be in their own function context, and should just be simple lambdas.
Tenfour04 answered my question in his comment. The function I needed is called stateIn().
I'm trying to use coroutines in my code since in one of my function, I need to do multiple network calls and wait for its result. Below is a portion of my activity code:
class SellerDeliveryRegister : AppCompatActivity() {
lateinit var sellerDeliveryVM:SellerDeliveryVM
sellerDeliveryVM = ViewModelProviders.of(this).get(SellerDeliveryVM::class.java)
var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url)
}
And below is a portion of my sellerDeliveryVM ViewModel code:
class SellerDeliveryVM: ViewModel() {
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): Unit = withContext(Dispatchers.IO){
var uploadMotorImage1Result = "-1"; var uploadMotorImage2Result = "-1";
viewModelScope.launch(Dispatchers.IO) {
uploadMotorImage1Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage1Url)
}
uploadMotorImage2Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage2Url)
}
return#launch;
}
return#withContext
}
}
Please take note that previously uploadVehiclesImages is a 'normal' function that doesn't use coroutine, so now I'm converting it to use coroutine.
Below are the problems I'm facing:
Line var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url) inside my SellerDeliveryRegister class returns this error:
Suspend function 'uploadVehiclesImages' should be called only from a coroutine or another suspend function
Initially I want to return Boolean from uploadVehiclesImages, so I have return true in place of the return#launch and return false in place of the return#withContext, but then I will get the error return is not allowed here, and Android Studio suggested me to make the changes above, although I really have no idea what the changes meant there.
So what should I do to fix this problem 1, and can anyone enlighten me more on what's really happening on the problem 2?
So what should I do to fix this problem 1
Remove the property. uploadVehiclesImages() returns Unit; there is no value in having Unit in a property. If your objective is to call uploadVehiclesImages() when the viewmodel is created, put a call to it in an init block, wrapped in a suitable coroutine launcher (e.g., viewModelScope.launch {}).
This assumes that you are going to keep the function in its current form — your next question suggests that this function may not be the right solution.
Initially I want to return Boolean from uploadVehiclesImages,
More importantly, you seem to want it to return values more than once. That is not how functions work in Kotlin. One call to uploadVehiclesImages() can return one Boolean value, but not one now and one sometime in the future.
If you want to be emitting a stream of results, a suspend function on its own is not the correct solution. For example, you could:
Use LiveData, with the suspend function updating the backing MutableLiveData, or
Use a function that returns a StateFlow or SharedFlow
For part 1, you cannot use a coroutine to initialize a property. Coroutines return some time in the future, but properties have to be initialized immediately at class instantiation time. You'll have to change the strategy so you launch a coroutine that calls the suspend function, and then does something with the result when it arrives.
For part 2, you have an awkwardly composed suspend function. A proper suspend function typically isn't launching other coroutines unless it is using them to break down multiple simultaneous asynchronous actions and then waiting for them.
The convention for a suspend function is that it is safe to call from any Dispatcher. It's not proper to be sending off these background actions by launching a coroutine in a specific coroutine scope. Usually, a coroutine that calls a suspend function should not have to worry that the suspend function is going to launch some other coroutine in another scope, because this breaks support for cancellation.
Also, you can use async instead of launch to run suspend functions that you need a result from. That will avoid the awkward variables you've created to store the results (and you neglected to wait for).
Assuming you want to return both of these image results, you'll have to wrap them in another class, such as List. So your function could look like below. It returns something, not Unit. It uses aysnc to run the two requests simultaneously.
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): List<ImageUploadResult> {
return listOf(uploadMotorImage1Url, uploadMotorImage2Url)
.map { aysnc { NetworkRepository.instance!!.uploadFile(it) }
.awaitAll()
}
I just put ImageUploadResult to stand in for whatever this uploadFile function returns. Maybe it's just Boolean.
Whenever you do want to call it, you would use either lifecycleScope (from an Activity or Fragment) or viewModelScope (from a ViewModel) to launch a coroutine that calls it. For example:
fun doUpload(url1: String, url2: String) {
lifecycleScope.launch {
val results = uploadVehiclesImages(url1, url2)
// do something with results here
}
}
How can I get the value of a Flow outside a coroutine similarly to LiveData?
// Suspend function 'first' should be called only from a coroutine or another suspend function
flowOf(1).first()
// value is null
flowOf(1).asLiveData().value
// works
MutableLiveData(1).value
Context
I'm avoiding LiveData in the repository layer in favor of Flow. Yet, I need to set, observe and collect the value for immediate consumption. The later is useful for authentication purpose in a OkHttp3 Interceptor.
You can do this
val flowValue: SomeType
runBlocking(Dispatchers.IO) {
flowValue = myFlow.first()
}
Yes its not exactly what Flow was made for.
But its not always possible to make everything asynchronous and for that matter it may not even always be possible to 'just make a synchronous method'. For instance the current Datastore releases (that are supposed to replace shared preferences on Android) do only expose Flow and nothing else. Which means that you will very easiely get into such a situation, given that none of the Lifecycle methods of Activities or Fragments are coroutines.
If you can help it you should always call coroutines from suspend functions and avoid making runBlocking calls. A lot of the time it works like this. But it´s not a surefire way that works all the time. You can introduce deadlocks with runBlocking.
Well... what you're looking for isn't really what Flow is for. Flow is just a stream. It is not a value holder, so there is nothing for you retrieve.
So, there are two major avenues to go down, depending on what your interceptor needs.
Perhaps your interceptor can live without the data from the repository. IOW, you'll use the data if it exists, but otherwise the interceptor can continue along. In that case, you can have your repository emit a stream but also maintain a "current value" cache that your interceptor can use. That could be via:
BroadcastChannel
LiveData
a simple property in the repository that you update internally and expose as a val
If your interceptor needs the data, though, then none of those will work directly, because they will all result in the interceptor getting null if the data is not yet ready. What you would need is a call that can block, but perhaps evaluates quickly if the data is ready via some form of cache. The details of that will vary a lot based on the implementation of the repository and what is supplying the Flow in the first place.
You could use something like this:
fun <T> SharedFlow<T>.getValueBlockedOrNull(): T? {
var value: T?
runBlocking(Dispatchers.Default) {
value = when (this#getValueBlockedOrNull.replayCache.isEmpty()) {
true -> null
else -> this#getValueBlockedOrNull.firstOrNull()
}
}
return value
}
You can use MutableStateFlow and MutableSharedFlow for emitting the data from coroutine and receiving the data inside Activity/Fragment. MutableStateFlow can be used for state management. It requires default value when initialised. Whereas MutableSharedFlow does not need any default value.
But, if you don't want to receive stream of data, (i.e) your API call sends data only once, you can use suspend function inside coroutine scope and the function will perform the task and return the result like synchronous function call.
To get the value of a Flow outside of a coroutine, the best option is to create the flow as a StateFlow and then call the value property on the StateFlow.
class MyClass {
private val mutableProperty = MutableStateFlow(1)
val property = mutableProperty.asStateFlow()
...
mutableProperty.value = 2
}
...
val readProperty = MyClass().property.value
val propertyAsFlow = MyClass().property as Flow<Int>
I've got a question about Flowables. I already have a few solutions for this issue, but I would like to double-check if these are the best possible solutions or not.
Context
I have an Interactor that is supposed to bookmark recipes on the DB. It looks like this:
/**
* This Interactor marks a recipe as "bookmarked" on the DB. The Interactor actually switches
* the isBookmarked value of the related recipeId. If it was marked as true, it switches its value
* to false. If it was false, then it switches its value to true.
*/
class BookmarkRecipeInteractorImpl(
private val recipesCacheRepository: RecipesCacheRepository
) : BookmarkRecipeInteractor {
override fun execute(recipeId: Int, callback: BookmarkRecipeInteractor.Callback) {
// Fetches the recipe from DB. The getRecipeById(recipeId) function returns a Flowable.
// Internally, within the RecipesCacheRepository, I'm using room.
recipesCacheRepository.getRecipeById(recipeId).flatMap { originalRecipe ->
// Switches the isBookmarked value
val updatedRecipe = originalRecipe.copy(
isBookmarked = !originalRecipe.isBookmarked
)
// Update the DB
recipesCacheRepository.updateRecipe(updatedRecipe)
// Here's the issue, since I'm updating a DB record and the getRecipeById returns
// a Flowable, as soon as I update the DB, the getRecipeById is going to get triggered
// again, and switch the value again, and again, and again...
}
.subscribe(
{
callback.onSuccessfullyBookmarkedRecipe(it.response)
},
{
callback.onErrorFetchingRecipes()
}
)
}
}
So, if you follow the code, the error is pretty straightforward. I get stuck on a loop, where I constantly change the recipe record.
Possible solutions
1) Have two different functions on my DAO, one called getRecipeByIdFlowable(id) that returns a Flowable, and another called getRecipeByIdSingle(id) that returns a rx.Single. That way I can expose the getRecipeByIdSingle(id) through the Repository and use it instead of the function that returns the Flowable. That way I cut the loop.
Pro: It works.
Con: I don't like having functions like this on my DAO.
2) Save the Disposable on a lateinit property and dispose it as soon as the subscriber triggers the onNext().
Pro: It works.
Con: I don't like having to do something like this, feels hacky.
3) Using ...getRecipeById(recipeId).take(1).flatMap... so it only handles the first emitted object.
Pro: It works, it looks tidy.
Con: I'm not sure if there's a better way to do it.
Question
Ideally, I would like to call some function that just allows me to disable the Flowable behavior and prevent it from emitting more items if the DB changes. So far the solution that I like the most is #3, but I'm not really sure if this is the right way to do it.
Thanks!
Edit 1
I'm just adding a bit more of information about the use case here. I need an Interactor that given a recipeId changes the isBookmarked value on DB to its oposite.
The DB records look like:
data class DbRecipeDto(
#PrimaryKey
val id: Int,
val name: String,
val ingredients: List<String>,
val isBookmarked: Boolean = false
)
I know that maybe there's some other ways in which I could tackle this issue differently. Maybe I could pass the recipeId arg and a bookmark (Boolean) argument and just run the update query.
But this use case it is totally made up, just an example; The thing that I'm trying to figure out how to prevent a Flowable from emitting more items if something changes on the DB.
You should probably call .take(1).singleOrError() on the end of getRecipeById(recipeId).
This will take the first item (or the error) emitted by the Flowable retrieved by calling getRecipeById and wrap it in a Single. In my opinion this correctly matches the semantics of what you want to achieve.
In addition, if I recall correctly, because you will be subscribing on a Single by doing this, your Flowable will not continue to do work after the first item is consumed by the downstream call to singleOrError.
I am new to Kotlin (coming from Delphi, which is object-oriented Pascal). I just want to ensure I am having functions return List<>s correctly:
Making up an absurdly simple example here:
fun firstTenInts(): List<Int> {
val iList: MutableList<Int> = mutableListOf()
for (i in 1..10)
iList.add(i)
return iList
}
So, my thoughts/questions here are:
Am I correct to use a MutableList within the function and simply return it (even though the function's type is List)?
Do I need to create a local MutableList variable? Do I need any local "list" variable? I am used to (again, Delphi) doing something like:
function firstTenInts: TStringList;
var
i: integer;
begin
result.Clear;
for i := 1 to 10 do
result.Add(IntToStr(i));
end;
which requires no "new" local variable. I simply "work" result which is very similar to Kotlin's return value except that it serves as a local variable of the function's type which can be "worked" throughout the function.
Is there no way to manipulate a Kotlin function's return value other than with another (locally created) variable?
Finally, I can rest assured that any local variables I create are destroyed when the function ends even though I'm "passing them back" - correct?
P.S. I know this is an absurd way to create a List of 10 integers. I am using this only as a framework for the questions/issues I have detailed above. Assume that the returned List will be of unknown size.
(Please do not suggest better ways of creating this list of integers; that is not what I am asking about).
Am I correct to use a MutableList within the function and simply return it (even though the function's type is List)?
Generally that's ok. You do such things if you require a list that can be mutated within the function but from outside you do not want it to be easily mutable (which doesn't mean that it isn't mutable; you could still cast it to a mutable list, i.e. firstTenInts() as MutableList would work and so you could also mutate it again).
Whether you need that mutable list in the function or not actually depends on you. For example just calling listOf(1, 2, 3) will return you a List<Int> immediately, i.e. fun first3Ints() = listOf(1,2,3) will immediately return a List<Int>.
Do I need to create a local MutableList variable? Do I need any local "list" variable? I am used to (again, Delphi) doing something like:
You do not need to, but you can. It's up to you what better suites your needs. This also applies to your local list variable. You do not necessarily need one, even though you will have one nonetheless under the hood. Example:
fun first3Ints() = listOf(1, 2, 3)
translates to something like the following:
fun first3Ints() : List<Int> {
val result = listOf(1, 2, 3)
return result
}
On smaller functions you at least can spare some variable declarations using direct assignments. You can also use something like apply, also, let, run or with to overcome val/var, e.g. (just simulating... all variants can be implemented easier):
fun first3Ints() = arrayListOf().apply {
add(1) // apply allows calling all methods of the receiver directly.. (receiver = arrayListOf...)
add(2)
add(3)
}
fun first2Ints() = arrayListOf().also { // similar to apply, but now there is an 'it' available... you could also name that variable differently, e.g. using .also { result -> result.add(1) }
it.add(1)
it.add(2)
}
Is there no way to manipulate a Kotlin function's return value other than with another (locally created) variable?
So this becomes yes (even though .. technically speaking there will be one)... it is possible, but you need to specify on what you basically want to operate, except for the case where you implement an extension function. Then this within the extension function actually becomes the object you called the function on.
Finally, I can rest assured that any local variables I create are destroyed when the function ends even though I'm "passing them back" - correct?
... yes and no. Yes, you can rest assured that any local variable will be garbage collected when need arises (except you are doing something very special). And there is also the no: you don't know when they will be destroyed. Regarding your returned value it is even more special: you are actually getting only a reference to an object, not the object itself... so somehow you basically get that local variable back. You may want to have a look at JVM / stack and heap and how the objects are referenced and stored...
Your return is right and you can use ArrayList instead of mutableListOf and there will be no problem...
but still I didn't understand your main problem but the main topic you were talking about shows that you want to be sure of using the list as a return and that is right man
There are various ways for creating ArrayList of 10 items (just as an example you given). You can find below code snippet such as example :
// directly returning array list as Kotlin has functionality to define such kind of function just like macros
fun firstTenInts(): List<Int> = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Or by using standard function apply
fun firstTenInts(): List<Int> = ArrayList<Int>().apply {
for (i in 1..10)
this.add(i)
}
// Or by declaring variable of ArrayList
fun firstTenInts(): List<Int> {
val iList = ArrayList<Int>()
for (i in 1..10)
iList.add(i)
return iList
}
// Or by using standard function with
fun firstTenInts(): List<Int> = with(ArrayList<Int>()) {
for (i in 1..10)
this.add(i)
return#with this
}
Above are the various examples defines how you can do differently (Although sample you provided is also a valid example).