Initialize property in Kotlin so that the code is executed only once - android

I would like to know if there is a more idiomatic way of writing the following in Kotlin:
private var loadingFlag: Boolean = false
override fun onResume() {
super.onResume()
if (!loadingFlag) {
// do something
loadingFlag = true
}
}
so far the closest I could come up with is:
val lazyValue: Unit by lazy {
println("computed!")
Unit
}
fun main() {
println(lazyValue)
println(lazyValue)
}
which executes println("computed!") only once as expected.
Basically when I call the code I want println("computed!") to be called only once and subsequent calls do nothing. Any ideas?

I wouldn't consider it idiomatic to have a property with value Unit , or to call a property for its side effect. Those are both code smells.
You can write a custom getter for a Boolean property so it always returns false after the first time it's retrieved. It can be reset by setting it to true.
var firstTime: Boolean = true
get() = field.also { field = false }
This is just convenience. There's nothing non-idiomatic about your first block of code.

Related

How to get value of the Flow only once and immediately?

I have created my DataStore and I have a boolean value in it:
private val DID_REBOOT = booleanPreferencesKey("did_reboot")
val didRebootFlow: Flow<Boolean> = context.dataStore.data
.map { preferences ->
preferences[DID_REBOOT] ?: false
}
suspend fun setDidReboot(didReboot: Boolean) {
context.dataStore.edit { preferences ->
preferences[DID_REBOOT] = didReboot
}
}
Now, on my BroadcastReceiver I need to check this value. Depending on the the value, I need to perform database operations:
GlobalScope.launch {
if(ds.didRebootFlow.first()) {
Log.d("-----------------", "true")
} else
Log.d("-----------------", "false")
}
In some place in my app, I am setting didRebootFlow to true. But my broadcast is printing false (in the second attempt it works fine).
2021-04-27 12:42:24.347 7469-7469/com.test.datastore D/-----------------: false
I am new to suspended functions and Flow and cannot figure it out how to make my code work.
My code logic is strongly dependant on the value of this field. How to get didRebootFlow once (and immediately) here?
I do not want to use .collect { } because I do not want "observe" didRebootFlow continuously. I would like to get its value only once. (Because observing didRebootFlow non-stop means I will be doing lots of database operations).

Implementing async- await() in kotlin coroutine

