Sunday, January 24, 2016

Android Preferences: PreferenceScreen, PreferenceActivity, PreferenceFragment

Notes about building preferences in Android.  I'm trying to coalesce it into one place for the sake of remembering all the parts and how it all fits together.

Preferences are Androids encapsulation of behavior for setting and saving (exposed) global state for an application.  Here are the parts which are needed:
  1. Preferences xml file
    1. Located not in layouts, but in res/xml directory.
      1. Preferences are contained within root node of PreferenceScreen type.
  2. String resources
    1. string
    2. string-array
  3. PreferenceFragment
  4. PreferenceActivity
  5. AndroidManifest
    1. Add preferences activity
  6. Initializing default values
    1. Set in XML
      1. android:defaultValue="value", where value is a string or @string/id
    2. In main onCreate method and any other activity the user can enter into application
PreferenceActivity:  Post Android 3.0

The activity instantiating the PreferenceFragment has to use a PreferenceFragment, but it can be any Activity, not a PreferenceActivity.  The code below attaches a fragment, from Google's Android Docs:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}

Version 3.0 and Older

Android docs show the following code to instantiate a PreferenceFragment before version 3.0, pay attention to which version you're developing for, because on version 3.0 and higher, addPreferences is deprecated.

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences); // DEPRECATED IN >= 3.0
    }
}

PreferenceFragment

addPreferencesFromResource in PreferenceFragment is not deprecated.

public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.preferences);
    }
    ...
}
Initializing Default Values

Call the following from onCreate of main application activity to make sure default preferences are initialized.  Note, that passing false in the third argument prevents overwriting any modifications to preference values, so it's safe to call in onCreate every time the app starts:

PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);

Preferences XML

Types of preferences are:
  1. Category
    1. PreferenceScreen
      1. Must be root category.
      2. Subsequent declarations create a screen break.
    2. PreferenceCategory
    3. Preference
      1. Creates subcategory
  2. Types
    1. CheckBoxPreference
    2. ListPreference
    3. EditTextPreference
    4. Intent
    5. RingtonePreference
  3. headers
    1. more detail coming

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);

Tuesday, January 19, 2016

Quick Summary: List of things to do for DialogFragment with State

These are my notes for creating a DialogFragment in Android.  There are a number of book-keeping details.  I'll post some code when I get this app I'm building finished.

Things that need to be done:
  • onAttach
    • check for:
      • Pre-M (23) versions and provide method taking Activity
      • M and beyond versions, providing method taking Context
  • onDetach
  • saveInstanceState
    • save settings/state into the bundle passed into the method.
  • onDestroy
    • save settings/state to persistant state (prefs)
  • onCreateDialog
    • Check for savedInstanceState
    • Check for getArguments - some dialogs pass arguments on load
    • If neither savedInstanceState or getArguments, check persistent state (prefs, db).
    • populate multiple choice items, single choice, adapters...
  • if using onCreateView
    • configure controls, set defaults
    • configure click listeners, checkbox listeners (if not in adapter)
      • CheckBox listeners that are aligned with a layout in a compound configuration (Layout holding TextView and CheckBox) need to call setOnCheckedChangeListener(null) before setting the checkbox from the click in the layout, then set it back to setOnCheckedChangeListener(this), in order to prevent a double-click.
  • on item click
    • update whatever member variables that need updating, so they get saved in any configuration or life-cycle change.
  • on positive button  click
    • pass data back to the listener configured in onAttach
  • on negative button click
    • Often do nothing, but sometimes pass a message back to notify whoever called the dialog that it needs to restore the state it had set when it called the previous dialog.
I'll add details when/if I notice anything's missing, but I'll probably add more detail to this article.

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.

Wednesday, January 13, 2016

OnAttach of a Dialog Listener for Version 23 and Below Version 23

I wrestled with this a little until I saw an implementation on the web and another on StackOverflow.  I'm posting it here so I can remember where to find it.  The version 23 Context method calls the Activity method, hence the check for Build.VERSION.SDK_INT < Build.VERSION_CODES.M, to avoid calling onAttachToContext twice, which results in a crash.

public interface NoticeDialogListener {
   public void onDialogPositiveClick( DialogFragment dialog );
   public void onDialogNegativeClick( DialogFragment dialog );
}

NoticeDialogListener _listener;

@TargetApi(23)
@Overridepublic void onAttach( Context context ) {
   super.onAttach(context);
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      onAttachToContext(context);
   }
}

@SuppressWarnings("deprecation")
@Overridepublic void onAttach(Activity activity) {
   super.onAttach(activity);
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      onAttachToContext(activity);
   }
}

public void onAttachToContext( Context context ) {
   Activity activity;

   if( context instanceof Activity ) {
      activity = (Activity) context;
      _listener = (NoticeDialogListener) activity;
   }
   else {
      _listener = (NoticeDialogListener) context;
   }
}

Monday, January 11, 2016

First Post, Coming Soon: Layouts

I created this blog a short time ago and have been thinking about topics to write about.  It's not that there aren't a lot of things related to Android development available to go into.  It's just that I wasn't sure what to write about Android development that wasn't vitriolic and loathsome.

Finally, after (another) three days of working through the combinatoric intricacies of layouts, I finally realized that writing about layouts might be a good place to start.  I'm going to finish the application I'm developing, give some thought to how to present layouts in a way that will hopefully offer insight into how to construct and manage them beyond what's already available, then see if I can put something together.

It's not that the details aren't out there, I personally haven't seen the depth of detail the subject warrants and the admonitions and warnings about complexity and performance only cloud the issue.  I've been groveling over a lot of Android docs, StackOverflow posts, and tutorial websites.  There's a lot of "scratching the surface" and "getting started" docs for Android.  But things can get pretty complicated when you step into real-world configurations, and the "Layouts 101" approach can break down and quickly, leaving one spinning the proverbial wheels as time flies quickly by and frustrations mount.

In the 34 years I've been coding, I've never been less productive as a developer than I've been with Android.  I'm surprised at times that I still spend my time working with it.  For all their money and developers, it's oftentimes maddening that Google's docs are so light and the only outside choices available beyond Google docs are the abundant number of sites that have cloned Google's docs with very little additional information.

I know, I sound like a cranky old man.  I am.