31 March 2017

Sharing is Caring

Or so they say.

Luckily for us, Android makes it super easy to share things between apps. It even provides a pretty-looking Intent chooser*!


We can get this nifty bottom sheet with only a few lines of code:
// Construct the intent we want to send
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
shareIntent.setType("text/plain");

// Ask Android to create the chooser for us
final Intent chooser = Intent.createChooser(shareIntent, getString(R.string.share_text));
startActivity(chooser);

That's it, your work is done.

But what if we want the users to choose our own app? For example, we have a Share via Domain activity in our app that we prefer our users to use when sharing properties. It generates an email you can send to your friends and includes some information about the property. However, we still want to give users the option to share via other channels.

Luckily for us (we Android devs are really lucky, in case you haven't noticed), there is an API that allows us to do just that.

We can add extras (via EXTRA_INITIAL_INTENTS) to the chooser Intent that will tell the OS to prioritise the Intents we want. In my sandbox app, I made a simple activity that will display the text we send in shareIntent above.

Intent customSharer = new Intent(this, CustomShareActivity.class);
Intent[] initialIntents = new Intent[] {customSharer};
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents);

This will surface our priority intents at the very top of the list. It will appear with app icon, with the value we have in label from the Manifest.

<activity android:name=".bottomsheet.CustomShareActivity"
          android:label="Choose Me!"/>
We end up with something like this:


Pretty cool, huh?


*I know, the developer docs screenshots are a tad outdated. ¯\_(ツ)_/¯

28 February 2017

A note on giving back

Recently, there has been a spate of tweets about developers admitting their weaknesses. A bunch of people I know even made into the Moment created by @ThePracticalDev. And then there's this tweet:
Go ahead and read that whole thread. It's important.

Over the last months I have talked to a lot of women in tech, and one thing I realised is that I am not alone.

I'm not the only one worried about making a mistake.
I'm not the only one who preface everything she says with "You all know this already, but...".
I'm not the only one who double-, triple-, quadruple-checks every. single. thing before saying anything.
I'm not the only one who constantly worries about being seen as stupid when she asks a question.
I'm not the only one afraid to write something for fear of being called a know-it-all.

I am not alone.

There are people who will feed on your fears. I made a post about a new discovery three days ago. And true enough, I got replies like this:
Uhmmm... Okay... I'm sorry for making you see something you already know. I'll be more careful next time.

But then a few hours ago, someone left this comment in one of my posts:

And this, my friends, is why despite all our fears, we should never stop sharing.

16 February 2017

:facepalm:

I just spent an hour debugging an issue that should have been a non-issue at all.

A thing about working on a huge product like Domain with such a small team is that there are some bits that tend to get left behind. Lately I have been playing with a bunch of libraries to find out how the modern ones do image pan and zoom. This is important for us because those crazy expensive houses have pretty high-res photos.

Look at this!
Click to embiggen
One of the libraries I was looking at was Subsampling Scale Image View. I have heard good things about it, but was too lazy to figure it out. Today will be the day, I said. Today I will stop being lazy and figure it out.

After about an hour of furious Googling, I ended up finding a bunch of code that I managed to cobble together that theoretically should work. Maybe at this point I should mention that images and graphics are not my strong suit. There's too much stuff going on and my I can't wrap my head around it.

Anyway, I need to get the Bitmap from a file downloaded by Glide. Someone on the internet said this should work:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(downloadedFile.getAbsolutePath(), opts);


I fired up my app and nothing is loading. All the pages in the whole of my image gallery are empty. Maybe I'm using the library wrong? Maybe this is not how I should apply BitmapRegionDecoder? Maybe it's not actually downloading the file?

I ended putting breakpoints all over the place:

And it's still not working. It's not crashing.. so at least I got that going for me.

I was getting frustrated, so I was like, "What does BitmapFactory.decodeFile and the options do anyway??" I looked for the JavaDoc.

inJustDecodeBounds

boolean inJustDecodeBounds
If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.

Ah. FML.

What have we learned today? Read the docs, kids.


20 January 2017

Troubleshooting autoVerify

So you implement app links and you are 300% sure you have implemented everything correctly. The important thing to remember here is that verification is all or nothing. From the docs:
For app link verification to succeed, the system must be able to verify your app with all of the websites that you specify in your app’s intent filters, and that meet the criteria for app links. 
Google has outlined several steps on how to test your implementation and they have provided several samples as well. As with life, however, things can go awry no matter how hard you try.

If automatic link handling does not work, chances are one of the hosts declared in your Manifest failed verification. If you have a bunch (subdomains are considered unique and separate), it can be hard to figure out which one is failing. Thankfully, there is a way for us to see which is causing the failure.

Fire up your terminal, start logcat, install your APK (adb install <path-to-apk>) and keep an eye out for the verifier logs. Here is a sample output:
I SingleHostAsyncVerifier: Verification result: checking for a statement with source a <
I SingleHostAsyncVerifier:   a: "https://domain.com.au"
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier: , relation delegate_permission/common.handle_all_urls, and target b <
I SingleHostAsyncVerifier:   a: "com.fairfax.domain"
I SingleHostAsyncVerifier:   b <
I SingleHostAsyncVerifier:     a: "AA:B4:3F:0F:A7:49:F8:90:F3:D6:64:30:FB:5E:69:54:7B:BA:EB:85:7D:9D:04:57:83:5F:FD:58:E7:B9:70:6A"
I SingleHostAsyncVerifier:   >
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier:  --> true.
I SingleHostAsyncVerifier: Verification result: checking for a statement with source a <
I SingleHostAsyncVerifier:   a: "https://www.domain.com.au"
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier: , relation delegate_permission/common.handle_all_urls, and target b <
I SingleHostAsyncVerifier:   a: "com.fairfax.domain"
I SingleHostAsyncVerifier:   b <
I SingleHostAsyncVerifier:     a: "AA:B4:3F:0F:A7:49:F8:90:F3:D6:64:30:FB:5E:69:54:7B:BA:EB:85:7D:9D:04:57:83:5F:FD:58:E7:B9:70:6A"
I SingleHostAsyncVerifier:   >
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier:  --> true.
I SingleHostAsyncVerifier: Verification result: checking for a statement with source a <
I SingleHostAsyncVerifier:   a: "https://m.domain.com.au"
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier: , relation delegate_permission/common.handle_all_urls, and target b <
I SingleHostAsyncVerifier:   a: "com.fairfax.domain"
I SingleHostAsyncVerifier:   b <
I SingleHostAsyncVerifier:     a: "AA:B4:3F:0F:A7:49:F8:90:F3:D6:64:30:FB:5E:69:54:7B:BA:EB:85:7D:9D:04:57:83:5F:FD:58:E7:B9:70:6A"
I SingleHostAsyncVerifier:   >
I SingleHostAsyncVerifier: >
I SingleHostAsyncVerifier:  --> false.
I IntentFilterIntentSvc: Verification 6 complete. Success:false. Failed hosts:m.domain.com.au.

