Null Pointer Exception in Android UIAutomator Testing

Are you getting a null pointer exception in UIAutomator testing code? Is it occurring during setText() or some other action on the UIObject2?

In our UIAutomater test code here we try to set the text of an EditText here:

UiObject2 uiObject = mDevice.findObject(By.res("com.mycorp.myproject:id/txtCustomerName"));
uiObject.setText(name);

Which throws this exception:

java.lang.NullPointerException
at android.support.test.uiautomator.UiObject2.setText(UiObject2.java:601)
at com.myproject.AutomationBaseTest.testSomething(AutomationBaseTest.java:299)

If we look into this we see that we are blowing up on:

if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, args) &&
        currentText.length() > 0) {
    // TODO: Decide if we should throw here
    Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_SELECTION) failed");
}

Ugh, I think we are failing on

currentText.length() > 0

Because the text was null for this field and we are getting the length of a null object.

No much we can do now except to either wait for Google to update the UIAutomator code.

For me the failing version is:
uiautomator-v18:2.1.2

Included build.gradle here:
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.+'

It looks like there is ticket opened already for this issue:

https://code.google.com/p/android/issues/detail?id=201469


Updated Sept 30, 2016

There apparently is a workaround. Instead of looking up an object using BySelector

public UiObject2 findObject(BySelector selector)

Use the lookup by UiSelector instead which will return an UIObject instead of UIObject2

public UiObject findObject(UiSelector selector)

Example

Note that there is a click() and a delay() added. If I didn’t add it then it would just keep filling in the first text field when I had multiple EditText fields in the view.

UiSelector selector = new UiSelector().resourceId("com.mycorp.myproject:id/txtCustomerName");
object = mDevice.findObject(selector);
object.click();
Thread.sleep(DELAY_SLEEP);
object.setText(data);

Android Apks, Zipfiles and Small Bits of Nastiness

Today we had some issues with apk handling. One of our platforms which we were trying to upload apks kept rejecting them on us. The error we got was:
The APK must be signed with jarsigner

Now an *.apk file is just a renamed *.zip so just rename the file back to *.zip and then extract the files with normal zip tools. We spent hours trying to figure out why our most recently updated apk is different than the previous one and found nothing conclusive. We looked at all the signing and it all looked correct.

You know things are grim when you start manually diffing all the files but even that did not help us.

Time to bring out the big guns. Lets look at the beginning of the files and see if we see something obvious.

--------------- NEW ---------------
hexdump app-xxxxx-release.apk | less
from app-xxxxx-release.apk (generated via android studio 2.2 RC2)
0000000 50 4b 03 04 14 00 08 08 08 00 e9 6e 25 48 6f 45

Let us lookup the format for the zipfile and lets decode it to something more human readable.

local file header signature 4 bytes - 50 4b 03 04
version needed to extract   2 bytes - 14 00
general purpose bit flag    2 bytes - 08 08
compression method          2 bytes - 08 00

bit flags are then: 0000 1000 0000 1000

General purpose bit flag:
  Bit 00: encrypted file
  Bit 01: compression option
  Bit 02: compression option
  Bit 03: data descriptor
* Bit 04: enhanced deflation
  Bit 05: compressed patched data
  Bit 06: strong encryption
  Bit 07-10: unused
  Bit 11: language encoding
* Bit 12: reserved
  Bit 13: mask header values
  Bit 14-15: reserved

That seems strange, the previous file looked like this…

--------------- OLD ---------------
from app-xxxxx-release.apk (older version uploaded at Mar-2016)
0000000 50 4b 03 04 14 00 00 08 08 00 bd 6a 76 48 2c 68

local file header signature     4 bytes - 50 4b 03 04
version needed to extract       2 bytes - 14 00
general purpose bit flag        2 bytes - 00 08
compression method              2 bytes - 08 00

bit flags are then: 0000 0000 0000 1000

General purpose bit flag:
  Bit 00: encrypted file
  Bit 01: compression option
  Bit 02: compression option
  Bit 03: data descriptor
  Bit 04: enhanced deflation
  Bit 05: compressed patched data
  Bit 06: strong encryption
  Bit 07-10: unused
  Bit 11: language encoding
