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


23 August 2016

A big step

Today was the day.

Today was the day a thing I worked on was open sourced. It's done. It's out there. My code. For everyone to see. It is like baring my soul to the world. It is baring my soul to the world.

You can't possibly imagine how I feel. I am frightened. I am elated.

Will people find it useful? Will they use it? Will they look at my code and laugh at how stupid I am? Will they finally know for sure that I am a ruse? Will they whinge about how I am Not Doing Things Right?

And you know what? I don't care. I've done my part and did the best I could.

(I lied. I do care. But I promise I'll try to get over it as soon as I can.)

Be afraid. But still do it.

Massive thanks to the Domain Android team for the support, code reviews, suggestions, feedback, and putting up with my baking frenzies; and to Mike for the code contributions, and for helping me through the very arduous publishing process.

14 August 2016

Winging It: How I Got to be an Android Dev And What I Learned From It

They say all relationships go through a rough patch at two, seven, then ten years. I don't remember who said it, but someone told me that many years ago.

Next week will be my moving-to-Sydney second anniversary, so I figured this is a good time to write this post.


During I/O last May, I met one of the coolest ladies ever, Yasmine. She asked me how I got into Android development, and when I was done telling her she said I should blog about it. So here it is, Yasmine. Better late than never. ;)


In the beginning...


If there's one thing you should know about me, it's that I find it very hard to make decisions. Who's your best friend? What's your favourite food? What should you name your stuffed panda? I don't know the answer to these things. So imagine 16-year-old me, about to graduate high school, and I had Zero Idea what I wanted to major in. The first university I applied to? I wrote down what major I was applying for right in front of the registrar, literally before handing her my application form (Business Economics).

I ended up going to another school, majoring in Electronics and Communications Engineering. I had one computer programming subject in freshman year. And I hated it. I hated it with a passion. I couldn't figure how anything works, and I swore I would never do that again.

My first job after uni was with Intel as a product engineer. I worked there for two years. Lived in the middle of nowhere, worked long hours. But I thought that's par for the course; part of being an adult is working hard, right? And then the semiconductor industry in the Philippines started flipping out. A lot of other factories closed down, some of the products we used to look after were being transferred to other sites. I decided I'd rather look for another job now, than being retrenched and not knowing how long I will be jobless for.


What Now?


I wanted a job back in the city, and I kinda don't want to stay in a sinking industry. But then again, there is nothing else I know how to do. Yeah, I am a licensed engineer, so technically I could work for a telco, or a TV station even! But at that time, if you want to for a telco, you'd have a better chance of getting hired if you interned with them right out of uni. And I didn't, so that's out. There were a lot of job postings for software developers though. But I hated programming! I don't know how to do it!

And this is when my first lucky break came. I am so fortunate that I met a manager who trusted in me. I was upfront with her, I don't know shit. I would have to learn on the job, so it would be a slow start. Needless to say, I learned a lot in that job. I worked on some pretty cool stuff (we made apps installed in SIM cards), and met a lot of really nice people. But more importantly, it kickstarted my foray into software development.

I eventually worked on more enterprise-y stuff (boring). Until the time we ran out of projects. I mean I'm all for coming in puttering around the office doing nothing and getting paid for it. But after two days it turns out it kinda sucks. It was 2009 and I keep on hearing about this new OS from Google called Android and that the SDK is Out Now! and that You Should Try It Out. So I installed all the things and started Android-ing.

Things Get Interesting


So now that I have built a shiny, new Hello World app that runs on an emulator, I took that as a sign that I have the creds to apply for an Android development job. I joined a start up, and again, I was upfront about it -- I don't know how to do this, I have just been playing around, but if you want to pay me to play around, then we can be friends. And so I was met with another lucky break.

It was an encouraging time to be a dev. The Android Dev StackOverflow community was much smaller, we are all learning at the same time, and honestly, I think everyone was kinder and more forgiving+.

I eventually worked for a company whose mobile team is distributed across offices in Manila, Sydney, and New York. I was the first Android developer in the Manila office, but by then I was so used to it that I didn't mind.

It was there that I met the guy who would eventually refer me to Domain, and for that I am forever grateful to him. Domain has done so much for me, both personally and professionally. I work with a really talented team, and I have never seen a company love a product so much. Domain made my dream of attending IO a reality, and through my work with them I got to work on a lot of pretty sweet features that I never dreamed of++. Another lucky break, and I mean to make the most out of it.

And?

I guess what I mean to say is that I have just been winging it all these years. But at least I'm honest about it, right? It there's anything I have learned, it is that there is nothing wrong with saying "I don't know". There are times when we need to pretend to know things, but there are a lot more times when we need to accept that we do not know things.

Do not be afraid to try something new, no matter how scared it makes you feel. Easier said than done, I know. But sometimes it really helps to take a deep breath, close your eyes, and just jump+++.  Lundagin mo, baby!



+ I was looking at my old StackOverflow questions, and I seriously think that if I asked them today, I would have a lot of "What are you, stupid?" comments. Or maybe I'm just old and cynical. I don't know. The point is, we have all been there, so be kind to another, okay?
++ This merits a post all on its own
+++  I distinctly remember that's how I applied to my first Android job. I wrote my cover letter, proof read it, hovered my mouse over the Send button, took a deep breath, then clicked it before I could change my mind.