August 4th, 2015
About a month ago I was tagged by Ron Amadeo who spotted an off-by-a-pixel misalignment in some of the content rows in the Play Store app. This is the story of that extra pixel – as illustrated above with zoomed in portion in the inset showing that the last card in the row is by one pixel taller than the other two cards.
We start with a Nexus 6 device (which showed this problem). The screen is 1440px wide and we have margins of 28px on each side. This leaves us with 1384px horizontal space for the three cards. This is where things get interesting:
Dividing 1384 by 3 gives us 461px for each card with one pixel remaining. Where should that pixel go?
In the previous implementation of the layout the custom onMeasure pass iterated over all child views in the row keeping track of the horizontal space already “given” to the previous children. Starting with the first child, it gave it 1384/3=461px, leaving us with 923 pixels. The second child got 923/2=461px, leaving us with 462 pixels. And those pixels are given to the last child. All is nice and good since we’ve used all the horizontal space available to the card content.
However, we’re operating in a two-dimensional space and each card type is responsible for determining its height based on how much horizontal space is available to it. As we operate in a continuum of screen sizes and screen densities, the grid spec starts from the edges of the screen and proceeds inwards. That means that instead of predefining hard-coded sizes for cards, it instead defines the margins and the maximum width of the content area and that, in turn, defines the width of individual cards within that content area – as illustrated above.
Once the card width is determined, each card proceeds to determine how much height it needs. Cards in this cluster lay out their content as a vertical “stack”. It starts with the cover image and then proceeds downwards to item title, subtitle, price and other textual elements that can be displayed in the card. The height of the cover image is determined based on the available width and the aspect ratio of the image itself:
And this is how we’ve ended up with the last card being by one pixel taller than the other two – that extra horizontal pixel bubbled through the measure pass of the card itself, respecting the aspect ratio of the cover art and maintaining the edge-to-edge layout of the cover image.
After considering possible options, the layout logic in a card row has been revised to use constant card width, in this case 1384/3=461px:
This means that on some devices the right margin is going to be by one (or two on larger screens) pixel wider than the left one. What are other alternatives?
We can keep the current logic that makes the trailing card(s) by one pixel wider than the leading ones. In our case, the trailing card is 462px wide as compared to 461px wide. Then tweak the measure logic within individual cards to account for this off-by-a-pixel difference to enforce consistent height of all cards in the row. What are we going to do? If all the cards are 462px tall, then the first two need to account for an extra vertical pixel. It can go above the image, between the image and the texts or below the last line of texts. In all the cases that extra pixel will create keyline inconsistency within the row. And if the last card is 461px tall, we need to take that extra vertical pixel out of something. We can crop off the image or reduce the vertical space above, between or below the texts. In the first case we’re removing visual information from the cover image – and we don’t want to do that. In the rest of the cases we’re back to breaking the keylines.
Alternatively, we can push that extra pixel in between the cards. That would break the horizontal spacing rhythm of the entire grid instead of the vertical one.
To summarize, there is no magic solution here. In an ecosystem with a continuum of screen sizes and screen densities you cannot create pixel-perfect designs. You can always end up with an extra pixel or two to account for. There are multiple options to shuffle them around, each with its own consequences on what it breaks in the grid. In the absence of a solution that eliminates the visual disruption the next best thing is the solution that minimizes it. In this particular case the visual “barrier” (card content) between the left and the right side margin would be such a minimizer.
June 17th, 2015
A long long time ago I wrote about companies being in control of their own long-term destiny. HP? Seriously? Silly me. And where’s Amazon? Anyways…
Not so long ago it used to be that a platform provider would give you a nice set of core widgets, along with access to the network stack, the local file system and a few lower-level graphics APIs. Good old times of shrink-wrapped software and platform updates that happened once every five years. In a good decade, that is.
Then the Internet happened, and after the dark old times of IE 6 a couple of big companies realized that not only they needed to have a fast and standards compliant browser available on their platforms, but that it’s such a basic block that it can’t be left to other parties. And so browsers developed by the platform providers became a table stake.
Somewhere in between, GPUs became more affordable to be included in the standard consumer-grade computers. Hello Moore’s law. They also happened to become quite powerful. Hello Moore’s law again. And OpenGL happened along the way, paving the road to gradually hardware accelerating all the things that find their way to the pixels. Along the way, local and streaming video became the expected part of a platform offering. Table stakes, if you will.
As a parallel track, platform providers started offering (built internally from scratch or acquired in a mad dash so as not to appear to fall behind) email services, ad platforms, office suite of apps (documents / presentations / spreadsheets) and search engines. Table stakes 2.0.
Then mobile happened. Not the kind focused on texting or plain-text email. Mobile as in having a frigging supercomputer in your pocket.
First came the app stores, along with the whole infrastructure around them such as authentication, serving downloads, replicating across multiple devices, user reviews, and search and recommendation engines. And oh, dipping the toes in the wonderfully macabre world of payments. Table stakes level++.
And you also had the other, more traditional media – books, movies, music and magazines. So the platform providers expanded the store capabilities to serve those, along with the matching applications to consume that content – reading books and magazines, watching movies and listening to music. Along the way came movie streaming, music streaming and music subscription services. You might also throw in the TV media players and TV streaming / casting sticks into the mix. Table stakes again.
The platforms themselves started branching into ever more form factors, from the traditional desktop screens and the just introduced mobile screens (a phone-sized screen and a tablet-sized screen) into a screen continuum. From phablets to TV screens, jumping into the wearable space (watches and glasses) and most recently into the automotive space. Along the way the platform providers started working on seamless integration flows between multiple devices owned by the same user. That included persisting the flow state and replicating it between devices, as well as unifying the user experience between the various “manifestations” of the underlying platform. And as the platform had successively more chunks moving into the cloud, the companies started investing ever more design resources into defining their own interface language and guidelines to help developers create apps that feel part of the platform. Table stakes raised another level.
And indeed the platform became this amorphous thing, with individual devices and screens at the edges and the data storage / sync layer in the middle. Platform providers offer their own storage and syncing solutions, with (mostly) generous free tier, spanning a variety of user-generated content, from documents to spreadsheets to, most recently, comprehensive photo services. And hey, let’s throw a mapping solution into the mix while we’re at it. Hello table stakes 2015.
There are quite a few pieces whose timelines I’ve generously rearranged in this article. There are a few pieces that were left out for brevity. To name just one, how about a bespoke system font that is used throughout the multiple manifestations of the specific platform. While we’re at it, how about the overall typesetting system in the world of high definition screens and the amazing “inventiveness” of various languages, living and dead. But I digress. Oh wait, how about a frigging voice-controlled assistant that predicts what you want even before you know that you want it?
Being a platform provider in 2015 is a big undertaking. And getting into the platform game might never have been as formidable. And when you’re in that game, you better up your game every year on the clock. At your own developer conference. But hey, no pain no gain. And the gains these days are enormous.
June 11th, 2015
Code reviews are good. You should do them. In this post I’d like to expand on one paragraph from that post:
Don’t do gigantic code drops. If it’s a big feature, map out smaller steps towards the final goal. Mark unfinished places with TODOs so that the reviewer knows this is not the final thing. Build trust in each other to understand that sometimes the road to the final feature takes multiple steps. But always keep an eye on those first couple of steps to make sure that the road is taking the right overall direction.
Gigantic code drops happen every once in a while, especially with new people on the team when they start working on their first feature request. People kind of drop off for a few days, and then they come back with this CL with 1000+ lines of code that span over a couple dozen new and existing files. And that’s very unproductive for everybody.
Some feature work is naturally big, and will eventually indeed span that many lines of code and that many files. But you shouldn’t be trying to do it all in one take. When you’re doing too many things at once, you focus on none, and everything suffers equally. You power through everything from beginning til end, from bringing the data to displaying it on the screen to handling rotation to caching things locally to pixel-perfecting the layout to animations and transitions. And then you add your reviewers. Let’s pretend I’m one of those reviewers.
So I get an email saying that there is a new CL for me to review. I click through and I see that it’s a 1000+ line drop. That’s your first barrier right there. It’s just too much stuff for me to look at right now. Maybe later in the day when I have nothing to do? Because I’d need something like half an hour to take a serious look at it. And it’s not like I just have that half an hour kicking around in my day. I can certainly find 5-10 minutes for a 100+ line review, or even 10-15 minutes for a 200+ line review. But a 1000+ line review? That would need to wait.
And then I finally realize that it’s been sitting in my queue for a while now. And I take a look. And it’s just too much. There’s just too much going on at the same time, from network to caching to persistence to pixels to animations to transitions. I wasn’t there in your head as you were writing it. So now I have to unravel the “roadmap” of this code drop, step by step. How many layers does it have? What is the first step in that roadmap? What is the foundation of that roadmap and your overall direction? How do I identify and compartmentalize each individual layer so that I can intelligently comment on it?
And what happens if I see something that should be reworked completely in one of those foundation steps? How is that going to ripple through the rest of your CL? What are the chances that in order to address the comments in just that one layer you now need to effectively rewrite the whole thing from scratch?
And what happens if me and this other reviewer are having a discussion on this one layer, and there’s a parallel discussion happening on another layer? If you have everything in one big code drop, it’s hard to keep track of what is being discussed, what decisions are being made, and how these decisions need to be translated to revising the code.
It’s perfectly fine to break it into steps. You might not be sure what are those few couple of steps to take at the very beginning – without feeling compelled to travel the whole road. That’s when it’s time for a short discussion. If you know who you’re going to be adding as your reviewers, grab them for a few minutes at their desk or over IM. Talk over the feature and what it involves in your opinion. Describe the steps you’re planning to take to get there, with special emphasis on those few first steps. See if those are the right steps to take. You might “waste” a few extra minutes talking it through, but it’s nothing compared to the hours of your teammates’ time being spent on reviewing that gigantic blob, and to the days of your own time being spent on rewriting that gigantic blob. Possibly more than once.
Break it apart. Define your data structures and server endpoints first. If the server side is not ready yet, create some fake data locally, but try to keep it as closely to the eventual communication protocol as possible so that it’ll be easy to swap that out for real data. Then get something on the screen. It doesn’t have to be beautiful. It needs to be functional so that people can get a rough feel how the data flows on the real screens in their hands. Up until now it’s been pretty sequential. Now you can parallelize the work, either as multiple CLs from you, or multiple people pitching in.
Do a separate CL for local data caching. Do another one for data and view persistence and restore as you rotate the screen, interact with the content around your new bits or go out of your app and back in. Do a separate CL for pixel-perfecting the visuals, attaching screenshots and sending local builds to people to take a look how the static mocks done on gigantic desktop screens actually look and feel on smaller mobile screens. Lay down the ground work for adding animations and transitions, and do those in separate CL(s).
I’d say that an ideal CL is not more than 200 lines of code. Some just have to be more and that’s fine. But if you can keep it under 200 lines or so, you’re making it that much easier for everybody to engage in a productive discussion and to move the process forward. And you’re spiraling ever closer to the final state instead of just going in circles. Funny thing about some spirals though. You might be getting closer but you’ll never quite get there. But that’s a whole other topic.
When you break it down into multiple steps where every step needs to be reviewed separately, it might feel that it’ll take much more time compared to just powering through everything and doing one gigantic code drop. Trust me, it doesn’t. And at some point it just becomes a second nature. You start to break everything into small, manageable steps, and focus on each one of them. And before you know it, the feature is done and ready to ship. And that’s when the really scary bugs start happening.
June 3rd, 2015
Sometimes it feels that you simply can’t win. No matter how many bugs you slay, no matter how fast you crank out those features, your incoming queue never quite dries up. It’s easy to get lost, and it’s hard to see that you’re making a dent.
I’ve been thinking recently that I can easily take three months off, forget that incoming queue and just work on stuff that I’ve been neglecting. Things that can been languishing in the queue, not quite ready to fall off the cliff, but not quite important enough to elbow their way to the top. Things that once were marked as launch blockers, and then when the launch time was getting near, turned out to be actually just nice to have.
Three months feels just right. It’s a good chunk of work that I can tap into every now and then when things get just a tad quiet in the regular release schedule. But it’s not so overwhelmingly big that it makes you think that you’ve sacrificed too many other things at the altar of featuritis.
Of course, I’m rarely in charge of my own time. I don’t quite envision a near future where I can just go off for three months, chipping away at things that I want to do. A fiercely competitive market is a wonderful thing. You can spend all your time tinkering at the edges. Or you can stay focused on your core, falling back to the edges every now and then. Just don’t lose track of which is which for you.