14

How to I set up an interface listener for detecting a variable change in Kotlin. I successful implemented the following in Java, but am running into issues doing it in Kotlin:

Interface:

public interface InterfaceRefreshList
    {
    public void refreshListRequest();
    }

Class containing listener:

public class SignalChange
    {
    private static boolean refreshListSwitch;
    private static List<InterfaceRefreshList> refreshListListeners = new ArrayList<>();
    public static void setRefreshList(boolean value)
    {
    refreshListSwitch = value;
    for(InterfaceRefreshList l : refreshListListeners)
        l.refreshListRequest();
    }
    public static void addRefreshListListener(InterfaceRefreshList l)
        {
        refreshListListeners.add(l);
        }
    }

Class where listener is listening:

public class FragmentBrowse extends Fragment
{
    public FragmentBrowse() /// Constructor
        {
        SignalChange.addRefreshListListener(() -> refreshList());
        }
    refreshList()
    {
    // do something
    }
}

To signal a change:

SignalChange.setRefreshList(true);

I can set up the interface and the signal class:

class SignalChange
{
    private var refreshListSwitch: Boolean = false
    var setSwitch: Boolean
        get() = refreshListSwitch
        set(value)
        {
            refreshListSwitch = value
        }

    private var refreshListListeners = ArrayList<InterfaceRefreshPersonsList>()
    fun sendRefreshSignal()
    {
        for(l in refreshListListeners) l.refreshPersonsList()
    }

    fun addRefreshListListener(l: InterfaceRefreshPersonsList)
    {
        refreshListListeners.add(l)
    }

}

But I cannot setup the listener in the FragmentBrowse class. The fragment class doesn't allow constructors.

Sergio
  • 27,326
  • 8
  • 128
  • 149
Samuel
  • 395
  • 2
  • 5
  • 20
  • Which issues are you running into? Why don't you simply run the Java to Kotlin converter on your Java code? – yole Jan 22 '19 at 17:52
  • The converter doesn't simply work like that. For example, the static setter for the private variable I had to do: private var refreshListSwitch: Boolean = false var setSwitch: Boolean get() = refreshListSwitch set(value) { refreshListSwitch = value } – Samuel Jan 22 '19 at 18:18
  • Why did you have to do that? This looks entirely unnecessary. Anyway, you didn't answer what issues specifically you ran into with your Kotlin code. – yole Jan 22 '19 at 18:21
  • See edited code – Samuel Jan 22 '19 at 18:26
  • The code for adding a listener needs to be placed in an [initializer block](http://kotlinlang.org/docs/reference/classes.html#constructors). The built-in Java to Kotlin converter in IntelliJ IDEA is capable of performing this conversion automatically. – yole Jan 22 '19 at 19:30
  • I already tried in init block and added instance of SignalChange: signalChange = SignalChange(), but the following does not convert: signalChange.addRefreshListListener(() -> refreshList()); – Samuel Jan 22 '19 at 19:43

2 Answers2

19

You can use built-in Kotlin delegates, for example:

object SignalChange {
    var refreshListListeners = ArrayList<InterfaceRefreshList>()

    // fires off every time value of the property changes
    var property1: String by Delegates.observable("initial value") { property, oldValue, newValue ->
        // do your stuff here
        refreshListListeners.forEach { 
            it.refreshListRequest()
        }
    }
}

interface InterfaceRefreshList {
    fun refreshListRequest()
}

Add listeners like this:

SignalChange.refreshListListeners.add(object : InterfaceRefreshList {
    override fun refreshListRequest() {
        refreshList()
    }
})

OR

Intead of interface you can use lambda:

object SignalChange {
    var refreshListListeners = ArrayList<() -> Unit>()

    // fires off every time value of the property changes
    var property1: String by Delegates.observable("initial value") { property, oldValue, newValue ->
        // do your stuff here
        refreshListListeners.forEach {
            it()
        }
    }
}

And to add listener just call:

SignalChange.refreshListListeners.add(::refreshList)
//or
SignalChange.refreshListListeners.add { refreshList() }

fun refreshList() {

}
Sergio
  • 27,326
  • 8
  • 128
  • 149
  • With the posted Java code I was able to signal a change from anywhere in the program. Can the Delegates.observable do that? – Samuel Jan 22 '19 at 19:07
  • I edited my answer. When you set value to `property1` Delegates.observable fires off and invokes `refreshListRequest()` on each listener. – Sergio Jan 22 '19 at 19:20
  • This seems the same as fun sendRefreshSignal() for(l in refreshListListeners) l.refreshPersonsList() but the issue I am having is how to invoke: fun addRefreshListListener(l: InterfaceRefreshPersonsList) refreshListListeners.add(l) from the FragmentBrowse class – Samuel Jan 22 '19 at 20:07
  • it seems you need implement `SignalChange` as `Singleton`. And then you will be able to add listeners like this. I've edited my answer, now `SignalChange ` is a `Singleton` – Sergio Jan 22 '19 at 20:32
  • You gave us 2 answers which is great. Is one better than the other? – Jevon Jan 31 '21 at 20:22
13

The simple way to Listen to variable in KOTLIN

private var myVariableName by Delegates.observable(0) { property, oldValue, newValue ->
             Log.d(TAG,"New Value $newValue")
             Log.d(TAG,"Old Value $oldValue")
        }

for more info read https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/

Ahmad Darwesh
  • 553
  • 4
  • 12
  • 1
    Thank you. How do you assign a type for myVariableName ? I tried ```myVariableName: String by Delegates....``` and it does not work – Jack' Jun 06 '21 at 19:31
  • 2
    Oh, I get it. Assigning 0 as the initial value automatically makes myVariableName as an Int. So we should do ```myVariableName by Delegates.observable("Initial Text")``` for a String – Jack' Jun 06 '21 at 19:34