UnfinishedStubbingException
even though you clearly finished the stubbing?
For example, the following code will fail at runtime (mockChannel
has been setup earlier):
1 2 |
|
With an exception like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This is a very good exception message. It gives you some common examples of what you might have done wrong, and how to fix them. However, our code doesn’t match any of the examples.
If we look at the generated Java for this (Tools > Kotlin > Show Kotlin Bytecode > Decompile
), it still looks like it should work - we definitely finish the stubbing:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
So why do we get an UnfinishedStubbingException
? Because we’re accessing a mock before we finish the stubbing.
The failure happens on this line from the decompiled Kotlin bytecode:
1
|
|
Let’s make this a little clearer by using descriptive names:
1
|
|
You can break this apart a little further:
1 2 |
|
So we have a reference to an OngoingStubbing
. Internally, Mockito statically maintains a reference to a MockingProgress
. When we access the mock (mockChannel.id()
) after we’ve already started the stubbing, Mockito goes off to the MockingProgress
and calls through to a validateState()
method, which does this:
1 2 3 4 5 6 7 8 9 10 |
|
Because a stubbing is still in progress, we get an UnfinishedStubbingException
. 💥
We can fix this by pulling the variable out of the mock on the line before we start the stubbing:
1 2 |
|
Of course, make sure to comment why you’re doing this for the next developer who comes along and tries to simplify this by inlining the variable.
Now that we see why it’s happening, it makes sense. Accessing a mock while setting up a different one is a bit of a code smell - they should probably be accessing some constant value. One of the best ways to prevent this situation is to use fakes not mocks for your models. Obviously, this isn’t always easy though - you might have legacy concerns that prevent this or you might need to make sure that you return the same thing from two different interfaces.
This can be a very confusing error to debug, especially when it’s deep in some utility or setup method (where it might not even be obvious that you’re accessing something on a mock), or when you’re in the process of converting some legacy code. Next time you encounter it, consider whether you can do some refactoring to make it harder to hit.
You can find the source code for this post here.
]]>Here I demonstrate how to take existing code that loads a list of screenshots from disk and convert it to load asynchronously using the Paging Library. This example could easily be adapted for network calls.
Throughout this post we’ll be using a Screenshot
class defined as follows:
1 2 |
|
You will also need to import the library:
1
|
|
When the app launches, it shows the user a list of images from a directory they supply during setup. My existing code was a simple repository that loaded all screenshots (as a Uri and width/height values) from this directory via Android’s storage access framework. The code was similar to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
As you can see, we have to go to disk to get information about every screenshot. This is expensive! On my test device I’ve only got around 100 screenshots - on a device that’s been around longer a user could have many hundreds or thousands of screenshots in this folder. Rather than loading these all up front, I need to load these asynchronously. In addition, since these are in a list there’s a high likelihood that the items further down the list won’t even be needed. The Paging Library helps with these problems.
The Paging Library has 3 different “levels” that build on top of each other.
At the base, we have a kind of DataSource
. This can be either “keyed” (such that you need to know about the item at index N-1 in order to know about the item at index N - like a linked list), or “tiled” (such that you can access elements at arbitray indices - like an array list).
Above that, we have a PagedList
, which as its name suggests, is a list that pages its data in from a DataSource
.
Finally we have the PagedListAdapter
, which is a RecyclerView.Adapter
that neatly wraps a PagedList
, calling the correct notifyItem...
methods for you as your data changes (when you call setList
with a new PagedList
) or loads in. This is fairly standard RecyclerView
boilerplate. If you need some custom behaviour, you can duplicate its functionality - it’s just a handy wrapper around the PagedListAdapterHelper
.
A DataSource
is reasonably simple. For a list like this, we implement a TiledDataSource
, doing the expensive disk IO in the loadRange
method. The PagedList
will call this from a background thread when it is time to load a new page of data.
Note that it does not clamp ranges for you - so if you have 10 items and a page size of 6, your second page will be startPosition = 6
and count = 6
- which will give you an IndexOutOfBoundsException
. Make sure to clamp your inputs as I do here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
For my use-case, 8 elements on a ‘page’ was sufficient. I’m showing elements in a 2-column list; most of the elements are around half the window height. You’ll need to decide what works well for you.
Note that here we use the default prefetch distance of pageSize
- that is, as soon as the first item in a given page of data is requested, the next page will begin loading. Depending on your data, you may want this to be smaller or larger.
We also enable placeholders (this is actually enabled by default). Since we know exactly how many elements are going to be in our list (we have Uris for every screenshot, even if we don’t have any details about that screenshot yet) we can use null placeholders while the images load - helping avoid weird scrollbars. Our onBindViewHolder
has to deal with this later. I’d recommend reading the PagedList
docs - they go into more detail on placeholders.
You need to supply two Executor
s - one for posting back to the main thread, and another for background work. In this example we create a main thread Handler
and post events to it directly, but our disk IO Executor
is injected from elsewhere.
Finally, this code has one subtle gotcha - the first two pages will be loaded immediately on whatever thread build()
is called from! From the docs:
Creating a PagedList loads data from the DataSource immediately, and should for this reason be done on a background thread. The constructed PagedList may then be passed to and used on the UI thread. This is done to prevent passing a list with no loaded content to the UI thread, which should generally not be presented to the user.
In this case, we’ll be going to disk 16 times (8 for the first page, and then another 8 as the second page is pre-fetched). I address this later when I wire everything together.
1 2 3 4 5 6 7 8 9 10 11 |
|
The simplest part of all. We just extend PagedListAdapter
, supplying a simple DiffCallback
for comparing Screenshot
objects, and implement onBindViewHolder
and onCreateViewHolder
like normal.
Note if you have custom logic (such as a custom BindingAdapter
- not shown here) you need to be aware that the object returned from getItem
can be null
- these are the placeholders we enabled earlier, and will be null
while the data at that index loads. If your page sizes are appropriate, receiving a null
object will be rare, but you must handle it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Now we have all the pieces we can put them together. Note that I supply an async callback for loading the screenshots - we use the disk executor we supply in the constructor to do the actual building (since this loads the first page or more, as mentioned above!) and then set the result on our list once that load has finished.
For brevity, this code doesn’t consider configuration changes or other kinds of activity destruction.
1 2 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
minHeight
on your list items, or otherwise specify the height when a placeholder is used. If you don’t, the adapter will query for (and the list will try to load) your whole list of 0-height items.PagedList.Builder.build()
- do this off the main thread!getItem
if you enable placeholders, even if you never encounter them in your testing.loadRange
call is not bounded to the size of the list; you need to do this yourself. It will happily handle results smaller than the requested count, however (i.e. when you’re at the end of the list).LiveData
, look into LivePagedListProvider
as it will do most of this overhead for you.Here’s a quick intro to getting set up with Tensorflow for Python 2.7 on Windows (using the recently released Bash on Windows).
This is pretty trivial. Just follow the instructions. I installed this on a Windows 10 64 bit machine with no problems.
Now you have a bash shell (which comes with Python 2.7.4), we have to intall & upgrade pip (Tensorflow requires pip 8.1 or later):
1 2 3 4 |
|
You can test that everything worked by opening up a Python interpreter (ie, just type python
and hit enter) then importing Tensorflow:
1
|
|
If you get no output, everything worked! exit()
that and carry on.
If you’d like to edit your files in the shell, you’re basically good to go - though you’ll need to install git.
However if you’d like to use a nice GUI editor (such as Github’s Atom) you have to beware. There’s a blog post which is very explicit that you must not change Linux files from Windows. Instead we go the other way, and access the Windows filesystem from the shell. This lets us edit our files in an editor while in a Windows environment but then run our changes from our bash shell (which we just set up with Tensorflow).
The Windows filesystem is located under /mnt/c
(or other drive letter), but if things are stored in your user directory then the paths can get a bit long. So first (this is optional) I like to add a shortcut to my working directory so it’s easily accessible by adding the following to my ~/.profile
file:
1
|
|
You’ll need to source ~/.profile
, then you can just cd $TFSCRATCH
whenever you want to get to your working directory.
If you’re using Atom (like I am) there’s a handy included extension called Line Ending Selector - it allows you to specify which line endings you’d like to use. Swap it to LF
when editing your Python files.
Now, running code is as you’d expect from a bash shell. Here I run the basic MNIST sample from Google:
1 2 3 4 5 6 7 8 9 10 11 |
|
So; edit in Windows, run in bash. Easy.
]]>The documentation for this is somewhat hand-wavy. Here I attempt to provide a true step-by-step guide to implementing this. I assume you’re not already using GCM for something else in your app (as that was the case for me).
The first step is to import GCM in your build.gradle
:
1
|
|
Now you can implement GcmTaskService
. This is as simple as the following:
1 2 3 4 5 6 7 8 9 10 11 |
|
Great. So you get a callback, on a different thread, where you can do your stuff.
The instructions say to add the service to the manifest and “Add all applicable intent filters. See details for intent filter support in the GcmTaskService API reference.”
Note that the GcmTaskService documentation has SERVICE_ACTION_EXECUTE_TASK
as the name for the com.google.android.gms.gcm.ACTION_TASK_READY
intent filter. This is right now the only thing we need to care about.
We also need to add the RECEIVE_BOOT_COMPLETED
permission so that our periodic sync will persist across reboots.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
You construct a PeriodicTask
object using a Builder
, and then pass that task to a GcmNetworkManager
instance - and that’s it! You can put this in your SyncService
class and call SyncService.scheduleSync(context)
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
I’ve used the Once library to ensure that my periodic sync is scheduled once per app install (on first launch). I just call this method from my Application class onCreate
.
1 2 3 4 5 6 |
|
Finally, we need to ensure that our periodic task is re-scheduled after an app update by overriding the onInitializeTasks
method.
When your package is removed or updated, all of its network tasks are cleared by the GcmNetworkManager. You can override this method to reschedule them in the case of an updated package. This is not called when your application is first installed.
This is called on your application’s main thread.
This is trivial enough to do:
1 2 3 4 5 6 |
|
You can test that your background process works by firing off the intent that triggers it:
1 2 3 |
|
Note speakman.whatsshakingnz
is the package, and .network.SyncService
is the component name within that package. You need to specify the tag you used earlier, too. If you’ve got a debugger attached you can hit a breakpoint inside your onRunTask
method and note that you’re not on the main thread.
taskParams.getTag()
returns the tag that the task was created with.However, the “delete and re-install” approach wasn’t working as expected - I kept getting a RealmMigrationNeededException
:
1
|
|
This doesn’t make sense! I should be able to uninstall an app and upon reinstalling it I should have a fresh slate to work with. As it turns out, not quite. I couldn’t figure out why at the time, but the workaround was to simply add a deleteRealmMigrationIfNeeded()
call to my Realm configuration when building it. I made a note to deal with this before release, and carried on my way.
This morning I was getting ready to publish and Lint warned me that I didn’t handle Android 6.0 Marshmallow automatic backup. After some investigation, I realised that this was backing up and restoring my Realm files! The order of operations which caused the above issue was:
It seemed simple enough to handle - I’d just exclude the default.realm
and default.realm.lock
files from backup and it’d work. Unfortunately this still backs up the Realm log files, all preference files (plenty of third party libraries make use of shared prefs) and even files related to instant-run! This makes a “clean install” not quite what you’d expect - not clean at all.
My recommendation is to explicitly include only the things you want. For example, I only care about my users settings - I can re-retrieve everything else remotely - so my xml/backup.xml
looks like this:
1 2 3 4 |
|
You also need to reference this file in your manifest:
1 2 3 4 5 |
|
Note that you must specify the .xml
extension when backing up shared preferences files - this is an implementation detail we unfortunately have to worry about, which is a bit strange, especially as we both specify a domain for the file and do not specify the file extension in code.
If you’re having trouble with this, I’d also recommend reading through this StackOverflow question & answer, as following through this helped a lot with figuring out what was going on.
Finally, the last useful bit of info I have is how to wipe a backup. You must have the app installed, and then you can wipe the existing server-side backup with:
1
|
|
Note that the BackupTransportService
part refers to the default transport for backup - you should check what yours is by running adb shell bmgr list transports
- the default will be marked with a *
.
We use Twitter’s Fabric platform to distribute our iOS app for internal testing. The “Beta” dashboard looks a little like this:
If you aren’t using this yet, I can’t recommend it enough - I can distribute a beta build to a select set of testers immediately after archive. I don’t have to deal with iTunes Connect, and as you can see, I get some great info on that distribution, such as who installed it, who’s experienced a crash, and more.
As you can see, each build I send out has a build number attached to it - this is set by CFBundleVersion
in your app’s Info.plist file, or alternately by adjusting the value of the “Build” field in the “General” tab of your app target configuration.
You might also have noticed that some of the builds in that screenshot have the same number. Whoops. Bit difficult to check if someone’s on the latest version if the build number didn’t change! At the time, my build process was “Remember to increment build number, commit changes, press Archive, distribute build”. You can see how that might fall down - any process that includes “Remember to X” is going to fall over eventually.
To avoid this kind of human error, we now have a handy script we’ve injected into our build process which automates the process of incrementing the build number.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
This script does the following:
git status --porcelain
prints anything
Much of this I pieced together from parts of this StackOverflow question and its answers.
To use this, make sure it’s executable (chmod +x increment-build-number.sh
), then add a new “Run Script Phase” to the “Build Phases” tab of your target configuration and drop in the path. I left the script at the top level of my project directory so my path is ${PROJECT_DIR}/increment-build-number.sh
.
To test that it’s working, make a change, don’t commit it, and attempt to build an archive - it should fail. Undo your changes, build an archive, and if it succeeds you should see a new tag listed when you run git tag --list
.
Now I can guarantee a new build number for every archive I distribute. Every time a tester reports a problem or I get a crash report, I can confirm which version they’re running without wondering if it’s actually the latest build or not. I no longer get submission rejections from the App Store because I forgot to increment the build.
Importantly, I now have git tags for every build - so if a problem shows up, I can easily find the exact place in our git history where it was introduced.
This script works well as a starter solution, but it has a few caveats. I’ll address some of these in a future post - however if you’re the only developer working on a simple application, this example is likely fine to use as-is.
These are all things we can fix (either through process or further automation) - I’ll go over these fixes in a later post.
Edit Feb 22nd 2016: Well, I never got around to fixing the above caveats. I’m no longer with Bridgit, and never had the chance to fix the above (with the exception of the schemes issue). My entire time with Bridgit I was either the sole iOS developer, or at least the only person doing releases - so I didn’t have to worry about conflicts. My recommendation is to simply use the current epoch for build numbers (which you can acquire through date +"%s"
), and to have a dedicated build/release machine.
For dealing with schemes, if I recall correctly I had 4 schemes - Debug, Development, Staging, Release. Each of these had a regular build configuration, and an archive configuration. I’d only increment build numbers if the build configuration name included “Archive”, otherwise I’d bail out (avoiding incrementing the build number every time you ran the app on the simulator!). We also used these configurations to enable analytics only on archive.
]]>FragmentStatePagerAdapter
? We’re using one at work for our ticket purchasing wizard. The user enters the wizard, and can progress to the next page once they’ve completed the current one. We control this by manipulating the stack of pages and notifying the adapter that the data has changed when a new page is available.
Unfortunately, when changing pages that have already been loaded, there’s an unexpected bug. Specifically, when you load a page, remove it and then insert a new one in its place, the next time the fragment at that index is loaded, it receives the savedInstanceState
bundle for the old fragment.
The specific use case where I discovered this was the case where a customer is purchasing tickets to a film, and they change their mind about which type of tickets they want.
First, the customer selects tickets that require manually selected seats. We save the tickets, receive the seating data, and send the customer to the next page where they can select from a seat in a map.
If the customer changes their mind at this stage and returns to the previous page, as soon as they make a change to their selected tickets we consider all future pages invalid. We ‘remove’ the seat selection fragment and notify the adapter. If the customer has now selected tickets that don’t require manual seat selection (that is, they’ve chosen tickets for an unallocated seating area), we save the tickets, receive empty seating data, and know to send them on to the “details” page where they can enter in their name and email.
This is where the process breaks down. Since the two fragments are different (one’s called SeatingFragment
and the other is CustomerDetailsFragment
, say), I wasn’t expecting to receive any saved instance state on the first load of the new fragment - however I was getting state passed in! This caused a crash, as I was depending on the state being null to assume first-load.
The state I was seeing was the state for the previously loaded fragment at that index. That is, when the CustomerDetailsFragment
in the example scenario was loaded (replacing the SeatingFragment
), it was receiving the saved state bundle for the SeatingFragment
, when it should’ve been receiving no saved state bundle at all.
I’ve written a very simple example app which shows this behaviour. If you swipe backwards and forwards you can see the fragments labeled “1”, “2”, “3”, colored Red, Yellow and Green. Now, press the ‘Switch Fragment’ button. You’ll be sent back to index 0 (fragment “1”). This forces the removal of fragment “3”, which gets its state saved. But we’ve changed the content of the adapter - so next time you load fragment “3”, you’ll see that its color has changed to Blue. This is a different fragment, but it’s label has been restored from the previous fragments saved state! If you rotate your device, or simply swipe back to the first view and then back to the third again, you’ll see the correct label of “4” (it saves the state fresh when it removes it, resulting in the correct saved state next time it’s loaded).
If we take a look at the source code as of this writing, we can see that the FragmentStatePagerAdapter
stores a list of states:
1
|
|
Looking through the code we can see that this array is used in four places. It’s used in instantiateItem
, destroyItem
, saveState
and restoreState
. We can ignore saveState
and restoreState
for now, as they’re just saving the adapters overall state into an external bundle, and then loading it back up.
First, let’s take a look at what’s going on in destroyItem
. When a fragment is due to be destroyed, this method first starts a transaction (if one isn’t already started), then pads out the mSavedState
array with null entries until it’s at least the size of the index of the fragment we’re removing.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Nothing too exciting there. It then saves the state of the fragment that is being removed into the corresponding index in the mSavedState
list, and removes the fragment:
1 2 3 4 5 |
|
Now let’s see what happens in the other direction - instantiating an item. First thing to do is check and see if we already have a Fragment
object created and stored at the given position. Short-circuit back out with this if we do:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
If however we don’t have a fragment there, we have to create one. This could be because we’ve never seen this page of the ViewPager
before, or it could be because the page was removed due to the left/right limits (recall a ViewPager
will only keep the first page to the left and right of the current one, by default).
1 2 3 4 5 6 |
|
Now, here’s the important part. After we’ve asked our concrete subclass to create/instantiate a fragment for us (through the getItem(position)
call), we check to see if we have any saved state at that position. There’s the crucial part - we’re checking for saved state based on the fragments index in an array, rather than on some unique property of the fragment.
1 2 3 4 5 6 |
|
The issue with this is that the fragment at that position may no longer be the same fragment as was there last time we displayed the page at this position! So that saved state bundle may no longer be the correct one.
Finally, we add the fragment to our list of fragments and display it:
1 2 3 4 5 6 7 8 9 10 |
|
Luckily, there’s a way around this problem! Hurrah!
We simply need some way of identifying the fragments, and comparing whether this identifying value is the same or not when we try to restore state to a freshly instantiated fragment. The best way to do this is to ask our concrete subclass for an identifier for this fragment - a tag.
So, let’s copy the entire source of FragmentStatePagerAdapter
and get started. First thing to do is add a way of getting tags from our subclasses. Since we don’t want to break existing implementations that don’t actually care about swapping out fragments, we won’t make this method abstract. Instead it’ll just return null;
by default, and we treat that as the default case, reproducing existing behaviour.
1 2 3 |
|
Ok, so now we have a way of getting the tags, let’s add an ArrayList<String>
member variable to track our fragment tags:
1
|
|
Now we go through and handle this in all 4 places where mSavedState
is touched.
In instantiateItem
we must find the tag for the newly instantiated fragment first. Once we’ve got that, if we have saved state we can then compare this new tag with the saved tag. If they match, then we restore the state! If they don’t, then we don’t restore state. Easy.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Note that we also add the fragment using the FragmentTransaction#add (int containerViewId, Fragment fragment, String tag)
signature - that is, we actually use the tag when adding our fragment:
1 2 3 4 |
|
In destroyItem
we just mirror what’s done to mSavedState
. We pad it out if necessary…
1 2 3 4 5 6 |
|
…then we save the tag at that location.
1 2 3 4 5 6 |
|
Finally we have the saveState
and restoreState
methods. These are pretty trival changes. In saveState
we put the saved fragment tags into the Bundle
:
1
|
|
And then in restoreState
, surprise, we restore the saved fragment tags from the bundle:
1
|
|
Two last things to do:
getTag(int position)
to return a unique tag for each fragmentIf you forget either of these things, you’ll just have the same behaviour as before. In my demo app, this would look something like the following - obviously you’ll need to adjust this to suit your own data source:
1 2 3 4 |
|
And that’s it! Those’re the changes we need to make to the FragmentStatePagerAdapter
for it to stop misbehaving and restoring the wrong state to fragments in different locations.
You can find a complete example of this fixed class in the sample project. There’s some lines commented out in the adapter in MainActivity
; just swap the class definition and uncomment the method and you’ve magically got an adapter working as expected!
Sure are.
getTag(int position)
, or else you’ll continue to see the old behaviour.getTag(int position)
must return a unique tag for each fragment.FragmentStatePagerAdapter
is an inner class of a Fragment
, and you’re calling that fragments getTag()
method, then that call will now give a compile error. You’ll need to change it to MyParentFragment.this.getTag()
instead, or change the fixed adapter to use a different method signature - getFragmentTag(int position)
, perhaps.FragmentStatePagerAdapter
when the support library updates. This is unlikely to be an issue though - it’s been in source for over 18 months as of this writing (Feb 20, 2014) without a single change (the v13 version, too).restoreState
method it checks for keys starting with “f” and assumes they’re fragments!No worries! Maybe one day you’ll write a post on how to fix some obscure bug that I’m having trouble with.
If you have any questions, you can ask me on Twitter, or Google+, or open an issue on (or send a pull request to) the Github project.
]]>12-16 12:01:40.446: W/System.err(3873): org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject
Faced this issue, again, at work today. We have a build system with build variants for different customers. To add a new customer, we just create a new folder, add the images and add a JSON config file to suit the new customers settings. We read from that file and into a JSON string (and then into a JSON object) something like this:
1 2 3 |
|
Sometimes, new customers speak a language other than English, and we have to save non-ASCII characters. In this case, the file gets saved as UTF-8. Testing this isn’t a problem on my devices (Galaxy S2/Nexus 7) - but my tester has twice come back to me now and said that it doesn’t work on our 2.3 device.
Figuring out the problem this time was pretty quick - I plugged her test phone in, saw this error popping up in Logcat and it triggered my memory about what was wrong. The problem is that the value hidden in that error message (encoded in this case as 0xEF 0xBB 0xBF
, often showing up as  - see the Wikipedia page) is a Byte Order Mark. This is used to signal the endianess of the text stream. However, in UTF-8 it probably shouldn’t even be there (but is still technically legitimate):
The Unicode Standard permits the BOM in UTF-8, but does not require nor recommend its use. Byte order has no meaning in UTF-8, so its only use in UTF-8 is to signal at the start that the text stream is encoded in UTF-8.
Because of this, Java doesn’t actually support automatically reading the BOM as an indicator of encoding - it adds it as part of the string, so you have to strip it out. If you don’t, automatic parsers such as the one built into JSONObject may freak out and give you a confusing error like the one above. Reading the message, it appears that it can’t convert a String, which doesn’t make sense, as the constructor takes a String. It’s actually referring to the invisible (or in some cases barely visible) BOM character between the words “Value” and “of”.
So why is it working correctly on my devices? This bug logged in 2011 was ‘fixed’ by updating the built in JSON reader to handle UTF-8 strings with or without the Byte Order Mark. This change came in with Ice Cream Sandwich (Android 4.0) - hence why my tester is seeing the problem and I am not.
The fix in our case has been to simply fix the file - the BOM shouldn’t be there anyway, so we just remove it. You can do this in Notepad++ by opening the UTF-8 file, clicking the Encoding menu and selecting “Encode in UTF-8 without BOM”. This may show up as “ANSI as UTF-8” in the encoding field at the bottom-right.
The other, more general option (if you can’t control the source of the JSON you’re trying to parse) is to always ‘clean’ your incoming JSON string. This workaround was suggested in the original bug:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
There are also many, many other solutions available.
Wonderful. Note that if you know your file is encoded a certain way, you should always pass the encoding to the reader - never depend on the default charset to be what you need. It’s worth spending some time reading about Unicode and the various encodings you’re likely to encounter - Windows-1252 (or CP-1252), UTF-8 and UTF-16, and how to interpret the bytes for these encodings. I find fileformat.info to be extremely useful, as well as the HexEditor Notepad++ plugin for looking directly at the bytes (which is reportedly a bit unstable with the latest version of NP++, though I’ve never had any issues) - there will no doubt be something similar either built in to or available for your text editor of choice.
Text encoding problems are painful to deal with - and if you’re not sure what you should be using, use UTF-8.
]]>I’ll assume you have git and ant installed.
First, we have to get the library:
1
|
|
Now we have the source code, we need to build it:
1 2 |
|
Now, we have volley.jar
in the bin
directory. Copy this into libs
in Eclipse (via drag & drop through the GUI so it sets everything up properly) or set it up in your build.gradle
if you’re using Studio. Wonderful.
Volley works by sending requests to a RequestQueue
. To create one of these requests, you override the Request
object and implement a few methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
You can send these with a priority (by overriding getPriority()
), so that higher priority requests get sent to the front of the queue, not the back. Useful if, for example, you’re loading some images in the background, but the user clicks on something that needs immediate download.
Before we can use a queue, we have to set one up. This should done as a singleton. Since the easiest way of creating a RequestQueue
requires a Context
, you can either subclass Application
(which the official docs advise against) or do it this way:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
And usage:
1
|
|
There are a few other ways of constructing a queue, allowing you to specify your own HTTP stack, cache, thread pool size, etc.
So now we’ve got our default queue set up, we can send a request. As you saw earlier, a request requires you to implement two methods - parseNetworkResponse
and deliverResponse
. The first of these methods parses your network response into some object that you’re expecting, from a worker thread. The second delivers that response back to your UI thread, unless parseNetworkResponse
returns null
.
To fetch some simple JSON back from a given URL, there’s a convenient utility class that comes packaged in com.android.volley.toolbox
called JsonRequest
.
This class manages parsing any request body string into a byte array (the getBody
method returns a byte[]
), as well as specifying the content type headers, etc. You still have to implement the parseNetworkResponse
abstract method from before, though you now supply a listener for the success case instead of an override. So now our request looks a bit like this (using Gson for parsing the response, because it’s awesome):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
POSTing JSON back is equally as easy! Instead of passing in null for our body, we pass in a JSON String.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Sort-of. I’ve abstracted the JSON parsing out so you only have to handle the success and the failure cases. If you’ve got more complex objects that need custom type adapters, you could put the Gson object creation and type adapter registration into another class somewhere and call it from here. Just drop this GsonRequest
class in and you can use it by simply passing in the class of object you expect, as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
|
And usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Loading images with Volley is one of my favourite features of the library. Once it’s setup, it’s really easy to use. It handles loading images off the UI thread, can show a default image and an error one, and handles caching all for you. You need to set up an ImageLoader
, similar to how you set up the RequestQueue
as a singleton:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Now, replace your ImageView
objects with NetworkImageView
ones:
1 2 3 |
|
And finally, all you need to do is pass your NetworkImageView
the URL of the image you’d like loaded, and the ImageLoader
:
1 2 |
|
There are also methods available such as setDefaultImageResId
and setErrorImageResId
, for supplying default and error resources.
Volley has a default TTL on requests of 2.5 seconds - after this, it’ll retry the request. This can result in some unexpected behaviour - for example where your error listener gets called (immediately after the retry), then your success listener gets called a little while later (when the original request returns). You can fix this by specifying a timeout in your request:
1 2 3 |
|
Another thing to be aware of is that the image loading will cache images in the full size they come down as. This means if you’re downloading images at full resolution but only displaying them at a much smaller one, you’re going to be caching them at full res. If this is in a list view, you’re going to be pushing stuff out of the cache (and then re-downloading them) a lot more often than desirable.
You can get around this by changing some code in NetworkImageView
. The important bit is near the end of the loadImageIfNecessary(final boolean isInLayoutPass)
method. The code makes a call to the following method in the ImageLoader
class:
1 2 3 |
|
Notice how that calls an overload that takes some int
values?
1 2 3 4 |
|
Well, if we ‘fix’ the code back in NetworkImageView
to pass in the width & height of the view, then the image gets scaled down and cached at the smaller size (this takes is utilised in the doParse
method of ImageRequest
:
1 2 3 4 5 6 |
|
Note that if you pass the image URL in for use in a different place, it’ll use the (scaled down) image from the cache - so if you need the full resolution image, this solution will need some modification.
And I certainly haven’t covered all of it here. There’s loads more that it can do. I’d recommend looking through some of the classes in com.android.volley.toolbox
to see what else is already written for you, and for some ideas of how to use some of the other cool features it has to offer.
OutOfMemoryError
‘bitmap size exceeds VM budget’. This issue can present itself immediately when testing, however on older devices it may not manifest except in certain cases. The reason for this is as follows:
In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
Depending on what you’re doing, there is a way to get around this.
The situation where I encountered this was at work when working on a control for displaying a seat map for a movie theatre as part of a ticket purchasing wizard. It shows the screen, the seats, some seat numbers, etc. From there the app user is able to pick seats to sit in for watching the movie. Previously this had been built by drawing many Button
controls with a custom drawable. This was terribly inefficient, as all those controls had to be totally redrawn whenever the user tried to zoom, and was practically unusable for large theatres (with hundreds of seats) even on top end devices.
Clearly this was due for a rewrite. The method I worked out for doing this was to create a ‘base’ bitmap from the theatre data showing all empty and already-sold seats. I’d use this as a static base image, and then paint ‘selected’ seats on top of that as the user taps to select/deselect seats they’d like to sit in.
This method had a few benefits we didn’t enjoy with the old method:
So off I went and coded this brilliant design. The code for the SeatingImageView
control ended up looking something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
So you’d set your base image, then overlay some selected seat images on top of that as necessary. I tested this on my phone (Galaxy SII i9100, Android 4.1) and my Nexus 7 (2012 model, Android 4.3) and sent it off to QA to be approved.
But our tester sent it back. She said it was crashing whenever she selected a seat - but only on certain cinemas. This was odd, as I couldn’t replicate it at all. I went and had a chat to her, and sure enough, it was definitely crashing on her device (an old HTC running Android 2.3). I borrowed the phone and went about figuring this out.
Of course, it was the dreaded OutOfMemoryError
. But how to fix this? I was already capping the size of the bitmap when building the base image and scaling seats down to fit. If I forced the max size to be lower, then large cinemas started to look awfully pixelated when zoomed in. I did some logging of the memory, and it appeared that the OOM was occurring at the time we created the new image with the seats - Bitmap mutable = immutablebase.copy(Bitmap.Config.ARGB_8888, true);
.
We had in memory at this point 3 copies of the bitmap:
mImmutableBase
)setImageBitmap(mImmutableBaseBitmap.copy(Bitmap.Config.RGB_8888, true));
)Bitmap mutable = immutablebase.copy(Bitmap.Config.RGB_8888, true);
)That seemed easy enough to handle - we’d just get rid of the one being displayed before we created a copy of the immutable base, then we’d only ever have 2 in memory at once. I updated my drawSeats
method to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
But this didn’t work either!
Reading through this thread (post #80 down is especially useful) helps to shed some light on what’s causing us to run out of memory here. The bitmap has memory in both the native and Dalvik heap, and it’s not getting recycled from native quickly enough. Luckily, there is a way to force this to occur.
The fix was to:
getDrawable()
ImageView
to show nothing - setImageBitmap(null)
- while still holding a reference to the old bitmaprecycle()
on the old bitmap - this clears the native heap allocationSystem.gc()
, I found that this was still required to consistently remove the bitmap from memory1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
This fixed the bug! The memory logging I’d put in showed that were were only keeping at max two copies of the base image in memory at a time, and more importantly we weren’t seeing any crashes.
However, the logs were also showing that we were still precariously close to the memory limit for the device. I was concerned about other devices with even lower memory limits - or other edge case theatre layouts we didn’t have examples for. This required something of a minor design change to resolve.
I’d been depending on using ARGB_8888 config for the alpha channel, providing a transparent background behind the seat images to match the background for the rest of the screen. After some experimentation I discovered that there was no noticable change in the colour of my seat images when switching to RGB_565, but the memory usage dropped by a large amount - enough that I was happy I wasn’t going to hit the cap again. It was simple enough to modify the control to take a ‘background’ colour at creation, which reproduced the effects of transparency. Of course if you’re faced with the same situation but are using a background with a gradient, or a colour not accurately reproducable in RGB_565, this will not work for you as easily.
Other tips:
While implementing this multiple currency support, I came across something of a problem - the Android Currency code doesn’t behave quite as you’d hope, and is inconsistent across versions. Sometimes you get a currency symbol where you’re supposed to, sometimes you don’t - and sometimes the symbol is a slightly different symbol in one locale to that which is in another (ï¿¥, I’m looking at you). I’ve now got a set of test cases which do a fair job of explaining the issue, as well as the solution we came up with which mostly works.
Our app is designed to use a JSON configuration file to set customer-specific settings during build, including a single default currency symbol which we were prepending to the currency value. The items being purchased in the app all come down from a web service, and the values are specified in cents - every value gets divided by 100 to get the ‘true’ value for the currency. This is legacy behaviour in the service backend that has been in place for over a decade and cannot be changed.
Luckily however, we can add to it. The design that came through initially was simply to allow the customer to specify a currency symbol on a per-site basis, and then expose this via the web service. Being developers, we didn’t feel this was good enough. What about the case where the ‘special case’ sites (that is, the ones which don’t match the default currency symbol) are in a different currency with the same symbol? (NZ$/AU$, or CA$/US$) Eventually we settled on specifying an ISO 4217 currency code per location that is a special case - 3 characters, and they’re associated with a symbol. This is populated in a drop-down list in the existing configuration application for the site.
So, now we’ve got this currency code coming through for these special case sites. We should be able to format currency values using this, right? According to the Currency documentation, we can. Our intent was to format the currency value (whether numbers appear as 15,00 € like in France, or as $15.00 like in the US) using the device locale, while customising the currency symbol - this would provide optimum readability for the user (the value appears in the format they’re most used to) while also providing an accurate representation of what currency the figure is in.
So, after a bit of research I figured I should be able to write code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Unfortunately, while I initially tested this with one or two samples and figured it would work as advertised, I was wrong.
As I was writing unit tests for this, I discovered that I wasn’t getting expected results. I was running these tests on a device (a Galaxy S II) set to New Zealand locale - and my tests were failing. When I ran them on an emulator (which defaults to US locale), I got different results - some of the failing tests passed, but I got a new set of failures.
The failures on my device were cases where I was getting the ISO currency code back instead of the symbol. “AUD” instead of “AU$”, etc. The case where I supplied “NZD” I was expecting “NZ$” - yet I was getting “$”, and not “NZD” like the others. And even more confusing, these tests were passing on the emulator - I was getting the expected symbols!
So I tried supplying a Locale
object and using that, rather than the device default, so I could see what behaviour I got on a variety of locales:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Looking at the code above, the line String symbol = currency.getSymbol()
is the one that fetches the symbol. The getSymbol()
method has an overload getSymbol(Locale locale)
. If I took the step of passing this method the locale
parameter, I got different results. Interesting!
The documentation for public String getSymbol (Locale locale)
in the Currency
class says:
Added in API level 1
Returns the localized currency symbol for this currency in locale. That is, given “USD” and Locale.US, you’d get “$”, but given “USD” and a non-US locale, you’d get “US$”.If the locale only specifies a language rather than a language and a country (such as Locale.JAPANESE or {new Locale(“en”, “”)} rather than Locale.JAPAN or {new Locale(“en”, “US”)}), the ISO 4217 currency code is returned.
If there is no locale-specific currency symbol, the ISO 4217 currency code is returned.
The important part there is the last bit. If there is no locale-specific currency symbol, the ISO 4217 currency code is returned. That’s what I was seeing - if the locale didn’t ‘know’ how to format a currency symbol for the supplied currency, it would just return the currency code.
Huh, okay. How do we fix that, then?
As it turns out, the US locale has formatting for most currency codes. It doesn’t quite match in some cases (it might provide UK£ instead of £UK, for example) and in certain scenarios it doesn’t have a symbol, but for the most part, it’s better than anything else. There’s one case where we don’t want this to use the US locale though - USD ISO code, with a non-US device locale. We can fix that with a hard coded symbol of ‘US$’.
There is also the issue that when the currency code matches the users locale (for example, NZD and en_NZ locale), the users see NZ$ rather than $. We handle this internally by utilising our existing behaviour of having a default currency symbol specified in the app build. If we receive a response that contains a currency code, we utilise the code for getting a currency symbol for that code. If we don’t receive a currency code (which is the case most of the time, to maintain backwards compatibility, and the majority of cinemas don’t actually support multiple countries), we just use the symbol from the build config. That code isn’t shown here, but it’s a simple check to see if the isoCurrencyCode
value is null or empty, and if it is then we use the default symbol.
So, the final version of the method is as follows. Note that we still use the device locale for formatting the numbers, and where the symbol goes within the string - we only use US locale for the symbol itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Now, that seems pretty good, right? Not quite - there’s a few scenarios you need to be aware of. Formatting numbers for France, one expects something like “15,00 $NZ” - that’s a comma instead of a period, a space after the numbers, followed by the symbol and then the country code. That’s how the French locale (fr_FR) localises currency values. Unfortunately, since we’re using the US locale (en_US) to format the symbol, we get “15,00 NZ$”. This is still completely readable, however it’s not quite right. It’s probably good enough, but still unfortunate.
Additionally, I was also getting differences in my JPY tests. First, my phone (running Android 4.1) was getting ï¿¥15 - that was what I expected, the yen does not have more than one unit. However the emulator was getting ï¿¥15.00 - two decimal places, where there should be 0! So, I fired up a few more emulators and did some more testing.
It turns out that for versions of Android 4.0.3 and down, the currency format (the NumberFormat.getCurrencyInstance(locale);
formatter) does not work correctly for locales that have only single units in their currency. If you check out the sample project you can see that for the Japanese Yen and the Chilean Peso, both of which have only single units, earlier versions of Android will format these with decimal places, where as 4.1 and up does not.
But wait, there’s still more. Different locales also return different versions of the same symbol. The US locale (en_US) returns a full-width ï¿¥ symbol, where as the Japan locale (ja_JP) returns a regular ï¿¥ symbol. Similarly, the French locale (fr_FR) will return a non-breaking space between the digits and the symbol, where as the French Canadian locale (fr_CA) which formats numbers the same way (“15,00 $NZ”, like above) uses a regular space. This makes writing tests a pain in the ass, but it shouldn’t have much impact on actually displaying these things to the user - of course, you do have to be careful if you’re doing string comparisons.
So, all up, what should’ve taken me an hour or so took over 2 days, working out why things weren’t behaving themselves. Even after all that time, I’m still not sure I’ve got it all figured out - I’ve rewritten this blog post about 5 times, and I can’t reproduce some things I had noted down from when I was doing this at work. Hopefully, reading through this saves someone else some pain and gives them an explanation as to why their tests are failing - or worse, users are reporting strange behaviour around currencies.
Of course, if you’re only targetting one market, this may not be a problem for you at all. But be prepared for the day you expand - this could definitely come back and bite you.
(As a side note, I realise that the test cases I’ve supplied are far from comprehensive - I especially lost my incentive to continue when I discovered they were behaving differently on different devices, at least until I’d discovered why that was the case.)
]]>My first attempt looked like this - in fact, this is currently what my ’Wookmark Viewer’ licenses page looks like:
However, while this page details the libraries used and their licenses, and provides links to those things, it isn’t good enough. It says in the Apache License, Version 2.0:
You must give any other recipients of the Work or Derivative Works a copy of this License.
So how should I do that? Do I have to include the same license multiple times for different libraries that use the same license? How do I include that in a regular old TextView?
I revisited these issues at work recently when I was tasked with adding the licenses page to our app. After some time looking around online, I couldn’t really find any suggestions as to how this page should behave, how it should look, or how to display licenses for multiple libraries. The only thing I found was this Stack Overflow question, but that was enough of a lead to set me on the right path. I checked out some of the other Google apps - see the Play Music and Gmail apps licenses pages below. This is simply an HTML page displayed in a DialogFragment. This is more like it!
This answers the questions of how to display things nicely (including the full license text), how to handle multiple libraries with the same license, how to display any copyright notice, and how to link to any modified source code, as required in the GPL-2.0 license, for example. It should also be simple enough to set up to automatically construct this page (or a similar one) in your build scripts - including any custom text you may want to add, as I’ve done above for my original attempt on the Wookmark app.
I’ve created a DialogFragment that reproduces the Google apps licenses page experience - AndroidLicensesPage on Github.
If you want to use this fragment in your application, you need to include LicensesFragment.java
in your projects source, as well as including the licenses_fragment.xml
layout file in /res/layout
and the licenses.html
file in /res/raw
. You should update the namespace to suit.
To display licenses for your app, you need to update the licenses.html
file to suit (including any libraries you’ve used, their licenses, copyrights, and any links to source you may have modified, if required), then you can display it as you would any other DialogFragment:
1 2 3 4 5 6 7 8 9 10 11 |
|
There are some TODOs in the LicensesFragment file - you should modify these things to suit your environment, though things will work fine without you needing to touch anything.
If you want to see an example of this in action, clone the repository and you should be able to open the included AndroidLicensesPageExampleProject in Android Studio. It should run directly on any device or emulator running Android 2.1 or higher.
]]>Edit July 10 2013: Note that this post only applies to projects not using Gradle.
Most of this information can be found on the Android Developer site.
This guide assumes you already have an application project set up, and you’re looking to add this support library in to enable new features. For setting up the main project, check out the Android Studio documentation.
google-play-services_lib/
folder (the whole folder, not just the .jar) from <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/
to your lib/
folder in your project.google-play-services_lib
folder (the one you just copied into your project directory, not the one from your SDK directory).google-play-services_lib/libs/
folder (again, the one in your application directory and not the SDK directory) and pick google-play-services.jar.The important part is that you import the whole project, not just the JAR file. If you only import the JAR, you don’t get the associated resources. Check out this Google Developers video for more detail - it’s the first mistake they list.
If you use proguard, add this to your proguard-project.txt:
-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}
You can confirm that it’s worked by building the project (you may need to do a Build -> Rebuild Project) and making sure there’s no errors. To see the imported module in the Project explorer, switch the drop down at the top of the Project explorer to ‘Packages’ instead of ‘Project’.
If you’re adding a project other than Google Play Services, such as ActionBarSherlock, that project may use the Android support library. If you’re using this too (as is likely) you need to ensure you only have one support library JAR referenced. You should export the JAR from the library (as in step 7 above) and then delete the one in your own module.
]]>This post-mortem picks apart what I enjoyed building, what caused me troubles, and what needs changing - the app is far from perfect.
The app should be fairly easy to pick up and use for anyone who’s used an app with a sliding menu before. These include Facebook, NHL Game Center, YouTube and Falcon Pro, among others - so the sliding menu system is not an uncommon one. While I’ve tried to keep the user experience consistent, the final result is actually (in my opinion) quite bad, and completely inconsistent.
Swipe open the side-menu, and choose ‘New’ (assuming you’re currently looking at ‘Popular’ images). Now, press the Back button on the device - and exit the app. The idea of swapping the fragments in/out via the menu, instead of navigating to a new page, may be lost on the user. Certainly I’ve exited the app by accident a few times when I just wanted to go to the previous page.
However, even this poor behaviour isn’t consistent. If you go to the ‘Color Search’ page and search of a colour, you can hit the Back button and return to the search page from the results. I consider this to be good behaviour on its own, but it’s still very confusing since it’s so different to every other page.
Finally, the ‘Settings’ page is different again. Clicking on Settings brings forward an entirely new activity - with no access to the sliding menu, no branding (on 2.2/2.3), and no subheadings on the settings. On newer versions of Android you’re provided with a PreferenceFragment
which you can use like a regular PreferenceActivity
, but as a fragment. Unfortunately this has not been implemented in the support library, and I couldn’t find a quick/easy way of doing it, so (since I was keen to just get the damn thing finished) I went the easy way of just using a PreferenceActivity
- resulting in this inconsistent behaviour.
Wookmark Viewer utilises a total of 5 open source libraries: SlidingMenu for the menu system; ActionBarSherlock for the action bar view styling; LazyList to lazy-load the images automatically in the background, off the UI thread; ColorPickerPreference for the colour picker; and finally AntipodalWall for the Pinterest-style view - more on this below.
Sliding Menu and ActionBar Sherlock worked well together - all I had to do was fork Sliding Menu and let the classes extend SherlockFragment
and SherlockActivity
instead. Easy. Open source is good for this - I was able to modify the code as necessary, on top of being able to step through the code when I wanted to know what was going on behind the scenes.
I made some modifications to the LazyList library as well. Originally it scaled all images it loaded; scaling is now optional for each image loaded, with the ability to set the scale factor when the ImageLoader
class is instantiated. I also added the ability to supply a listener when you call DisplayImage
, so that when the image is finished loading the calling class can update the UI - for example by removing a progress bar. I need to provide overloads for the DisplayImage
method so that existing code can continue to use the class exactly as before, and then I’ll send a pull request back to the original author.
I chose the ‘AntipodalWall’ library as my base Pinterest-style view to work with as it allowed a varying number of columns, worked on Android 2.2 and up, and was easy to set up - none of the other options met all those criteria. Unfortunately, the app quickly ran out of memory - there was no view recycling. This is fine for a static, finite number of resources, but the nature of the app means that there are an infinite number of images to load. I spent some time working on rewriting this to implement view recycling - it’s now an AdapterView
, recycling views as they scroll off screen and supplying them back to the getView
method of the Adapter
. In addition I’ve overridden the onSaveInstanceState
and onRestoreInstanceState
methods so the scroll position and visible views are kept when the device is rotated or the app is paused.
There are a few problems with my implementation of this, however. It could be simplified a little, as I’m not sure my class and variable names will make too much sense to others, though I did work on refactoring that a little recently. It doesn’t handle views resizing (for example if content like an image loads in the background and a view needs resizing) - in fact, it requires views to come pre-measured from the Adapter
and then scales them to fit, which only really works for my use case of knowing the size beforehand, and probably doesn’t work for views that contain more than just a single image. And it doesn’t behave as expected with respect to some methods; the setNumberOfColumns
methods for example cannot be called at any time, it has to be called before you call setAdapter
, which has the potential for confusion.
Once I’ve fixed these issues, I’m a little confused as to what I should be doing about sending a pull request or not. A diff between the original and my code reveals that while a lot of the original code remains, the file is mostly new content. Certainly, anyone who was using the old library would not be able to use my version without a significant refactor - they’d have to implement Adapter
, for a start. So I don’t know if I should leave my fork as it is - a fork - or send a pull request, or create a new repository with a new name as a new project altogether. When I’m happy with it, I’ll probably send an email to the creator of the original project and get his opinion; I’m more than happy to send the PR if he wants it!
Well, for an actual use-case for the app, as the API stands right now there isn’t much of one, really. The mobile site works well, looks a lot better, and offers much greater functionality. But it does have some limitations - it only offers a single-column view for images, for example.
Mostly, writing this app was simply good experience. I got some experience with some new libraries, and a new API - which is great, as reading other peoples code is an excellent way to grow as a developer. I learned a lot about the Android application life cycle (which I’d effectively managed to ignore with my other Android app), and figured out how to recycle views efficiently.
Currently, there’s no way to filter adult/NSFW content. While these are tagged on the website, that information is unfortunately not available in the data returned from the API. I’ve been in contact with the creator of the Wookmark website recently, and asked if it’s possible to add this information - if so, I can filter all NSFW content, and obey the ‘No nudity’ rule of the Play Store developer content policy.
Until then, if you want to load the app onto your device manually, you can either build it from source or I can send you an APK file - just send me a message on Twitter.
Unfortunately, Color Search doesn’t work that well. Indexing images by colour is, I understand, fairly difficult. It works for values such as 0xFF0000 (red), or 0x000000 (black) - but not for 0xFA0000 (slightly less saturated red). A little experimentation shows the search works for colour values 0xXXYYZZ - that is, where the R/G/B values are chosen from the set of numbers 00, 11, 22, …, DD, EE, FF, rather than the full range 00-FF. I can probably hack this fairly easily to find a “closest” colour to the one that comes out of the colour picker - this is a better solution than the current case of not finding any results at all, in most cases.
Partway through development of this app I switched from Eclipse to IntelliJ - I wish I’d done it sooner. IntelliJ worked immediately with my already installed Android SDK, and after figuring out a few nuances I’ve found it to be a significantly easier IDE to use, and much more stable. One problem I’ve recently noticed though is that Eclipse used tab indentation, and IntelliJ uses 4 spaces, so my indentation on any class I’ve edited in IntelliJ is now a mixture of both. Oops.
That’s all the thoughts I have for now. It’s unfortunate I can’t keep this app up on the Play Store, as I did spend some time on it, but that’s the way of things, I guess. If I can get a little extra info out of the API then it’ll only take a few minutes to update and publish. In the meantime, I’ve sure learned a lot!
]]>Note: This post assumes you’ve already converted your Windows Phone 7 app to Windows Phone 8 and are wondering how to get rid of the deprecation messages.
It covers converting your code to use the new Pushpins (assuming you were using the default ones previously - though a glance over the Windows Phone 7 custom Pushpin image docs indicates to me that it should be fairly straightforward to port those over as well, after a read through this), as well as the changes required in XAML.
This post does not delve into displaying routes or providing directions, etc.
First, delete both the old Windows Phone Toolkit if you were using it (I was using it for the ToggleSwitch
control), and the Microsoft.Controls.Phone.Maps
library from your project references. If some of your other code breaks (ie, the ToggleSwitch), don’t worry, we’re going to fix that later.
There will of course be references to these namespaces throughout your code. Feel free to delete these now, or as we go.
While you’re changing things in the Solution Explorer, you may as well update the WMAppManifest file - you need to include the new IP_CAP_MAP
capability.
Next, edit your XAML to reference the new xmlns declaration:
1
|
|
Now your <maps />
tag will probably show up with some blue squiggly underlines, previously I had this on one of my Map pages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
However, my new XAML looks like this:
1 2 3 4 5 |
|
Much simpler, and all the previous properties I had marked as Collapsed
are now collapsed by default (and no longer accessible).
Unfortunately the other part of the Maps that we’re talking about today (the Pushpins) has become more complicated, not less. Where previously you would simply create a Pushpin
object, assign it a location and some content (and perhaps an event listener), and then add it via a Map.Children.Add(pin)
call, now it’s not that simple.
To add the Pushpins, we now need to download and reference the new WPToolkit via NuGet. Go on, I’ll wait (the new one I used was published Oct 30 2012, was top result after searching for WPtoolkit). We need this for the Pushpins, as they are no longer included in the built-in Maps library (which is now located in the Microsoft.Phone.Maps
assembly, as seen above). This should also fix any other issues you had where things from the previous Toolkit were no longer available (once you update your using
statements).
Once that’s done, you can start fixing your existing Pushpin code. First, add a reference to the package you just downloaded:
1
|
|
The next thing you’ll notice is that the Location
property of the Pushpin no longer exists. It’s now called GeoCoordinate
, that’s all. That should be all you need to change there:
1 2 3 4 5 |
|
Now to add the pin, we need to:
MapOverlay
object and add the pin to the overlayMapLayer
object and add the overlay to the layerMapLayer
to the Layers
property of the mapSo let’s do that:
1 2 3 4 5 6 7 8 9 |
|
A few things to note here:
MapOverlay.PositionOrigin
is the point in your overlays content you would like centered on the GeoCoordinate
. The overlay has (0,0) as top left, and (1,1) as bottom right of your content. Since we’re using the old Pushpin image, we want (0,1) for bottom left. If (for example) you had an arrow pointing from left-to-right and you wanted the tip of the arrow to be pointing at the location, you would specify overlay.PositionOrigin = new Point(1, 0.5);
for the center of the right-hand side.MapLayer
sMapLayer
can have multiple MapOverlay
sGeoCoordinate
property twice - once for the Pushpin
itself, and once for the MapOverlay
it’s going into.If you’re adding multiple pins simultaneously, you may want to add them in separate layers or on the same layer. I’ve opted for inserting all pins on the same layer, as I’ve found this exhibits the same behaviour I was experiencing previously - pins added later appear on top - so I’ve found no reason to experiment.
For further reading, check out the Windows Phone Dev Center.
You may have noticed earlier that you no longer specify the CredentialsProvider
in the XAML. Instead we have an event handler for the Loaded
event on the map. As detailed on MSDN we now specify an ApplicationID and AuthenticationToken in code. This looks to be very simple, though I haven’t tried it yet (as you get the ApplicationID and AuthenticationToken during the app submission process).
1 2 3 4 5 |
|
One part of the clean up was localization. Localization of your resources is important, as it allows you to easily distribute your application in multiple languages without having to modify your code. Too few developers do this, and having an application display its content in the language of the device is a great way to make your users feel appreciated. I’ve done localization in Java previously, but never in .NET. I did some Googling and found a few guides, it was pretty simple:
AppResources.resx
file (for your default language, as specified in Assembly Info for the project)AppResources.xx-YY.resx
, where xx
is the region and YY
is the language. For example, AppResources.de-DE.resx
. You can see the full list here.*.csproj
file to include your newly supported languages.Pretty simple. However, the guide I was following didn’t detail how to handle the case where you had multiple projects. Oh dear. I couldn’t find anything online about supporting multiple projects - other than just having a new AppResources.resx
file per project, which I didn’t want.
As it turns out, it’s still very simple to do. Instead of adding the AppResources.resx
file to the current project, just add a new project as a Windows Phone class library and then add the AppResources.resx
file to that. Now, you can reference it from anywhere, provided you include a reference and a using
statement from the other project back into the resources project:
1 2 3 4 5 6 7 8 |
|
To access the resources from XAML, you need to add the following class to your resources library:
1 2 3 4 5 6 7 8 9 10 |
|
You will also need to add the following to the App.xaml
file (the guide I linked to above doesn’t detail all of this):
1 2 3 4 5 6 7 8 9 10 |
|
And then to reference the resources in the XAML for each page, change anything that was using hardcoded text to use a binding as follows:
1 2 3 4 |
|
You can see all of these changes in this commit.
There are a few important gotchas with this:
WhatsShakingNZ.Resources
to WhatsShakingNZ.Localization
. After doing this, everything worked perfectly.AppResources.resx
Access Modifier as public
or you won’t be able to access the resource properties:
AppResources.resx
file is considered to be. If the device your application is running on is running any language other than your default, it will search for the resource string in the corresponding language resource file. If that resource file does not exist (or, if the specific key it is looking for does not exist in that region-specific file), then it will fall back to your default. This means you can have some strings localised and some not, even within the same page.AppResources.xx-YY.resx
file, and also declare in the .csproj
file for the main project that it supports the language (not the project containing your resource files). You have to edit this file manually in a text editor, as detailed in step 4 of the guide linked above. You can check out this MSDN blog, this guide or this forum post for a bit more information on how and why.I’d previously considered the fact that I would likely need to adjust the zoom level on a per-device level, and had added a method stub with the intent of adding some logic in there. Up til now I hadn’t bothered with it, as, well, “It works on my device!” However looking at it on this Nexus 7, I figured it was time to do something about it.
A bit of Googling around showed that the problem of zooming relative to screen size was common - but the common problem/solution was to ensure the map zoomed to span a set of markers in an ItemizedOverlay
. I specifically did not want to do this - I want to show the whole country, not just the areas where the earthquakes are!
The MapController.zoomToSpan(int latSpanE6, int lonSpanE6)
method looked like it would do what I wanted, but honestly, it’s fucked. It gave me either zoom level 5 or zoom level 7, and I could never get it to give me zoom 6 (which is what I wanted, for the screen size on the emulator I was trying it on). Incrementally decreasing the size of the area I was requesting it to zoom to was no help. Admittedly the documentation gives no guarantees, and says that after the zoom “at least one of the new latitude or the new longitude will be within a factor of 2 from the corresponding parameter.” This wasn’t good enough.
I ended up doing it the hard way and experimenting with different screen resolution emulators until I found the right zooms. I figured 5, 6 and 7 would probably do it, depending on screen size. My code now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
I went with these resolutions because they represent some of the most common sizes among popular phones. Below 480px wide we have 320x480 devices such as the LG P500 (Optimus One) or the Galaxy Ace. Below 720px wide we have the common 480x800 devices such as my Nexus One, or the Galaxy S and Galaxy S II. And finally we have even larger devices with higher resolutions, such as the 720x1280 Galaxy S III (this is the most common device for all downloads of What’s Shaking, NZ? for Android) and HTC One X, and the 800x1280 Nexus 7 tablet.
There are some obvious problems with this solution. It’s not generic at all, and works only for New Zealand. The Display.getWidth()
call is actually deprecated since API Level 13, however I want to support older devices as well - currently nearly 45% of all my downloads for this app are Android 2.3 and below. Also, note that this solution doesn’t work very well for people with strange resolution phones - anything that doesn’t fall into the broad categories I’ve defined above is likely to meet with a strange/unfortunate default zoom. However if your screen size is that weird, there’s probably not a whole lot more I can do about it - the Android Maps API doesn’t allow anything more granular than these discrete levels, and these zoom levels fit New Zealand to the screen the best for the given sizes. Lastly, it won’t scale properly when even larger devices are released.
However, despite these problems, I still found it to be the simplest, and most effective way of handling zoom at a per-device level. I would love to see a more granular zoom level (in Windows Phone you supply a double for zoom level), as it would presumably fix the problems with the zoomToSpan
failures (this is a far more generic solution and would almost certainly be preferable). However, even if that does come about it won’t make much difference. With all these older devices still in use that won’t support it, it’ll take a long time to make enough of a dent in the user base that it’s worth thinking about again.
A few things to note:
Now, on to the post!
My intent was to host the website as a project page on a Github repo, using a custom domain. This repository would consist of a master
branch with the source on it, and a gh-pages
branch with the website on it. From there, I could have the www.whatsshaking.co.nz domain point at the right place and everything would all work splendidly. I spent a fair bit of time playing around with colours and layout and resizing locally (resizing the header was only possible thanks to Lee at Big Dinosaur). When it was at a this-sucks-less-than-everything-else-I’ve-tried stage, I decided it was time to put it up online.
I spent a bit of time poring over the Octopress documentation as well as this post by Rob Dodson. I figured I was armed with enough knowledge to get this to work first time.
My test setup on my local machine looked something like this:
~/Sites/wsnz
using the command on the siterake watch
What I wanted, was this:
- Octopress WSNZ site in ~/Code/wsnz-website
with source on master
branch and easy deployment to gh-pages
branch
- POW running the same as above, for easy local development
So, with beer in hand and the internet at the ready I embarked on this adventure.
First of all, I wanted my code in a different place locally. So I created the repository on the Github site (adamsp/wsnz-website) and cloned to ~/Code/wsnz-website
using the ‘Clone in Mac’ button and the Github application. Then through the OS X Finder I copied the contents of ~/Sites/wsnz
into ~/Code/wsnz-website
. Importantly, this means I no longer have the original .git folder, so that explains why the wsnz-website repository has no commit history (where as the repo for this site is a ‘proper’ fork and does have the commit history Edit 18 Sep 2012: This site ended up copy/pasted from that linked repo).
Now my code was in the right place, I went ahead and did some other modifications. I changed my _config.yml
file so that the URL property pointed at the domain, url: www.whatsshaking.co.nz
, I added a .gitignore, added a Google Analytics ID, etc. I also did echo 'www.whatsshaking.co.nz' >> source/CNAME
, as instructed in the guides, so I had a CNAME file with my site URL in it.
I now ran rake setup_github_pages
(with the git endpoint as git@github.com:adamsp/wsnz-website
) – this changed my _config.yml
and Rakefile
. This is wrong!
When I ran rake deploy
I ended up with a broken website. I figured I should be able to see it at http://adamsp.github.com/wsnz-website – but that didn’t work. This was confusing, as everything appeared to be right on the gh-pages
branch.
I was looking through HTML in the gh-pages
branch when I realised that the files weren’t at root. Checking back in my source again, it looked like everything in the config files was prefixed with wsnz-website/
!
The setup_github_pages
command changed all the URLs to be prefixed with wsnz-website/
. I had to go through and edit these out. So you should still have the following settings in these files:
1 2 3 |
|
1
|
|
1 2 3 4 |
|
But oddly enough, things still weren’t working. Turns out, I had to leave the URL property in _config.yml
as url: http://adamsp.github.com/wsnz-website
.
Once I did this and redeployed, everything worked as expected. I updated my link in ~/.pow
to point to ~/Code/wsnz-website
and I could access my site for local development again. Pushing blog posts and changes is now as simple as rake generate
then rake deploy
. Easy.
Anyway, after all of that, these are the issues I had, and their fixes/correct settings:
_config.yml
require a space after the colon or else the generate command fails..sass_cache
.url
setting in _config.yml
is the Github pages URL – eg url: http://adamsp.github.com/wsnz-website
. This is true even if using a custom domain. See this commit for what I mean._config.yml
, config.rb
and Rakefile
should not contain your sites repository name. eg destination: public
and not destination: wsnz-website/public
. The rake setup_github_pages
command may put these settings there.CNAME
file should exist under the source folder, and then when deployed it will exist in the root of your gh-pages
directory. This is required.CNAME
file present you cannot access the site at the Github address. Note that you should just point it to http://username.github.com
rather than http://username.github.com/project-page
.Again, I must emphasise that most of this is fairly new to me. I’m sure most of these things are common knowledge, but not to me - and that means probably not to someone else.
]]>