Creating a custom listener in Android (listener pattern)

Introduction

listener

“Listener pattern” is one of the most commonly used patterns used in application development. The working principle is based on the fact that in one class we define an event and the moment when the execution of the event starts (“trigger event”), while in another class we define an object (so-called listener object) that listens to that event and reacts to it.

There are quite a number of built-in listeners within Android itself, but in addition to them, we can also create our own custom listeners and thus enable us to define callback methods for events that are triggered from other parts of our code. Custom listeners are used in the following cases:

Example of a standard listener built into the operating system

This pattern is often used within the operating system, and the best example of the built-in interface in android core is OnClickListener which is responsible for the “click” event. This listener is defined within the View.java class.

A setter method named setOnClickListener() is defined within the View.java class. Calling this setter method in another class defines a listener object that listens to that event, and through it a callback method that reacts to the event. In this example, the object “btnNekiButton” is the successor of the View.java class, and therefore it has inherited all interfaces, including the one mentioned, so it can simply call its setter method:

Or if we show the known code with an “on the fly” created object:

EXPLANATION:

Instantiating “on the fly” an anonymous object that implements an interface

An anonymous class is a class that does not have a name, so if we wanted to instantiate an object from this class it would look like this:

We need a specific anonymous class, one that implements a specific interface, so although we don’t need to define a class name, we do need to define which interface to implement. If the interface is defined in its own file, this is achieved by inserting the name of the interface before the brackets:

We also need to implement interface methods:

Procedure

The interface can be defined independently in a separate file in the communication between two classes is defined in one class for practical reasons. In these examples, we will use the terms “First Class” (the place where the interface is defined, i.e. the event) and “Second Class” (the place where the object that listens and defines the callbackMethod is registered, i.e. the action that should be performed after “triggering” the event).

a)Interface (first class):

As with ready-made listeners within the Android operating system, the interface must first be created here. In the previous example, this part of the work was written as part of android, while in this case we will do it. The interface ensures that every object that implements the interface has a callback method:

b) Setter method (first class)

In addition to the interface, a setter method is needed, by calling which one defines which object is the listener (that is, the object that listens to the event). This method allows us to define that object anywhere, because it is enough to pass it as a parameter when we call that method (see more about it here).

c) Triggering the event = calling the callback method (first class)

Calling the callback method can be considered as an event trigger, so call the callback method somewhere in the class and that way the switch will be “triggered” and the event will be started:

Although exception:

should be avoided

You can view the entire code from the first class here.

d) Defining the object that listens and the callback method

All previous parts are created in one class that created the event, while this part of the code is in another class and is in charge of defining a listener object that listens to the event in that other class. By defining the listener object, we also practically define a callback method that reacts to the event. There are several ways to do this:

d1) Defining the listener object and the callback method through the setter method

Defining the callbackMethod can be done in two ways depending on what the listener is.

d1.a) Listener = anonymous object

In this case, defining the callback method is done by calling the setter method and passing an “anonymous” object that implements the interface and thus the callback method. In order to be able to call a method from another class, we need to call it through an object of the first class or an object that implements an interface (more about “objectImplementingInterface” see here).

The object that we pass through the parameter is created as an instance of an anonymous class that implements an interface (how to “on the fly” create an object from an anonymous class, see here).

d1.b) Listener = Whole other class

In this case the whole class implements the interface, so it is necessary to register the whole class as a listener by passing the class through the setter method using the keyword “this” and then override the callbackMethod:

d2) Defining the listener object and the callback method through the constructor

If the listener is extremely important for the class itself, then the setter method is replaced by the constructor method of the class itself:

In the second class, we create an object based on the constructor method of the First Class, by passing an “on the fly” created anonymous object that implements a listener through a parameter:

d3) Defining the listener object and the callback method through the lifecycle method

This approach isused in communication between fragments and activities.

Fragment

The procedure within the fragment is similar to the procedure (code) from FirstClass, so that within the fragment there are all three previously described steps: creating an interface, setter methods and triggering events.

