Translate

Sonntag, 21. Dezember 2014

View.setClickable() does not work

What the ...?


Some time ago I stumbled upon something weird, while working on an Android  application again.
I created a button that should not be clickable in the beginning until some
conditions applied. Because it should still be visible, I made it half opaque and set the button to not clickable with button.setClickable(false). But what was happening is,
that even though I used the right method, the button was still useable and I spent
a lot of time figuring out why.

If you want to know, why it seems that view.setClickable(false) doesn't work sometimes,
read my post below!



Example


Let's assume you want to create a button and set it programmatically to not clickable.
Like me in the introduction to this post.
Then you will likely end up with something like this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    private void initAcceptButton() {
        acceptButton = (Button) rootLayout.findViewById(R.id.fragment_home_accept_button);
        acceptButton.setClickable(false);
        acceptButton.setAlpha(HALF_TRANSPARENT);
        acceptButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onAcceptClicked();
            }
        });   
    }


And now you run you project on an emulator or a testdevice and wonder why the button is still
clickable.


Solution


We have to make a closer look to the method view.setOnClickListener() here,
which is the common way to react on userinteractions with a listener set
programmatically. I assume that you've already used this method hundred of times
and that you think that you almost exactly know what it does.
Well, sure, setOnClickListener sets the listener, which you provide as an
argument for the method, and which will be invoked once the view gets clicked.
So far so good. But let's take a look at the documention (I recommend to do
this with all your used methods).

Google says:

Register a callback to be invoked when this view is clicked. If this view is not clickable, it becomes clickable.

Like what? If this view is not clickable, it becomes clickable. Seriously?
So this method does not only set the OnClickListener, but also resets the flag for 

beeing clickable back to true. In my opinion this is a design flaw in the method
setOnClickListener, because it violates the single responsibility principle,
which is also applicable to methods. One method, one task. If the methods
name indicates that it attaches the provided listener to the view, it should do
exactly that thing!

Ok, now we know why our button is still clickable, we can easily change our code to
fix the weird bug and work like intended:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    private void initAcceptButton() {
        acceptButton = (Button) rootLayout.findViewById(R.id.fragment_home_accept_button);
        acceptButton.setAlpha(HALF_TRANSPARENT);
        acceptButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onAcceptClicked();
            }
        });
        acceptButton.setClickable(false);
    }

We just call the setClickable method after the setOnClickListener and prevent it to reset the flag.

I hope you like this post and I was able to help someone out there!

Cheers!





Recommandations

I can highly recommend to read the book Design Patters.
Because I don't want to make any advertisement, I provide a link to wikipedia:

Design Patterns: Elements of Reusable Object-Oriented Software

This is like THE book about design patterns, although there are a lot of more good books
out there, which are maybe easier to read, understand and structured. But they are all based
on this one, thats why I like to share it with you and recommend to read it.