How to observe a reference variable? - android

I have a class below that updates a data variable. How can I observe when this variable changes?
object Manager {
private var data: Type = B()
fun doWork{
while(active) {
if(conditionA)
data = A()
else if(conditionB)
data = B()
}
}
fun getData(): Flow<Type>
}
interface Type {
}
Some classes that implements the interface.
class A: Type {}
class B: Type {}
I want to be able to observe these changes without using LiveData or anything that is Experimental. How can I let other areas of my code observe the data variable?
I know there is BroadcastChannel but I cannot use it because it is experimental.

You can use listener and built-in Kotlin delegate:
object Manager {
var dataListeners = ArrayList<(Type) -> Unit>()
// fires off every time value of the property changes
private var data: Type by Delegates.observable(B()) { property, oldValue, newValue ->
dataListeners.forEach {
it(newValue)
}
}
fun doWork{
while(active) {
if(conditionA)
data = A()
else if(conditionB)
data = B()
}
}
}

Related

Proper way to update LiveData from the Model?

The "proper" way to update views with Android seems to be LiveData. But I can't determine the "proper" way to connect that to a model. Most of the documentation I have seen shows connecting to Room which returns a LiveData object. But (assuming I am not using Room), returning a LiveData object (which is "lifecycle aware", so specific to the activity/view framework of Android) in my model seems to me to violate the separation of concerns?
Here is an example with Activity...
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity);
val viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
val nameText = findViewById<TextView>(R.id.nameTextBox)
viewModel.getName().observe(this, { name ->
nameText.value = name
})
}
}
And ViewModel...
class UserViewModel(): ViewModel() {
private val name: MutableLiveData<String> = MutableLiveData()
fun getName() : LiveData<String> {
return name
}
}
But how do I then connect that to my Model without putting a "lifecycle aware" object that is designed for a specific framework in my model (LiveData)...
class UserModel {
val uid
var name
fun queryUserInfo() {
/* API query here ... */
val request = JSONObjectRequest( ...
{ response ->
if( response.name != this.name ) {
this.name = response.name
/* Trigger LiveData update here somehow??? */
}
}
)
}
}
I am thinking I can maybe put an Observable object in my model and then use that to trigger the update of the LiveData in my ViewModel. But don't find any places where anyone else says that is the "right" way of doing it. Or, can I instantiate the LiveData object in the ViewModel from an Observable object in my model?
Or am I just thinking about this wrong or am I missing something?
This is from official documentation. Check comments in code...
UserModel should remain clean
class UserModel {
private val name: String,
private val lastName: String
}
Create repository to catch data from network
class UserRepository {
private val webservice: Webservice = TODO()
fun getUser(userId: String): LiveData<UserModel > {
val data = MutableLiveData<UserModel>() //Livedata that you observe
//you can get the data from api as you want, but it is important that you
//update the LiveDate that you will observe from the ViewModel
//and the same principle is in the relation ViewModel <=> Fragment
webservice.getUser(userId).enqueue(object : Callback<UserModel > {
override fun onResponse(call: Call<User>, response: Response<UserModel >) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<UserModel >, t: Throwable) {
TODO()
}
})
return data //you will observe this from ViewModel
}
}
The following picture should explain to you what everything looks like
For more details check this:
https://developer.android.com/jetpack/guide
viewmodels-and-livedata-patterns-antipatterns

Return value from override function

I'm not sure what I'm looking to do is even possible. If it is, it's new to me.
here's a basic outline of what I'm trying to accomplish:
class MyClass : SomeInterface {
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun iDidSomethingElse(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun onFailure(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
}
Explanation:
We're using a required SDK that has about 15 overrides. I call into this class to call a function in the SDK. That function is going to call one of the override functions when it's done.
Is there a way (live data, flows, anything) to have whoever called makeSomethingHappen() receive the value from any of the override methods?
This is a basic example of reactive programming. You wait for an event that a producer/observable emits, it's like when you declare a click listener on a button.
You can't return the value in makeSomethingHappen(), but you can create a listener and the observer implements that listener to get the value.
typealias MyListener = (Value) -> Unit
class MyClass : SomeInterface {
private var _listeners: List<MyListener> = mutableListOf()
fun addListener(listener: MyListener) {
_listeners.add(listener)
}
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun iDidSomethingElse(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun onFailure(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
}
The you can get the result implementing MyListener
val myClass = MyClass()
myClass.makeSomethingHappen { value ->
// Here you have the value and you can do whatever
print(value)
}

Kotlin - Return callback event

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)
}
}

LiveData is observed multiple times inside onClickListener in Android

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()
}

How to emit distinct values from MutableLiveData?

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

Categories

Resources