The need of the hour was simple, develop an Android SDK for external clients to integrate with, allowing their users to do…. a thing. (Not sure if I’m at liberty to discuss what that thing is. I intend to remain employed)

Without giving too much away, the expectation was that the client app would call our SDK, the SDK would open a URL where the user would log in, after which he could do … a thing. Once he was done with the thing, he would be returned back to the client app. Easy peasy right?

The main area of ambiguity was how we would open our url and process the response from said url. We basically had two options, we either do it in a browser or we use a web view.

The issue with the browser is that there is a context switch. Every time the user would open a url in the phones default browser, he would basically open another app. This was a concern because if the user were to switch apps while the url is open, when returning back to the app he would see the browser and the app running separately and this could lead to a confusion as to which one exactly he is supposed to return to. In addition to this, there was no way for the client app to be able to customize the browser UX, so every time the user was in the browser the aesthetics would be very different that that of the app leading to a jarring UX.

Our other option was a Web View. A Web View runs within the app, so there wont be multiple apps running unlike the browser, plus we can customize the UX in any way we want!

But there were two downsides to web views as well

1) If the user had say 5 apps that had integrated with out SDK, he would have to login separately in EACH app as a web view maintains cookie information only within THAT app and is not shared across.

2) This…..yeah.

Due to the well knows vulnerabilities present in a web view, browsers seemed like the only way to go. The UX would suffer miserably, but at least the user data would be secure. But whats the point of building the most secure app in the world if the UX is so bad that no one would want to use it. This would be a huge turn off for client that wanted to integrate with us.

While we were contemplating a super complicated implementation to TRY and address some of the vulnerabilities of web views, someone pointed out the existence of Chrome Custom Tabs. What to me seems like a 80–20 mix of browser and web view.

And that is the best way to describe a chrome custom tab, at the end of the day what you are running is the chrome browser, but it is disguised to run within the context of the app (so the user would not see it as a separate app unlike a browser), also its UI is somewhat customizable (not to the extent of a web view but its something). Best of all, it has all the latest security patches that chrome has right out of the box! Any time there is new patch, if the user updates his chrome browser, the custom tab gets that update too! GREAT! What could possible go wrong?

Where do I begin…

Fallback is not an option, but a necessity

As I mentioned earlier, a chrome custom tab is the chrome browser in disguise, what that basically translates to… if you don’t have the chrome browser installed, you don’t get to use custom tabs. Fortunately, despite Android OS having a million flavors almost all of them come preinstalled with chrome as a service. So the number of android users without chrome was a very small number. And for those users we would use browser as a fallback. So far so good.

A double edged sword

Chrome custom tab is dependent on the chrome browser that is currently installed on the device. That way if there are any improvements in chrome, they are automatically present in custom tabs as well. This proves to be a double edged sword unfortunately. With all the security patches, all bugs that are present a version of chrome come to custom tabs too.

And there is not a damn thing we can do about it! We may depend on the the latest library of chrome custom tabs in the app, but if the user is using an older version of chrome on his device, all the bugs associated with that version flow into custom tabs too!

You might think that chrome doesn’t really have a lot of bugs, but we ran into two particularly annoying ones with older chrome versions (they were addressed in the newer versions).

In low memory conditions if you navigate away and then back to custom tabs, they reload whatever page you were on. This caused some of the pages to break. We couldn’t even go and try to address the issue on that page, as the page that was breaking was not even owned by us.

Another annoying one was when intermittently, for no good reason at all, when we would redirect from the custom tab back to our SDK the redirect would fail with INVALID_URI_SCHEME. This happened fairly rarely, and there was no deterministic way to reproduce this. The only fix was to update your chrome.

Non backward compatible changes.

This one is on you Google. While integrating with our SDK we expect the client to manually add a dependency on chrome custom tabs as well. The trouble is our SDK was built with custom tabs 24.x.x. If the merchant integrated with 25.x.x or higher, the app would crash with a “noSuchMethod” exception in the lauchUrl method no less. Some other people faced this issue as well. Only way we fixed it was to ask the client to use custom tab 25.x.x or above and rebuild our SDK with the new custom tab library.

Custom Tabs are like the party guests who stay too long

This one was easily the most frustrating one, ideally once our SDK was done doing whatever it was supposed to do, we “finish” the activities and remove them from the stack history. This way after using the SDK, if the user presses back, he doesn’t end up once again in our SDK. Unfortunately no one told chrome custom tabs this.

Let me try and explain this one… (TL;DR at the end)

Imagine the client activity goes to another activity which will invoke our SDK(lets call it invoking activity) , which in turn runs an activity of its own. So the activity stack at this point looks like this

Client Activity → Invoking activity → SDK Activity

Now our SDK Activity launch a url in custom tabs…but WAIT! Custom tab is designed to be an activity of it own, so once we launch the url our activity stack looks like this:

Client Activity → Invoking activity → SDK Activity → Custom Tab

Now we return back from Custom Tab to our SDK Activity

Client Activity → Invoking activity → Custom Tab → SDK Activity

We process the response, and finish the SDK activity

Client Activity → Invoking activity → Custom Tab

At this point once we finish the invoking activity we would want to return to the Invoking activity, but the custom tab is still in the damn activity stack, so it reopens whatever URL it was last on giving the impression that chrome custom tab DID NOT CLOSE AT ALL. In fact, a simple google search reveals how common this problem is.

It took me a while to figure out WHAT was exactly happening, and the only way to remove it safely is to set a precise combination of flags to remove it from the activity stack. At first I thought I was the one unable to come up with an elegant solution, but Googles own app auth uses the same convoluted solution I was using! I’m going to be honest, one one hand it felt kind of nice to be validated by the people who own android, and yet there was disappointment that this mess was the only way to achieve this behaviour.

(TL;DR Chrome custom tabs don’t automatically leave the activity stack after we redirect back to app, which in our case was undesirable, the only way to achieve that behavior is fairly tedious and odd)

Don’t get me wrong, my intention is not to bash on custom tabs or Google, both of which I think do a great job. My only complaint is the weird things I had to do to get custom tabs working the way I wanted it to.

Integration wise chrome custom tabs leave a lot to be desired especially in our use case. For blindly opening URIs where we don’t expect a response back from those pages, I suppose its much simpler. Hopefully, if someone else comes across these issues, he/she will come across this post and not have to go through the entire song and dance.