Why can't I assign a value to LiveData<Boolean>? - android

The Code A can work well.
I think Code B can work well too, but in fact , it failed, why ?
The error information is listed below.
Val cannot be reassigned
Code A
val displayCheckBox : LiveData<Boolean> = _displayCheckBox
fun switchCheckBoxShowStatus(){
_displayCheckBox.value?.let {
_displayCheckBox.value = !it
}
}
Code B
val displayCheckBox : LiveData<Boolean> = _displayCheckBox
fun switchCheckBoxShowStatus(){
_displayCheckBox.value?.let {
it = !it
}
}

it is passed as a local variable in lambda. So basically you are trying to modify a Val . Which will not compile.
inline fun <T, R> T.let(block: (T) -> R): R
As the Scoping function let defines it will call the block(Which is being passed as lambda) with this value as its argument. So its will be a method argument a val . So you can not reassign it.

Related

Clear retrofit result with MVVM when fragment back

In my ViewModel I have two MutableLiveData for the response of my webservice :
val getFindByCategorySuccess: MutableLiveData<List<DigitalService>> by lazy {
MutableLiveData<List<DigitalService>>()
}
val getFindByCategoryError: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
and this method for the request :
fun requestFindByCategory(categoryId: String){
viewModelScope.launch {
when (val retrofitPost = digitalServicesRemoteRepository.getFindByCategoryRequest(categoryId)) {
is ApiResult.Success -> getFindByCategorySuccess.postValue(retrofitPost.data)
is ApiResult.Error -> getFindByCategoryError.postValue(retrofitPost.exception)
}
}
}
It's working fine using it in my Fragment class :
viewModel.getFindByCategorySuccess.observe(viewLifecycleOwner, { digitalServices ->
logD("I have a good response from the webservice; luanch an other fragment now!")
})
The problem is if I go to an other fragment in my observable (using findNavController().navigate(action)). If I go back to the previous fragment, I go automatically to the nextFragment because the observable is called again.
So I'm looking for solutions...
Maybe clearing all my viewmodel when I go back to my fragment ?
Maybe clearing only getFindByCategorySuccess and getFindByCategoryError ?
Maybe an other solution? I think my architecture is not good. What do you think about it ?
By default, a livedata will emit to its current state (the value that exist on it) for any new observer that subscribes to it.
Answering your question, you might try the operator distincUntilChanged transformation, which, according to the documentation:
Creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.
But, this showcases a problem with your snippet, and a bad practice that is common when using livedata, you shouldn't expose mutable live data to your observers. Instead, you should expose a non-mutable version of them.
In your case, in my opinion, your view model should look like the following:
private val getFindByCategorySuccess by lazy {
MutableLiveData<List<DigitalService>>()
}
private val getFindByCategoryError by lazy {
MutableLiveData<String>()
}
val onFindByCategorySuccess: LiveData<List<DigitalService>
get() = getFindByCategorySuccess.distincUntilChanged()
val onFindCategoryError: LiveData<List<String>
get() = getFindByCategoryrRror.distincUntilChanged()
And your observers would subscribe as follows:
ExampleFragment
fun setupObservers() {
viewModel.onFindByCategorySuccess.observe(viewLifecycleOwner) { // Do stuff }
}
I hope it helps
I found a solution to my problem using this class :
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, { t ->
if (mPending.compareAndSet(true, false))
observer.onChanged(t)
})
}
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
}
Like this :
var getFindByCategorySuccess: SingleLiveEvent<List<DigitalService>> = SingleLiveEvent()
var getFindByCategoryError: SingleLiveEvent<String> = SingleLiveEvent()

Converting a callback to a suspendCoroutine, when the callback is a field of class

I want to convert some 3rd-party API based on callbacks to simple suspend functions that are easy to use. Here's a simplified example of mine implementation.
class LibraryWrapper {
private lateinit var onFooFired: (Boolean) -> Unit
private val libraryCallback = object : LibraryCallback {
override fun foo(result: Boolean) {
onFooFired(result)
}
}
private val library = Library(libraryCallback)
suspend fun bar(): Boolean {
return suspendCoroutine { performingBar: Continuation<Boolean> ->
onFooFired = {fooResult -> performingBar.resume(fooResult) }
library.bar()
}
}
}
But this solution sometimes works and sometimes not. There is such an issue with this lambda field, that sometimes it initializes correctly, but sometimes the exception is thrown that "lateinit property onFooFired is not initialized".
It's very strange because I do initialize it before run library.bar() and foo of LibraryCallback is called only after library.bar() was called.
first of all, I think it is not a good approach to use "lateinit var" when you don't control the initialization of a field. Use lateinit only when you have the promise of initialization.
Try to use nullable field implementation like
private var onFooFired: ((Boolean) -> Unit)? = null
and in callback :
private val libraryCallback = object : LibraryCallback {
override fun foo(result: Boolean) {
onFooFired?.invoke(result)
}
}
In this case, until you do not have an implementation of "onFooFired" lambda, it does not invoke

Type mismatch. Required: Observer<in Int!> Found:?

I want to Observe live data change in ViewModel and want to change another live data so I am using Mediatorlivedata, I don't know how to observe it in ViewModel, I am getting the compile-time error
Type mismatch. Required: Observer Found: ?
class CheckmeasureViewModel(private val repository: UserRepository) : ViewModel() {
var estimateFinancialyear: ArrayList<FinYear> = ArrayList()
var asset = arrayListOf("Select")
var estimate = arrayListOf("Select")
var appPref: AppPref
var estimateyearpos = MutableLiveData<Int>()
var mediatorLiveData: MediatorLiveData<Int> = MediatorLiveData()
init {
appPref = AppPref.getInstance()!!
estimateFinancialyear.add(FinYear(0, "Select"))
estimateFinancialyear.addAll(repository.getFinYears())
estimateyearpos.observeForever(object : Observer<in Int> {
fun onChanged(#Nullable integer: Int?) { //Do something with "integer"
}
})
}
}
You Shouldn't observe a live data on the viewModel, try adding it as a source to a mediatorLiveData and observing it directly on the view:
val mediatorLiveData: MediatorLiveData<Int> = MediatorLiveData().apply{
addSource(estimateyearpos) { /*Do something with "integer" */}
}
or even (if you don't need it to be mutable)
val liveData = Transformations.map(estimateyearpos) { /*Do something with "integer" */}
Both of this options will observe the source live data and apply the given function to it, but you still need to observe it on an Activity or a fragment to properly get the values.

Using an array of null instead of an optional

I was wondering why an array of nulls of size 1 instead an optional:
https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/test-common/java/com/android/example/github/util/LiveDataTestUtil.kt#L27
Original code:
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
return data[0] as T
}
why not using:
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
return data!!
}
Is it better to use an array than an optional object there? why?
Or it's just the same?
Thanks
Looking at the project history, the file used to be Java, but was translated directly into Kotlin - the original Java version is here, and you can see they just converted it directly to Kotlin.
While the version you gave makes more sense in Kotlin, there is no such capability in Java, because local variables you use in an anonymous class or lambda have to be final. That is why a single element array is used instead. But if they had written it originally in Kotlin (or if they were to refactor it), they would almost certainly do it the way you did.

Kotlin lazy default property

In Kotlin, how do i define a var that has a lazy default value ?
for example, a val would be something like this:
val toolbarColor by lazy {color(R.color.colorPrimary)}
What i want to do is, have a default value for some property (toolbarColor), and i can change that value for anything else. Is it possible?
EDIT: This does the partial trick.
var toolbarColor = R.color.colorPrimary
get() = color(field)
set(value){
field = value
}
Is it possible to ease this by writing
var toolbarColor = color(R.color.colorPrimary)
set(value){
field = value
}
in a way that the default value is computed lazily? At the moment it won't work because color() needs a Context that is only initialized later.
You can create your own delegate method:
private class ColorDelegate<T>(initializer: () -> T) : ReadWriteProperty<Any?, T> {
private var initializer: (() -> T)? = initializer
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: initializer!!()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
Declare in some delegate:
object DelegatesExt {
fun <T> lazyColor(initializer: () -> T): ReadWriteProperty<Any?, T> = ColorDelegate(initializer)
}
And use as follow:
var toolbarColor by DelegatesExt.lazyColor {
// you can have access to your current context here.
// return the default color to be used
resources.getColor(R.color.your_color)
}
...
override fun onCreate(savedInstanceState: Bundle?) {
// some fun code
// toolbarColor at this point will be R.color.your_color
// but you can set it a new value
toolbarColor = resources.getColor(R.color.new_color)
// now toolbarColor has the new value that you provide.
}
I think this could be a cleaner way to do, but I don't know yet (starting with kotlin few days ago). I will take a look and see if this could be done with less code.
You can store your property in a map to basically create a mutable lazy. You need a mutable map (like a HashMap<K, V>) with a default function to delegate to:
var toolbarColor by hashMapOf<String, Any?>()
.withDefault { toolbarColor = R.color.colorPrimary; toolbarColor }
You'll also need to import some extension functions: import kotlin.properties.getValue and import kotlin.properties.setValue.
It would be nice if Kotlin provided something built-in and optimized for this (like a mutableLazy or something). As such, I've created KT-10451.

Categories

Resources