Activity

In the case of Activity, the procedure is partially different from that of the Second Class, because it must be checked in the onFragmentAttach() method whether the activity implements the interface. Only when we are sure that the activity implements the interface is it defined that the activity becomes a listener object. By validating the callbackMethod, the action after triggering the event is defined:

There is also another way when checking within the fragment itself whether the activity implements the interface or not. Then the entire validation code is executed in the fragment within the onAtach() method (see the full code here).

NOTE:
If you need to register more than one listener, then you need to adapt the code to work with an array of listeners:

See the full new code here.

Communication Fragment – Activity

In this example, the interaction between fragments and activities will be explained. Within the creation fragment, the interface “triggers” the custom event, while the callback body is defined in the activity, i.e. reaction to the execution of that custom event from the fragment.

MyListFragment

Activity:

Communication Fragment – Fragment

Communication between them can be achieved in two ways:

Since the topic of this article is the listener pattern, in the next example we will show how to communicate between two fragments using listeners, although using a shared “ViewModel” is a simpler approach.
The working principle is as follows: a message from the FirstFragment is sent to the Activity, after which the Activity sends a message to the SecondFragment through the callback method.

FirstFragment

Activity

SecondFragment

Communication between Dialogue and Activity is solved in a similar way.

Communication Adapter – Activity

In this example, the interface is defined by default, followed by the setter method, as well as event triggering within the adapter class. Although in the examples on the net you can often find that the triggering of the event is done within the “onBindViewHolder() method, it is recommended that the triggering of the event is done within the ViewHolder class, i.e. within its constructor.

Adapter

After defining the interface,it is necessary to register a listener and define a callback method in the fragment or activity that uses the adapter:

Fragment or Activity

Here we will define an anonymous listener object “on the fly” and in it define the action to trigger the event:

This could have been done in another way: when the Activity implements the interface, it is enough to validate the callback method for defining the action after the event is fired.

×

Instantiating an anonymous object that implements the interface “on the fly”:

An anonymous class is a class that does not have a name, so if we wanted to instantiate an object from this class it would look like this:

We need a specific anonymous class, one that implements a specific interface, so although we don’t need to define a class name, we do need to define which interface to implement. If the interface is defined in its own file, this is achieved by inserting the name of the interface before the brackets:

We also need to implement interface methods:

Communication between two classes:

When the interface is needed for communication only between two classes, then it is usually defined and created within the so-called. first class and it is necessary to call the interface through the first class:

In addition to this, we need to implement all the abstract methods of this interface:

In this example, using the “anonymous class” we created an “anonymousListenerObject” (that is, an object that listens), but the “listenerObject” does not have to be anonymous, it can be stored in a variable and used multiple times:

×

Instantiating an anonymous object that implements the interface “on the fly”:

An anonymous class is a class that does not have a name, so if we wanted to instantiate an object from this class it would look like this:

We need a specific anonymous class, one that implements a specific interface, so although we don’t need to define a class name, we do need to define which interface to implement. If the interface is defined in its own file, this is achieved by inserting the name of the interface before the brackets:

We also need to implement interface methods:

×

×

×

If “objectImplementingInterface” is a descendant of View class then it can be inserted into View of other class as “custom view”.

1) Inserted statically (directly in .xml)

If “objectWhichImplementInterfejs” is inserted statically (as a custom view in the layout) then we target it as a regular view using the method indViewById():

2) Inserted programmed
2.a) By creating a First Class object

“listenerObject” is actually an anonymous object created “on the fly”:

2.b) DrugaKlasa implements the interface

In case the whole class implements the interface then it is used:

×

First class

Second class

×

With this approach, within the Activity, we do not define which object will be the listener, but we do it within the fragment itself through the onAttach() method. So that the listener object becomes any activity to which the fragment is attached, while implementing the interface:

In the activity that implements the interface, only the callbackMethod is overridden to define the action when the event is triggered: