0

This article shows one example code using multiple handlers in one observer. I give a code example from this article below. But there is a memory leak error in this code - the handler is added to the mutablelist, but it is not deleted from the list when, for example, an object using one of the handlers is deleted from memory.

class WeatherStation {
    val temperatureChanged = mutableListOf<(Int) -> Unit>()

    var temperature: Int by Delegates.observable(0) { _, _, newValue ->
        temperatureChanged.forEach{it(newValue)}
    }
}

// ...

val weatherStation = WeatherStation()    
// Adding observer to the list, but where is its removal???
weatherStation.temperatureChanged.add { temperature ->
    println("Temperature changed: $temperature")
}

How to fix it, or are there alternative solutions? I need - so that when changing one property, several observers are invoked. Trying to use LiveData causes a lot of difficulties.

hata
  • 11,633
  • 6
  • 46
  • 69
Gregory
  • 802
  • 10
  • 16

2 Answers2

1

Traditionally, when something subscribes to something else, it is responsible for unsubscribing itself. You could do this by using an IdentityHashMap:

class WeatherStation {
    val temperatureChangedObservers = IdentityHashMap<Any, (Int) -> Unit>()

    var temperature: Int by Delegates.observable(0) { _, _, newValue ->
        temperatureChangedObservers.values.forEach { it(newValue) }
    }
}

// ...

val weatherStation = WeatherStation()    

weatherStation.temperatureChanged.add(this) { temperature ->
    println("Temperature changed: $temperature")
}

// remove self as observer when going out of scope:
weatherStation.remove(this)

I used IdentityHashMap rather than a MutableMap or HashMap so we won't have to worry about the possibility of two different observers possibly having object-equality.

If you want to automate unsubsribing, so you don't have to worry about it when your Fragment or Activity goes out of scope, you can require observers to be LifecycleOwners so you can observe their lifecycles. I didn't test this:

class WeatherStation: LifecycleObserver {
    private val temperatureChangedObservers = IdentityHashMap<LifecycleOwner, (Int) -> Unit>()

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onObserverDestroyed(source: LifecycleOwner) {
        temperatureChangedObservers.remove(source)
    }

    fun observeTemperature(observer: LifecycleOwner, action: (Int) -> Unit) {
        temperatureChangedObservers[observer] = action
        observer.lifecycle.addObserver(this)
    }

    var temperature: Int by Delegates.observable(0) { _, _, newValue ->
        temperatureChangedObservers.values.forEach { it(newValue) }
    }
}

// ...

val weatherStation = WeatherStation()    

weatherStation.observeTemperature(this) { temperature ->
    println("Temperature changed: $temperature")
}

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Thanks! The first code example works great. I did not check the second code example, because in my case the first example is more suitable. But a little confusing is the use of IdentityHashMap in the first example - it uses direct links as a key. Therefore, if it is used inside an Activity or Fragment, then it should be used with extreme caution. I took the first example as a basis and replaced IdentityHashMap with WeakHashMap. – Gregory Apr 16 '20 at 19:43
  • The second example also works! I took it as a basis and wrote a universal class, I give an example in the answer [below](https://stackoverflow.com/a/61264055/8313316). – Gregory Apr 17 '20 at 04:33
0

Thanks for the answer to the Tenfour04! I took his answer as a basis and made a simple universal class that maintains a list of observers. Class supports auto unsubsribing if the LifecycleOwner is used as a key. This is a simple alternative to LiveData.

class Visor<T>(initialValue: T): LifecycleObserver {
    private var value = initialValue
    private val observers = WeakHashMap<Any, (T) -> Unit>()

    fun subscribe(owner: Any, observer: (T) -> Unit) {
        if (owner is LifecycleOwner)
            owner.lifecycle.addObserver(this)
        observers[owner] = observer
    }

    fun subscribeAndInvoke(owner: Any, observer: (T) -> Unit) {
        add(owner, observer)
        observer(value) // invoke
    }

    fun remove(key: Any) {
        observers.remove(key)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onObserverDestroyed(owner: LifecycleOwner) {
        remove(owner)
    }

    operator fun getValue(thisRef: Any?, prop: KProperty<*>): T = value

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: T) {
        this.value = value
        observers.values.forEach{it(value)} // invoke all observers
    }
}

// example of using

class WeatherStation() {
    var temperatureVisor = Visor<Int>(0)
    var temperature: Int by temperatureVisor
    // ...
}

// addition of the first observer
val weatherStation = WeatherStation()
weatherStation.temperatureVisor.subscribe(this) {
    Log.d("Visor", "New temperature: $it")
}
Gregory
  • 802
  • 10
  • 16