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.

04 August 2016

Stylish Dynamic Layouts

One of the things we are taught in Android is that we should gracefully handle different layouts based on screen sizes. With more and more things being not just screen size-specific but also OS version-specific, this is one thing I think a lot more devs need to pay attention to. Today was my turn to do just that.

I wanted to create a dialog with an image that resizes itself while maintaining aspect ratio but being constrained by the container. Okay.

So I went about creating my ImageView:
<ImageView
android:id="@+id/permission_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="200dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@{ContextCompat.getDrawable(context, permission.descriptionDrawable)}"/>

Loaded up the layout and it looks satisfactory:

But then, I tried rotating and what the hell!

The image does not fit, you cannot see "NOT NOW". It doesn't work. Then I tried it on a tablet:

OMG how could it possibly get worse. There is so much space and it's not filling it up properly! I am pretty sure the root layout has layout_height="wrap_content". Weird.

After some furious Googling, I found out that I have to set the LayoutParams on the dialog after it has been shown. Why do I have to do that? I said wrap it and you refuse and now you want me to do it myself? Why?

I gave in and did the thing that StackOverflow tells me to. But it didn't work. It still looked the same. I tried moving stuff around; calling setLayout instead of setAttributes. It still looked the same. :sad_panda:

And so the time has come to look for another approach. A couple of things need to happen:
a. We shouldn't make the user scroll just to click the buttons on a landscape phone
b. Make the image take up more space on tablets

To make (a) happen, we either: create a new layout for this orientation or just set the image to be invisible. The second option is more appealing for me. There really is not much difference in the two orientations aside from the image after all. We can set the visibility of the image at runtime, but then maybe we can afford to show the image if there is enough space for it.

This means that to make (b) happen, maybe we need to tweak our layout a little. And this is where the beauty of Android's alternative resources come in. To be as less repetitive as possible, I chose to harness styles too, since I only need to override one attribute -- the visibility.

First I created a style in the default /res/values folder that I would apply to the image:
<style name="DescriptionImage">
   <item name="android:visibility">gone</item>
</style>

Now it's time to make the variations. We want to hide the image if we are in landscape, so into /res/values-land this style goes:
<style name="DescriptionImage">
   <item name="android:visibility">gone</item>
</style>

But we want to show it if there is enough space, so into /res/values-h500dp this style goes:
<style name="DescriptionImage">
   <item name="android:visibility">visible</item>
</style>

And now it's just a matter of applying this style to our image (and I found out that setting maxHeight works a bit better):
<ImageView
android:id="@+id/permission_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
style="@style/DescriptionImage"
android:src="@{ContextCompat.getDrawable(context, permission.descriptionDrawable)}"/>

I applied this technique to another dialog and here's the end result on a phone:


Yay!

19 July 2016

Using resource IDs in data binding layouts

I have been playing with data binding more and more over the last couple of weeks. This week, it's all about creating a dialog with stuff dictated by a value from an enum.

Each value in this enum has the following properties:
@IdRes int textView
@StringRes int quote
@StringRes int name
@ColorRes int colour

I have added a new button in my Sandbox app to choose one value from the enum and display the properties in an alert dialog via data binding. Ambitious, I know!

The relevant Java part is mainly just creating and showing the dialog:
@OnClick(R.id.data_binding_alert)
public void onSendDataBoundAlert() {
   AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MaterialAlertDialog);
   builder.setPositiveButton(android.R.string.ok, null)
           .setNegativeButton("Not now", null);
   View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_data_binding_demo, null);
   DialogDataBindingDemoBinding binding = DataBindingUtil.bind(view);
   binding.setCharacter(getRandomCharacter());
   builder.setView(view);

   builder.create().show();
}

The tricky part is telling data binding how to use our resource IDs. Say I want the colour to be set as a background for a TextView, and the name to be displayed:
<TextView
     android:id="@+id/name"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="64dp"
     android:textSize="24sp"
     android:textStyle="bold"
     android:gravity="center_vertical"
     android:paddingLeft="16dp"
     android:background="@{character.colour}"
     android:text="@{character.name}"/>

Running the sample though, you'll see that the colour is not set and a dark grey-ish lilac-ish colour is used instead:


Unacceptable indeed!

So how do we make the Earl of Lemongrab happy? There are two ways of solving this problem.

A. Use a BindingAdapter

A BindingAdapter lets you manipulate and do a bit more involved logic on your data before applying it to the View. To use a BindingAdapter, first create a static method in your code that is bound to either a standard Android attribute or a custom one.

I create a custom attribute here called characterBackground:
@BindingAdapter({"characterBackground"})
public static void characterBackground(TextView textView, AdventureTimeCharacters character) {
     textView.setBackgroundColor(ContextCompat.getColor(textView.getContext(), character.getColour()));
}

You can then use this BindingAdapter in the TextView:
app:characterBackground="@{character}"
Do not forget to add the app namespace! Android Studio can add this for you. Just type in appNs and it will autocomplete.

This solution works, but is a bit too involved. And you said data binding is easy!!!

Well it kinda is, because, it turns out that we can:
B. Set the value in XML

But it didn't work! Well it turns out the problem is because we are passing a resource ID into an attribute that expects a resolved color value. So the only thing we need to do is to move all that code in our BindingAdapter into XML.

First add the import:
<data>
    . . .
    <import type="android.support.v4.content.ContextCompat" />
</data>

And then use it:
<TextView
     android:id="@+id/name"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="64dp"
     android:textSize="24sp"
     android:textStyle="bold"
     android:gravity="center_vertical"
     android:paddingLeft="16dp"
     android:background="@{ContextCompat.getColor(context, character.colour)}"
     android:text="@{character.name}"/> 

Very convenient! And it works! Note that you do not have to import anything to use context, there is some data binding magic going on that automatically injects it for you if you call it. Think of it as The Beckoning of the Context.


The Earl is now happy.

PS: If you need to set a drawable from a resource ID as an ImageView src, I found that you would also need to do this trick, aka:
android:src="@{ContextCompat.getDrawable(context, character.icon)}"