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!