I have created function as below :
fun getPercentage(id:String): String {
var percentage=""
scope.launch {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percenatge
}
Here, I can not return the updated value using the varibale : percentage.
Logs I am getting is as below :
$$$ value outside >>
$$$ value >> 50
means I can't return the latest value. something going wrong with the flow.
Someone suggested me to use async{} and await(). But I don't know How it will be helpful here?
Please guide. Thanks.
The launch function launches the coroutine in the background, and then continues. Your "outside" code is therefore running before the "inner" coroutine completes.
Use the async function instead to return a Deferred value from the coroutine:
fun getPercentage(id:String): Deferred<String> {
return scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
}
Note of course that its more likely you want to make getPercentage a suspend function, and then call await directly:
suspend fun getPercentage(id:String): String {
val percentageDeferred = scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
val percentage = percentageDeferred.await()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
It's also likely you want to do something else before the await otherwise you're probably just better off making repo.getPercentage a suspend function as well, and calling it directly:
suspend fun getPercentage(id:String): String {
// if repo.getPercentage is a suspend function, this call suspends
// like the await in the previous example
val percentage = repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
See Concurrent using async in the Kotlin docs.
I don't believe you necessarily need to use async in this case, particularly. You only need to be aware that whatever is inside launch { ... } is executed asynchronously. So by the time getPercentage returns, your coroutine may not have even started yet.
Keeping that in mind, I believe you may want to change the way your code works. The only way you can make fun getPercentage(id: String): String work without changing that signature is by replacing scope.launch { ... } with scope.runBlocking { ... }, but you probably don't want to do that, because it would block your thread.
Instead, you might change getPercentage to be a suspend method:
suspend fun getPercentage(id: String): String {
return repo.getPercentage(id)?.get(0)?.percent.toString()
}
However, suspend methods can only be called from inside a coroutine. So you would need to call it like this:
scope.launch {
val percentage = getPercentage("some ID")
// Now you can use `percentage` for whatever you need.
}

Testing Observable Field Changes within a Coroutine

I am writing unit tests and I have this particular case when I change observable value before executing a suspend function and then right after. I would like to write a unit test to check if the value was changes twice correctly.
Here is the method:
fun doSomething() {
viewModelScope.launch(ioDispatcher) {
try {
viewModelScope.launch(Dispatchers.Main) {
commandLiveData.value = Pair(CANCEL_TIMER, null)
}
isProgress.set(true) //need to test it was set to true in one test
checkoutInteractor.doSomethingElse().run {
handleResult(this)
}
isProgress.set(false) //need to test it was set to false here in another test
} catch (ex: java.lang.Exception) {
handleHttpError(ex)
isProgress.set(false)
}
}
}
When writing a test I am calling doSomething() but I am unsure how to detect that the value was set to true before the checkoutInteractor.doSomethingElse call and set to false after.
Here is the test I have
#Test
fun `test doSomething enables progress`() {
runBlockingTest {
doReturn(Response()).`when`(checkoutInteractor). checkoutInteractor.doSomethingElse()
viewModel.doSomething()
assertTrue(viewModel.isProgress.get()) //fails obviously as the value was already set to false after the `doSomethingElse` was executed.
}
}
BTW, isProgress is an ObservableBoolean
You would need to either mock or spy on isProgress and checkoutInteractor fields to record and verify the execution of their methods.
Pass a mock or spy for isProcess and checkoutInteractor into your class.
Execute doSomething()
Verify inOrder the set() and doSomethingElse() functions
Example:
#Test
fun `test doSomething enables progress`() {
runBlockingTest {
val checkoutInteractor = Mockito.spy(CheckoutInteractor())
val isProcess = Mockito.spy(ObservableBoolean(true))
val viewModel = ViewModel(isProcess, checkoutInteractor)
viewModel.doSomething()
val inOrder = inOrder(isProcess, checkoutInteractor)
inOrder.verify(isProcess).set(true)
inOrder.verify(checkoutInteractor).doSomethingElse()
inOrder.verify(isProcess).set(false)
}
}

"return is not allowed here" when returning inside a kotlin lambda

I use a lambda to handle callbacks from an asynchronous call. I'd like to define the callback outside the calling method to avoid a bulky method, but I can't seem to use early returns within the lambda, which makes the code unnecessarily difficult to read.
I've tried defining the lambda as a variable but returns are not viable inside the lambda.
I've tried defining the lambda inside a function and returning but returns were not viable there either.
For example:
private fun onDataUpdated(): (Resource<List<Int>>) -> Unit = {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return#onDataUpdated // This is not allowed
}
// Handle the data update
}
}
I've also tried:
private val onDataUpdated: (Resource<List<Int>>) -> Unit = {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return // This is not allowed
}
// Handle the data update
}
}
I'd like to perform an early return instead of using an else case, to avoid unnecessary indent, but I can't seem to find a way to use returns inside a lambda.
You can achieve this by labelling the lambda. For example, if you label it with dataUpdated:
private val onDataUpdated: (Resource<List<Int>>) -> Unit = dataUpdated# {
if (it.data.isNullOrEmpty()) {
// Handle no data callback and return early.
return#dataUpdated
}
// Handle the data update
}
Usually you'll just put the "return" at the bottom of the lambda. Here's an example using Dispatchers.IO:
var res = async(Dispatchers.IO){
var text : String? = null
if(channel==null) {
text = networkRequest(url)
}
text
}.await()

Method always returns false from Firestore DB query

I have a method that checks if a list contains a user or not. For some reason it always returns false, even though the user is in the list. The function does work, I know it does find the user, just not sure why it doesn't return anything else but false.
I know it works because I have another method with this code snippet in to check if the user is in the list and remove or add them. That works so I know it is pulling the list.
Method:
fun checkUserChatChannel(channelId: String): Boolean {
var list = mutableListOf<String>()
val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid
var bool = false
chatChannelsCollectionRef.document(channelId).get().addOnSuccessListener {
if (it.exists()) {
list = it.get("userIds") as MutableList<String>
if(list.contains(currentUserId)){
bool = true
}
}
}
return bool
}
Calling:
boolean inOut = FirestoreUtil.INSTANCE.checkUserChatChannel(moduleId);
if(!inOut)
{
JLChat.setText("Join");
}
else
{
JLChat.setText("Leave");
}
But the button will always remain on "Join":
This is the other method that I use so I know the code works to remove a user from the list(Nothing to do with question):
fun LeaveGroup(channelId: String) {
currentUserDocRef.collection("engagedChatChannels").document(channelId)
.delete()
var list = mutableListOf<String>()
val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid
chatChannelsCollectionRef.document(channelId).get().addOnSuccessListener {
if (it.exists()) {
list = it.get("userIds") as MutableList<String>
if(list.contains(currentUserId)){
list.remove(currentUserId)
}
}
val newChannel = chatChannelsCollectionRef.document(channelId)
newChannel.set(GChatChannel(channelId,list))
}
}
Firebase APIs are asynchronous, meaning that onSucces() function returns immediately after it's invoked, and the callback from the Task it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, the value of your bool variable you're trying to return, will not have been populated from the callback yet.
Basically, you're trying to return a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to use the value of your bool variable only inside the callback. This also means that you need to change your checkUserChatChannel instead of returning a boolean to return void, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback.

Categories

Resources