In this case one host (https://m.domain.com.au) fails, which means automatic link handling will not work at all. If this happens, hit up your friendly web developers and go through the troubleshooting steps outlined in the developer guide.

Once successful, the last tine in the verification process should say Success:true:
I IntentFilterIntentSvc: Verification 7 complete. Success:true. Failed hosts:.
H/T to Wojtek Kaliciński for the tip!

25 November 2016

The Quirks of Supporting SDK 25

The last developer preview of Android 7.1 has started shipping, which means APIs are (based on past experience) more or less stable. There is a very good write up on developer.android.com on how to get started with supporting these new features. I set about trying it out, and here's what happened.

Update SDK version

Let's start with the (supposedly) easy bit -- updating target SDK to 25. It should be as easy as updating your build.gradle file, but just like when the API 24 sources became available, I found that I have to edit the SDK path before Studio starts to recognise the new APIs.

Go to Preferences > Appearance and Behavior > System Settings > Android SDK, click Edit beside Android SDK Location and just keep on clicking Next until you exit the wizard.


Circular Launcher Icons

The next (supposedly) easier bit is having a circular launcher icon. First stop is to generate the new icon. Android Studio has a built-in icon generator, in fact the documentation encourages you to use just that. So launch Asset Studio (CMD+SHIT+A then Asset Studio, or, right click in Project Pane > New > Image Asset).

Choose Image as the Asset Type, then click the three dots to choose your existing icon. Now I know that pointing it to the current icon in mipmap/ or drawable/ should work but it didn' t for me. I had to copy the asset to somewhere else (in my case Desktop) and point the tool to there.

Next choose Circle as the shape and voila, you have your asset. Not.


So it looks like the tool does not trim the icon to be a circle. It tries to, you can see the shape. I filed a bug for it, but I don't think it has been triaged yet.

In the meantime, you can use Roman Nurik's excellent online Asset Studio to generate your icons.

App Shortcuts

One of the biggest features on this version is app shortcuts (please stop calling it force touch, that's not ours).

The Developer site is quite verbose on how to implement app shortcuts. For an overview on what app shortcuts are and the different types (static and dynamic), head on over to the developer site right now and read the intro.

But in a nutshell, and I'm quoting here:
Android 7.1 allows you to define shortcuts to specific actions in your app. These shortcuts can be displayed in a supported launcher, such as the one provided with Nexus and Pixel devices. Shortcuts let your users quickly start common or recommended tasks within your app.

Static shortcuts

Static shortcuts are simple enough. It is so simple in fact, that I doubted myself.
Basically copy-paste stuff from the dev guide, tweak it to use your own app's Intents and it just works. To try it out, I re-used existing PNG icons from our app. But of course, we want to do it right, right?

So first up is to make the icons follow the design guidelines.
• icons should be 24dp x 24 dp
• should be centred in a circle that's 44dp wide
• circle should be in material grey #F5F5F5
• there should be a 2dp padding all around the circle

I needed to convert the icons to vectors, and I found this SVG to vector converter to work best for my purposes. And by best I mean it doesn't mangle the circle, it doesn't lose any of the holes, and is as close as possible to the SVG input.

As always, I tried to define strings in strings.xml. This works well enough for the shortcut labels, but it strangely does not for the shortcutId attribute. And by "does not work" I mean your shortcuts will not appear at all.


Dynamic Shortcuts

App shortcuts can also be dynamic. For my use case, I wanted to expose a few more shortcuts for users who are logged in.

Again, the Developer site has a very good guide on how to get started with including these shortcuts in your app. I highly recommend reading the ShortcutManager Javadoc, as there are more details there than is available in the guide.

A bunch of people have already written about how to implement shortcuts in your app, but I think what most don't mention is that if you need to build up a backstack for the Activity triggered by your shortcut, the docs recommend to use TaskStackBuilder.

This applies to my use case, which is opening the user's Shortlist. In normal circumstances, users get to their shortlist from our main (search) screen. This means that once they are on their shortlist, pressing the back button will take them back to the main screen. We want to replicate this behaviour when they go through the shortcut, and TaskStackBuilder helps us do just that.

Sure you can just manually write out your own Intents[], but remember to set the correct flags for the first Activity in your stack. It's something that's easy to forget, but if we create our Intents via TaskStackBuilder, it will automatically add the correct flags for us.


Reporting Usage

The docs exhort you to report using shortcuts regardless of how the users get there. The API name may be a bit misleading -- reportShortcutUsed -- but think of it as just adding analytics tracking for the screen. So please report usage of both static and dynamic shortcuts!


What's next?

There is so much more stuff to explore with app shortcuts. What happens when the users restore your app from a backup? What if they pinned a shortcut you removed in a new version? How do you re-arrange shortcuts?

I have yet to explore these, so if you have, what are the other quirkiness you encountered?

In the meantime, watch out for our new updates!


27 October 2016

A Little UX Love Goes A Long Way

Yesterday, my bank pushed a notification asking if I'm going to travel. Yes! I am! I filled up the form they asked me to fill up, and tapped Submit.

And then I got this:

I try again, same error. I try again, the app crashes.

I know some of the devs for this app, so I messaged one of them. As any proper developer does, he asks me "What version are you on?"

I went to the app's settings, and I can't find it there. Hmmm. Opened their navigation drawer. Not there. Where is it?! Apparently, it's nowhere in the app. The dev told me to go to my device's settings, look for the app, and look for the version number.

I thought that was weird. I asked the dev why they don't have it, and he said "I can find you in our crash logs using your user ID. I can find your device, version number, user name. Users do not need the version number."

I have always put in the version number somewhere in all the apps I have worked on ever.  Am I doing it wrong? And so I shouted on Twitter:
A bunch of people replied to me, all with some variation of "Yes". I needed the internet on my side today, thank you for heeding the call, Twitter friends.

I really don't understand why you would not want to put the version number in your app.

It's just one TextView. Use the tiniest, thinnest font you have. You don't even need to think about the code. It's all there, in BuildConfig.

If users care enough about your app to report issues, then that means they are really comfortable using it. Why would you kick them out of their comfort zone just to ask them for this piece of information that should be plastered on the wall? Not all users are as tech savvy as you, 23-year-old developer.

I can just imagine someone calling support:
Customer: The application keeps on stopping.
Support: What version of the app are you on?
Customer: Where do I find that?
Support: Open your device's settings....
Customer: What? How?
Support: What phone are you using?
Customer: I don't know, my children gave this to me.

Remember, going to Settings > Applications may be different for each manufacturer. (I'm looking at you, Samsung). And it may not be as easy as telling someone "Go to device settings".

Having the version number is a good excuse to put in an easter egg. Why pass up this chance?

I guess what I want to make out of this is that something as inconsequential as this for you as a developer may be important to end users, or to other people who help you make your app better. It is bad enough that users encounter bugs so severe they feel like it's worth reporting. And surely we do not want to lose those users because simply by the act of reporting issues, they show us that they care about our product. The least we can do as developers is to make it as easy as possible for them to help us.

06 October 2016

Tools of the Trade: Unabridged

I gave an unabridged version of my last Android Meetup talk at this year's YOW Conference. It has been an honour being part of this awesome conference!

What wonderful timing to announce being a part of the Google Developer Experts program too! Thanks to everyone who stayed behind to watch me babble on and on for an hour!


20 September 2016

Tools of the Trade

Here are the slides to my talk at the Android Meetup tonight.


Thanks for all the great questions!

As promised, here are additional links:

Leak Canary (Square) - Really useful leak detection library
Multidex and Performance (Tim Mellor) - Great overview of everything multidex


17 September 2016

String formatting and Lint

One piece of advice that we keep hearing over and over is to extract strings into resources. There really is no reason for you to hard code strings in code.

I wrote before about easily moving strings between Java and XML, and today I'd like to focus on string formatting. The Android dev guide gives a good overview of the support Android has for passing arguments into a String.format(String, Object...).

Worried about using incorrect syntax? I was! I can never remember which of the % or the $ comes first, or if I'm passing arguments in the correct order. To be clear, the syntax is
%[arg number]$[arg type]


Thankfully, Android Studio has come a long way with pointing us in the right direction when working with strings.

First off, a very useful warning when setting manually concatenated text into a TextView:
 And by "placeholders" they mean the format arguments.

You can mix and match multiple arguments and argument types in one string too!

And in case you removed an argument in the XML file but forgot to edit code, another helpful error for you!

If you change the argument type, Studio will tell you about it too.

If you forget the syntax, fear not for Studio will tell you. (May not be too obvious here but I used the incorrect "$1$s" format.)

Android Studio is really so helpful now, we are running out of excuses to be lazy coders. 😱

06 September 2016

Pidcat <3 Android M

If you use Pidcat, there might be issues running the tool if your device is on M+. The issue has been fixed on master but hasn't been released yet.

The readme says to fix the issue, get the latest off master:
12:46 $ brew unlink pidcat
Unlinking /usr/local/Cellar/pidcat/HEAD... 1 symlinks removed
✔ ~/Android 
12:48 $ brew install --HEAD pidcat
Warning: pidcat-HEAD already installed, it's just not linked
✔ ~/Android 

That didn't quite work, so let's link it:
12:48 $ brew link pidcat
Linking /usr/local/Cellar/pidcat/HEAD... 1 symlinks created
✔ ~/Android 

I tried re-running pidcat, but it's still not displaying anything. Which probably means we really don't have the latest code off the repo. The --force switch has been deprecated, so to force an update we would have to call reinstall instead:

12:49 $ brew reinstall --HEAD pidcat
==> Reinstalling pidcat
==> Cloning https://github.com/JakeWharton/pidcat.git
Cloning into '/Library/Caches/Homebrew/pidcat--git'...
remote: Counting objects: 10, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 10 (delta 0), reused 7 (delta 0), pack-reused 0
Unpacking objects: 100% (10/10), done.
Checking connectivity... done.
==> Checking out branch master
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
==> Summary
🍺  /usr/local/Cellar/pidcat/HEAD: 5 files, 19.5K, built in 4 seconds
✔ ~/Android

Or, if do not want to do all that, you can also do:
$ adb logcat -v brief | pidcat my.package.name