Showing posts with label Adapter. Show all posts
Showing posts with label Adapter. Show all posts

Thursday, January 21, 2016

Calling setCheck on a CheckBox in an Adapter calls onCheckedChange...why?

[Update:

Calling setCheck on a CheckBox (in getView) of an Adapter shouldn't trigger a call to the onCheckedChange listener, because (in my opinion) the listener is listening for the physical touch on the screen, not a programmatic call to configure the display.  Man, did that take some time to figure out, because I kept thinking the feature/bug was in my code, not Android code.

I hope it's a bug/feature in an old library, though I thought I had the latest libs.  A lot of my development notes turn into stacks that start at the answer and descend into the path to the solution, so don't be surprised if a nugget turns up on top of this update that addresses this with the final answer.

This issue just suddenly showed up in code that was working and I didn't update anything, so I spent time thinking my new code had broken something.  Because wow, did it break my program so spectacularly and when I was putting a whole new feature in, and in a way that left me with no awareness that this was the source, that I was tearing through working code like it was broken until I realized it was over in that other part of the program, in an innocuous part of code, that I thought was working.  Because the call to setCheck had been working for five years.  Or I'd just been lucky all that time.

In order to fix the bug (feature?), I set the onCheckedChangeListener to null before calling setCheck, then I set the listener back to the instance that needs to respond to the call.

active.setOnCheckedChangeListener(null);
active.setChecked( isChecked );

active.setOnCheckedChangeListener(this);

50% more code, lost time, and all for no good reason.  Whatever it takes to ship...so long as it works.

I'm keeping the original article below, because it highlights how insidious OS features/bugs are.  They can look like a sequencing problem, when in fact, their behavior performs in unexpected ways.

P. S. I'm pretty sure in the end, it's my ignorance that caused the bug.  I probably should have read the docs for setChecked, groveled a bug database, or checked online for solutions.  But I'm already swimming in Android docs and setting a checkbox has never, in any OS I've worked with (to the best of my recollection), triggered an event as if the user had changed something.

end update]

Just a mental note to remind me not do what I just did, because it took a minute to figure out what was wrong:
I set the listener after setting the state of the control in the getView method in the Adapter.  By setting the listener before setting the state, Android was causing subsequent getTag calls to return null.  I'll figure out why later, because I really have to finish this product I'm working on.
This bug was especially confusing because I don't remember changing any of the code and it just suddenly broke in a place I knew had been working and it suddenly stopped working and I wasn't working on this piece of code at all.

// No.  This code is broken.
CheckBox active = (CheckBox)entry.findViewById( R.id.setting_active );
active.setTag( object );
active.setOnCheckedChangeListener(this);
active.setChecked( isChecked );


// Yes.  This code works.
CheckBox active = (CheckBox)entry.findViewById( R.id.setting_active );
active.setChecked( isChecked );
active.setTag( object );
active.setOnCheckedChangeListener(this);

Friday, January 15, 2016

The Adapter in the DialogFragment with a Checkbox

[Update: It looks like the switches and xml code is necessary, but that I might not have had to make a custom layout to make the adapter respond right.  I'll clear things up as soon as I have it all straight.]

I changed the title of this post to make it sound like the ending of a game of Clue.

These are my notes, meant to help me remember the hours of thrashing it's taken to make a simple dialog in Android.  What a mess.

I made a dialog using a DialogFragment.  It was good.  I added an adapter that was connected to a List returned from a database.  Connected the onClick methods and everything worked.

Then, I wanted to add a checkbox to the row for each element in the Adapter list.  And things got weird.

The problem with Android is that there are too many options, choices, and configurations, each with myriad options, choices, and configurations.  It's a combinatoric nightmare at times, made worse by documentation that refuses to go into any topic with any real depth.  I shouldn't say any, but it feels like it.  It's rare for answers to come from the docs.  Or the books sitting next to me.  Or the internet.  Finding answers usually takes an incredible amount of detective work.

In order to make the DialogFragment work, I had to create my own dialog layout.  Then, I had to override onCreateView and manage the click in the DialogFragment.  So I had to inflate a layout, find the controls, and connect the actions with the methods.  The docs were okay, but kind of light, so it took a little while to get it all sorted out.  Especially since I wanted to just use the code in the AlertDialog.builder thingie, because it took me right to the edge of getting my interface developed.  But I couldn't find a way to make it work.

So I had to override and populate and test and document onCreateView with the custom setup.  More work, more risk, more bugs, more docs, more wasted time.  Not because I can't code, but because it's not clear from the docs when to use the builder and when to use onCreateView from the start.  The dialogs docs shows all kind of cool dialogs that look kind of like the one I wanted to make.  Why wouldn't I think that was the way to build a dialog when all the examples suggested that was all I'd need?

Fine.  Whatever.  Android.

I got it built, got it populated and then tried to use it.  The click didn't work.  It had been working in the other layout.  I figured it'd just be an easy fix to make the program respond to the click.  It wasn't.  I groveled docs until I found that in order to make the row respond to a click event, I had to add the following line to the RelativeLayout that held the controls for each row of the Adapter:

android:descendantFocusability="blocksDescendants"

Of course, why didn't I remember the descendantFocusability switch with the blocksDescendants option?  I can't tell if blocksDescendants is the affirmative or the negative, but I'll look it up again later when I really need to remember yet another detail out of the myriad details that is Android that I'll immediately forget because of all the complexity and inconsistencies.

But adding that switch thing only made the row respond to the click.  The CheckBox didn't respond to the click.  Turns out the CheckBox click has to be managed in the Adapter.  I've seen that one before, but it's been a while, so it took some more groveling through StackOverflow and Android's pathetic excuse for documentation (that frequently contains errors and omissions which require further research, as if developing for this train-wreck isn't time consuming enough.)

Then I read and remembered that I had to add the code to manage the click on the CheckBox in the Adapter.  Of course.  Now my object-oriented code is fragmented into two classes to handle a click event, and that requires duplication of effort as well as remembering that the response to one event happens in one place and the response to the other event happens in another in spite of them both looking like they're happening in one place.

I had to add the following magic line to the XML for the CheckBox that is a member of each row:

android:focusable="false"

Then my adapter had to implement:

implements CompoundButton.OnCheckedChangeListener

// Here's the full class declaration:
public class SpecialSettingsAdapter extends BaseAdapter implements CompoundButton.OnCheckedChangeListener {

And then the method had to be added:

@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ...code... }


And then the parent has to be discovered, and the row id found, or the data needs to be tucked away in the setTag() method when created or recycled, and retrieved by the getTag() method on the click, because unlike the OnItemClickedListener, which gives you a little detail that makes it easy to work with the ListView, this method doesn't give you anything but whether the CheckBox is checked, and the CheckBox itself.  Like nobody using an adapter is going to associate a control with a row number.  I'm sure I'm missing a method that provides all the detail I need, but I've wasted so much time just trying to get this insanely trivial part of my program to work, I could care less right now.