Android WTF: Java Interfaces By Example
This article (and accompanying video) is directed to my novice/junior Android brogrammers and siscripters. It’s really important that you understand how to use and create interfaces, but most examples and explanations I had read online, solved erroneous problems (or none at all). I felt as a beginner, that this lead me to pass off interfaces and abstract classes as either being useless, or something far outside my capabilities of understand (I read many, many articles to no avail).
In retrospect, I believe I just needed someone to give me a good example or two of a problem which could be solved by using an interface. In working through this example, I hope to also demonstrate to you the practicality of using abstractions in your projects. I won’t define that word in English (I might another day), and instead encourage you to know it in the code examples.
This article has an accompanying video tutorial, available here:
https://www.youtube.com/watch?v=VCmi0gBxd0E&t=807s
While I strongly believe that studying and using new concepts and tools in the context of working application code is the best way to learn, one of the problems of being a beginner, is that you might not have any experience building applications, and thus may be lacking reference experiences to ground the concept and its usage.
The first few times I tried to understand what an interface was, and what it was for, I was in effect trying to learn something which solves problems that I didn’t understand yet. Since I had no one to guide me (couldn’t afford a degree), I missed out on the benefits of interfaces (and abstraction in general) for my entire first year of building applications; it was pretty ugly, and I couldn’t change anything without other things breaking!
With any luck, the following examples will help you cross that bridge far sooner than I did.
What is an Interface?
Before I explain things on my own terms, I would like to start by introducing you to the technical definition, because it will eventually become important to understand. If you aren’t aware of half the things in it, then skip it and don’t worry at all!
“[1] In the Java programming language, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods. [2] Interfaces cannot be instantiated — they can only be implemented by classes or extended by other interfaces.”
Quoted from Oracle.
[1] For now, I’d simply suggest you think of an interface as a class with less details. That’s what it looks like right? That’s essentially what it is. Don’t worry about why we want something with less details to stand in for a real class; that part will come later.
[2] A key difference between an interface and a class, is that an interface cannot be instantiated. The reason for this distinction should also become self evident as you use the abstraction.
Don’t worry if this isn’t sinking in, come back to this part at the end and see if it’s any clearer.
In the following examples, we’re going to imagine that we’re software engineers working on the pre-release of Android 1.0. Obviously that’s a silly thought, but the imagining is actually part of the learning process. While part of this should teach you what an Interface is and how to make one, the most important takeaway is simply learning to know when an interface might be a good approach, among several possible approaches which we will observe.
Example 1: How to implement a bad Solution
Our current task is to figure out some way for Buttons to communicate with Activities. Since doing this for real would be a monumental waste of our time, I’ll provide you with some pseudo-code examples which contain only relevant implementation details. We’ll start by solving the problem with a quick solution that ‘works’ (at least hypothetically), but also sucks. The important thing is why it sucks; as we’ll see later.
Create the following two Classes:
public class ButtonOne {
//By holding the Activity as a reference variable, we can talk to it when button clicks are fired.
private MainActivityOne mainActivityOne;
public void setMainActivityOne(MainActivityOne mainActivityOne){
this.mainActivityOne = mainActivityOne;
}
/**
*Fires when a User Clicks the Button.
* Let’s not worry how, just assume it works.
*/
private void OnClick(){
//we’re basically relaying the fact that the button was clicked, and which one was clicked (by hypothetical Id), so that the Activity can handle user input.
mainActivityOne.onClick(getViewId());
}
//In reality, the View Id is generated at Runtime. At the moment, I don’t care how.
public int getViewId(){
//fake view id
return 123456;
}
}
Followed by:
public class MainActivityOne extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButtonOne buttonOne = new ButtonOne();
/*
What does “this” mean? In this particular case, “this” refers to the current Instance
of MainActivityOne. If you’re still confused, try passing “MainActivityOne.this” instead
of just “this” in the method parameters. Notice how it still works :).
*/
buttonOne.setMainActivityOne(this);
}
public void onClick(int viewId){
//handle event somehow
}
}
Obviously this code wouldn’t do anything useful when compiled, but there’s a couple things about it that we need to understand. If our goal was is simply to have one class talk to another, there’s not anything intrinsically wrong about this approach. If our requirements never changed, then doing extra work is hard to justify. However, once our goals start to include “changing requirements”, we’ll see how quickly this solution starts to break down.
Notice how we can now say that ButtonOne has an explicit dependency on MainActivityOne. I know I sound like Captain Obvious here, but we know this because ButtonOne says MainActivityOne in its code. This would mean that for each different kind of Activity, we either need to create a specialized Button class for each Activity (read: making a bad solution worse), or handle this some other way.
If you find yourself repeating the same/similar lines of code, across several similar classes, abstraction is in order! Let’s get to it.
Example 2: Changing Requirements
A few hours before the official release of Android, our project manager decides that it would be a great time to add a few items to our launch check-list, because keeping clients happy is more important than letting engineers deliver a solid and tested product (amirite lol?).
Specifically, we now need to make sure that our solution satisfies the following new “requirements”:
- We must figure out a way for multiple Views (Buttons, ImageButton, Pickers, etc.) to talk to whatever class contains them.
- This solution must also work with multiple classes, as we now have Fragments which can also contain/handle Views.
In looking at our previous solution, we know that things must change. Filling our View classes with explicit dependencies is quite obviously a terrible approach at this point, and we’ll end up with similar issues in our Fragments/Activities.
In other words, we need to figure out a solution which isn’t tightly-coupled to specific classes:
- Some signs of Tightly-Coupled code: Excessive Explicit Dependencies, changing code in one class/unit breaks other classes/units, excessive specialized classes which do similar things.
- Some signs of Loosely-Coupled code: Dependencies are made explicit/abstract depending on likelihood of changing requriements and what the situation calls for, you can change specific classes/units of code without breaking other classes/units, classes are only as specialized as they need to be and emphasis is put on reducing complexity and repetitive code (this trespasses in the realm of Cohesion, but I’ll save that for another post).
While there’s multiple ways in which we could solve this problem, I feel like the most straight forward and effective solution is to use an interface instead of direct calls to explicit dependencies.
Now, just as you can have a file which contains a single class, we can have a file which contains a single Interface. However, we can also have nested classes/interfaces within the same file as well. I’m just trying to make the point that you don’t need to make a seperate file like this, but you certainly can. I’m sure if you just work with them, you’ll start to see when they should be nested or kept separate; trust your own instincts once you’re more comfortable.
Create the following Interface. If you’re IDE doesn’t have an obvious way to do that, just make a class and change class to interface like so:
//note that it says “interface” instead of class :)
public interface OnWidgetClickListener {
void onClick(int viewId);
/*
Notice how all I’ve done is just pulled the method we originally had in MainActivityOne into
this Interface. How the method is handled is still up to the class that implements this
interface, but we’ve basically made a Contract that requires a class to have a method
which is called onClick, returns void, and must pass a viewId as parameters.
*/
}
Once you get away from all the jargon, interfaces are actually pretty damn straight-forward. There’s nothing mystical going on in this code, it’s basically just a contract which allows two or more classes to communicate without having to know or care who’s on the other end. As long as both classes uphold their end of the contract (i.e. including the methods, supplying the right params, handling return statements appropriately, etc.), we can keep our objects concerned with their own responsibilities/implementation.
Since the utility of doing this may not yet be obvious (it wasn’t to me at first, but hopefully I’m doing a better job teaching you than the sources I first learned from), let’s implement our new solution and see where we’re at from there:
Create the following classes. I’ve chosen to make copies of our original Activity/Button classes, like so:
public class ButtonTwo {
//Replaced Explicit Dependency with Interface!
private OnWidgetClickListener onWidgetClickListener;
/*
After our Activity is created, it will pass itself into the Button with this method.
The key takeaway here, is that we don’t need to have the Explicit Dependency passed in,
as our Button only cares that whatever Class calls this method, implements the interface.
Give that some contemplation :)
*/
public void setOnWidgetClickListener(OnWidgetClickListener onWidgetClickListener) {
this.onWidgetClickListener = onWidgetClickListener;
}
public ButtonTwo(){}
/**
*Fires when a User Clicks the Button.
* Let’s not worry how, just assume it works.
*/
private void OnClick(){
//as long as setOnWidgetClickListener was set (i.e. it isn’t null),
// this method will work the same as before
onWidgetClickListener.onClick(getViewId());
}
//In reality, the View Id is generated at Runtime. At the moment, I don’t care how.
public int getViewId(){
//fake view id
return 123456;
}
}
Followed by:
public class MainActivityTwo extends AppCompatActivity implements OnWidgetClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButtonTwo buttonTwo = new ButtonTwo();
//notice how we can still say “this”, even though the method requires an Interface
// to be passed in? That’s because “this” implements the Interface.
buttonTwo.setOnWidgetClickListener(this);
}
//Note that if this method isn’t present, our compiler/IDE will scream at us. That is BY DESIGN!
@Override
public void onClick(int viewId) {
//handle somehow
}
}
Now that we’ve implemented a new solution, let’s think about what it does for us:
- We no longer have any explicit dependencies in our Button class. This means that our Button is set up to talk to any kind of class, as long as it implements the interface. This is a great example of “loose-coupling” in action.
- We can also reuse this interface with multiple kinds of Views, as the interface doesn’t have any dependencies to begin with.
There’s plenty more minutia I could get into, but let’s leave that for now and finish satisfying the rest of our project’s requirements.
Example 3: I guess Interfaces are pretty useful sometimes…
To finish off this Sprint, we need to make a Fragment which talks to a View the same way in which an Activity can. But we’ll make this more complex by creating a different kind of View as well; an ImageButton:
public class Fragment extends android.support.v4.app.Fragment implements OnWidgetClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButtonTwo buttonTwo = new ButtonTwo();
buttonTwo.setOnWidgetClickListener(this);
ImageButton imageButton = new ImageButton();
imageButton.setOnWidgetClickListener(this);
}
@Override
public void onClick(int viewId) {
//do something
}
}
Followed by:
public class ImageButton {
private OnWidgetClickListener onWidgetClickListener;
public void setOnWidgetClickListener(OnWidgetClickListener onWidgetClickListener) {
this.onWidgetClickListener = onWidgetClickListener;
}
public ImageButton(){}
private void OnClick(){
onWidgetClickListener.onClick(getViewId());
}
public int getViewId(){
return 123456;
}
}
I think you’ve probably got the picture by now, but here’s a quick recap anyway:
- Our old solution kind of worked at first, but started to suck really bad once our requirements changed. We don’t always need flexible solutions, but we should try to understand when we might need them.
- Our new solution can now handle changing requirements quite easily (as we just did). We could cook up all kinds of Views and Managing Classes, but they just need to respect the Contract/Interface in order for things to function properly.
That’s basically it. A few of you may have noticed that we have some repetitive code between our View classes that could also be pulled out, perhaps by creating a “View” parent class for each widget. That’s absolutely true, but I’ll leave that idea for another day.
Thanks for learning.
Follow the wiseAss Community:
https://www.instagram.com/wiseassbrand/
https://www.facebook.com/wiseassblog/
https://twitter.com/wiseass301
http://wiseassblog.com/
Support wiseAss here:
https://www.paypal.me/ryanmkay