I have a quick question about Kotlin,
For example I have a class A which have this field:
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) {
}
}
Is there any Kotlin way of returning/passing/extending the onChange event (not the value) thru a method?
I don't want to expose the output thru a listener/callback(Java way).
What I'm looking for is to somehow return the onChanged method call, without using a "middle" object/callback
Thanks
when we say return a value, it returns a value back to the callee, in this case, whoever called the onChanged method. This happens in case of synchronous calls.
In this case, onChanged call will be invoked in an asynchronous manner which makes it impossible to simply return a value back to the callee without a callback.
If i correctly understand your question, you can use observer.onChanged as Kotlin Function:
val observerOnChangedFunction = observer.run { ::onChanged }
Than you can invoke this function:
observerOnChangedFunction(instanceOfO)
Usecase: onChanged as var field
class Foo<O> {
var onChanged: (O) -> Unit = { /* default */}
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) = onChanged(output)
}
}
fun main() {
val foo = Foo<Int>()
foo.onChanged = { it.toString() }
}
-
Usecase: parameter in constructor as observer
class Foo<O> (
observer: Observer<O>
) {
private val observer: Observer<O> = observer
}
-
Usecase: parameter in constructor as onChanged lambda
class Foo<O> (
onChanged: (O) -> Unit
) {
private val observer: Observer<O> = object : Observer<O> {
override fun onChanged(output: O) = onChanged(output)
}
}
Related
The issue that I have is not actually bug or big problem. And all works as it should, but nevertheless it annoys me.
In Fragment pbserver:
viewModel.pageNumbersPosition.observe(viewLifecycleOwner) {
if (it!=null) {
SharedPreferenceHelper.pagesNumber = viewModel.pageNumbersArray.value?.get(it)
DLog.d("Set: ${viewModel.pageNumbersArray.value?.get(it)}}")
//Log shows twice as start
}
}
ViewModel:
class MenuViewModel : ViewModel() {
var pageNumbersArray = MutableLiveData(getPageNumbering())
var pageNumbersPosition = MutableLiveData(pageNumbersArray.value?.indexOf(SharedPreferenceHelper.pagesNumber))
private fun getPageNumbering():Array<String> {
val list = mutableListOf<String>()
for (i in 1..25) {
list.add(i.toString())
}
return list.toTypedArray()
}
}
Spinner:
<Spinner
android:id="#+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="#{viewModel.pageNumbersArray}"
android:selectedItemPosition="#={viewModel.pageNumbersPosition}"/>
What happes is viewModel.pageNumbersPosition.observe triggered twice on start. Once from the initiation of the fragment and second time when the spinner sets. This is actually suppose to happen, but I don't like it when Shared Preference sets twice.
I came across a handy class SingleLiveEvent that we can use instead of LiveData in ViewModel class to send only new updates after subscription.
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
fun call() {
postValue(null)
}
}
This LiveData extension only calls the observable if there's an explicit call to setValue() or call().
Update, primary constructor with parameter:
class SingleLiveEvent<T>(value: T) : MutableLiveData<T>(value) {...}
You can check if there is equal value in your shared to avoid the double set
if (it!=null) {
viewModel.pageNumbersArray.value?.get(it).let{ value ->
if (SharedPreferenceHelper.pagesNumber != value)
SharedPreferenceHelper.pagesNumber = value
}
}
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
I have a repository setup like this
class ServerTimeRepo #Inject constructor(private val retrofit: Retrofit){
var liveDataTime = MutableLiveData<TimeResponse>()
fun getServerTime(): LiveData<TimeResponse> {
val serverTimeService:ServerTimeService = retrofit.create(ServerTimeService::class.java)
val obs = serverTimeService.getServerTime()
obs.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io())
.subscribe(object : Observer<Response<TimeResponse>> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: Response<TimeResponse>) {
val gson = Gson()
val json: String?
val code = t.code()
val cs = code.toString()
if (!cs.equals("200")) {
json = t.errorBody()!!.string()
val userError = gson.fromJson(json, Error::class.java)
} else {
liveDataTime.value = t.body()
}
}
override fun onError(e: Throwable) {
}
})
return liveDataTime
}
}
Then I have a viewmodel calling this repo like this
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
fun getServerTime(): LiveData<TimeResponse> {
return serverTimeRepo.getServerTime()
}
}
Then I have an activity where I have an onClickListener where I am observing the livedata, like this
tvPWStart.setOnClickListener {
val stlv= serverTimeViewModel.getServerTime()
stlv.observe(this#HomeScreenActivity, Observer {
//this is getting called multiple times??
})
}
I don't know what's wrong in this. Can anyone point me in the right direction? Thanks.
Issue is that every time your ClickListener gets fired, you observe LiveData again and again. So, you can solve that problem by following solution :
Take a MutableLiveData object inside your ViewModel privately & Observe it as LiveData.
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
private val serverTimeData = MutableLiveData<TimeResponse>() // We make private variable so that UI/View can't modify directly
fun getServerTime() {
serverTimeData.value = serverTimeRepo.getServerTime().value // Rather than returning LiveData, we set value to our local MutableLiveData
}
fun observeServerTime(): LiveData<TimeResponse> {
return serverTimeData //Here we expose our MutableLiveData as LiveData to avoid modification from UI/View
}
}
Now, we observe this LiveData directly outside of ClickListener and we just call API method from button click like below :
//Assuming that this code is inside onCreate() of your Activity/Fragment
//first we observe our LiveData
serverTimeViewModel.observeServerTime().observe(this#HomeScreenActivity, Observer {
//In such case, we won't observe multiple LiveData but one
})
//Then during our ClickListener, we just do API method call without any callback.
tvPWStart.setOnClickListener {
serverTimeViewModel.getServerTime()
}
I observed that MutableLiveData triggers onChanged of an observer even if the same object instance is provided to its setValue method.
//Fragment#onCreateView - scenario1
val newValue = "newValue"
mutableLiveData.setValue(newValue) //triggers observer
mutableLiveData.setValue(newValue) //triggers observer
//Fragment#onCreateView - scenario2
val newValue = "newValue"
mutableLiveData.postValue(newValue) //triggers observer
mutableLiveData.postValue(newValue) //does not trigger observer
Is there a way to avoid an observer be notified twice if the same or an equivalent instance is provided to setValue()/postValue()
I tried extending MutableLiveData but that did not work. I could be missing something here
class DistinctLiveData<T> : MutableLiveData<T>() {
private var cached: T? = null
#Synchronized override fun setValue(value: T) {
if(value != cached) {
cached = value
super.setValue(value)
}
}
#Synchronized override fun postValue(value: T) {
if(value != cached) {
cached = value
super.postValue(value)
}
}
}
There is already in API : Transformations.distinctUntilChanged()
distinctUntilChanged
public static LiveData<X> distinctUntilChanged (LiveData<X> source)
Creates a new LiveData object does not emit a value until the source
LiveData value has been changed. The value is considered changed if
equals() yields false.
<<snip remainder>>
You can use the following magic trick to consume "items being the same":
fun <T> LiveData<T>.distinctUntilChanged(): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this, object : Observer<T> {
private var isInitialized = false
private var previousValue: T? = null
override fun onChanged(newValue: T?) {
val wasInitialized = isInitialized
if (!isInitialized) {
isInitialized = true
}
if(!wasInitialized || newValue != previousValue) {
previousValue = newValue
mediator.postValue(newValue)
}
}
})
}
If you want to check referential equality, it's !==.
But it has since been added to Transformations.distinctUntilChanged.
If we talk about MutableLiveData, you can create a class and override setValue and then only call through super if new value != old value
class DistinctUntilChangedMutableLiveData<T> : MutableLiveData<T>() {
override fun setValue(value: T?) {
if (value != this.value) {
super.setValue(value)
}
}
}
In my case I have quite complex objects which I have to compare by some fields. For this I've changed EpicPandaForce's answer:
fun <T> LiveData<T>.distinctUntilChanged(compare: T?.(T?) -> Boolean = { this == it }): LiveData<T> = MediatorLiveData<T>().also { mediator ->
mediator.addSource(this) { newValue ->
if(!newValue.compare(value)) {
mediator.postValue(newValue)
}
}
}
By default it uses standard equals method, but if you need - you can change distinction logic
I have a function that retrieves a list of items from a repository. Instead of using a regular callback I pass in a function and invoke this with the result. But how can you unittest this kind of function. Is there some way to verify that the passed in function is being invoked or should I refactor and use a regular callback and test it using a mocked callback-interface?
My code:
class WorklistInteractor #Inject
constructor(private val worklistRepository: WorklistRepository,
private val preferenceManager: PreferenceManager,
private val executor: Executor)
: WorklistDialogContract.Interactor, Executor by executor {
#Volatile private var job: Job? = null
override fun getWorklist(callback: (Result<List<WorklistItem>>) -> Unit) {
job = onWorkerThread {
val result = worklistRepository.getWorklist().awaitResult()
onMainThread { callback(result) }
}
}
override fun cancel() {
job?.cancel()
}
}
To check it is called, something like that would work:
var hasBeenCalled = false
interactor.getWorklist({ result -> hasBeenCalled = true })
assertTrue(hasBeenCalled)
Of course you could also check that the expected result is passed, etc.
You can simply pass a callback function that sets a test-local variable which you verify as an assertion. For simplicity, I changed the example a bit:
fun getWorklist(callback: (String) -> Unit) = callback("helloWorld")
#Test
fun testCase() {
var invoked = false
getWorklist {
invoked = true
it.length
}
Assert.assertTrue(invoked)
}