* Bit 12: reserved
  Bit 13: mask header values
  Bit 14-15: reserved

Now we are getting somewhere, let’s search for the central directory segment 50 4b 01 02

--------------- NEW ---------------
011b140                                        50 4b 01
011b150 02 14 00 14 00 08 08 08 00 e9 6e 25 48 6f 45 e0

central directory header            - 50 4b 01 02
Version needed to extract (minimum) - 14 00
General purpose bit flag            - 14 00
Compression method                  - 08 08 

--------------- OLD ---------------
0125e70                            50 4b 01 02 14 00 14
0125e80 00 00 08 08 00 bd 6a 76 48 2c 68 ab 89 

central directory header            - 50 4b 01 02
Version needed to extract (minimum) - 14 00
General purpose bit flag            - 14 00
Compression method                  - 00 08

Notice that the compression method has changed from:
00 08 -> 08 08

Well… it looks like Android Studio 2.2 RC2 changes the compression type of zipfile and our platform processor can’t deal with it.

What a pain, to fix we just rezip the apk with no compression and the upload works fine:

unzip app-xxxxx-debug.apk -d app-xxxxx-debug
cd app-xxxxx-debug
zip -r -0 ../app-xxxxx-debug-rezipped.apk *

Why was this so painful
We got a red herring! Red herrings are extraordinarily painful in software development and while a developer cannot avoid them, it behooves us to not lay these traps for others.

A common red herring occurs during during exception handling. Whenever I see code like this:

try {
    // do a whole bunch of stuff here
} catch (Exception $e) {
    $err = "The name was null";
}

I know that eventually one day some other error will occur and turn this small error into something that takes another developer two days to solve.

Moral
Bad errors hurt way more than unknown errors.

Throw an “Unknown Error” or “Error 500” instead of returning a red herring.

Clearing rePasta Installs

To clear a repasta install, do the following:

1. Remove the app from your machine
https://support.apple.com/en-us/HT202235

2. Remove all rePasta data

(WARNING) This will wipe all rePasta data and should only be done if you intend to delete all rePasta data and are comfortable with the command line.

For version 2.0 of rePasta

defaults delete com.kuchbi.repastaext
pushd "/Users//Library/Application Support/rePasta/"
rm "/Users//Library/Application Support/rePasta/rePasta.archive"
popd

For version 1.0 of rePasta

pushd "/Users//Library/Application Support/rePasta/"
rm "/Users//Library/Application Support/rePasta/rePasta.archive"
popd
pushd "/Users//Library/Preferences"
rm "/Users//Library/Preferences/com.kuchbi.repasta.plist"
rm "/Users//Library/Preferences/com.kuchbi.repasta.plist.lockfile"
popd

Migrate posts to a different WordPress user

A lot of times you start a WordPress blog and publish with the original admin user.  You later decide to create a new user and you need to move the posts over.

The easiest way that I can figure out how to do it is just to update the tables via SQL.

Note Please check your WordPress table prefix and modify before running these commands. Normally it is wp_xxxxx but your admin might have changed it.

Look at the data before running them

select * from wp_users;
select * from wp_posts;

Run the update to migrate the posts to user 2;

update wp_posts set post_author = 2 where post_author = 1;

Django Gives Bad Request (400) Upon Rolling to Production

If the code run in development but fails with a Bad Request (400) error when you roll it to production.

And there is no indication of what is wrong in the logs.

Try toggling
DEBUG = True
from
DEBUG = False
in settings.py

Restart the webserver
sudo service httpd restart

And retry reloading the page. If it works then is is possibly an
ALLOWED_HOSTS issue.

Bad Fix (don’t do this)! This can cause security issues later as one should limit to the relevant hosts.
ALLOWED_HOSTS = ['*']

Doesn’t work I have seen this answer several places, it is wrong and doesn’t work with version 1.9.4 of Django. Do NOT attempt to mix the asterisk with the host parts
ALLOWED_HOSTS = ['repasta.com', '*.repasta.com', 'localhost']

Works
ALLOWED_HOSTS = ['repasta.com', '.repasta.com', 'localhost']

Web page now shows correctly. Yay!