Saturday, April 23, 2016
How to hide Android soft keyboard in tests
Posted by
Drew Hannay,
on
April 23, 2016
I've been running Android UI tests at my company using the API 22 revision 1 emulator (Android 5.1). Things have overall been pretty stable, but since most devices have received an update to at least Android 5.1.1 (the revision 4 emulator of API 22), I wanted to update the emulator we were using to run our tests as well.
When running Espresso UI tests, we found that disabling the software keyboard during tests made them significantly more stable. We did this by setting the following line in our AVD's config.ini file:
hw.keyboard=yes
Configuring our emulator have a hardware keyboard was enough to tell Android not to show the software keyboard while typing in input fields. However, in Android 5.1.1, this behavior changed. A new setting was added in Android 5.1.1 to the "Language & Input" section of the Android OS Settings app:
adb shell settings put secure show_ime_with_hard_keyboard 0
Once we found this command, we modified our emulator startup script to call it after launching the emulator and we were back to our stable behavior - no software keyboard. If you're having similar problems, try out this solution for running tests on Android 5.1.1 and above.
When running Espresso UI tests, we found that disabling the software keyboard during tests made them significantly more stable. We did this by setting the following line in our AVD's config.ini file:
hw.keyboard=yes
Configuring our emulator have a hardware keyboard was enough to tell Android not to show the software keyboard while typing in input fields. However, in Android 5.1.1, this behavior changed. A new setting was added in Android 5.1.1 to the "Language & Input" section of the Android OS Settings app:
The new part is the toggle switch, "Hardware - Show input method". By default, it's enabled, which means that even if the device has a hardware keyboard (or in our case, our emulator is pretending to have a hardware keyboard), the software IME will still be displayed on screen.
This had a significant impact on our tests, and was causing many tests to fail. For example, a test that called the Espresso method pressBack() and expected to close the current activity would fail because the pressBack() call closed the soft keyboard instead of closing the activity.
After quite a bit of searching, I found that it's possible to toggle this OS setting using an ADB command:
adb shell settings put secure show_ime_with_hard_keyboard 0
Once we found this command, we modified our emulator startup script to call it after launching the emulator and we were back to our stable behavior - no software keyboard. If you're having similar problems, try out this solution for running tests on Android 5.1.1 and above.
Monday, February 29, 2016
Android Logcat Tag Best Practices
Posted by
Drew Hannay,
on
February 29, 2016
The Android API provides a useful way for developers to collect and view system debug output. The tool, Logcat, contains stack traces from application crashes, as well as custom messages that developers can provide in their code. Every log message has an associated "tag" field, which is the main focus of this post.
Conventionally, developers will define a constant string field called TAG near the top of their code files. There are a variety of ways to define this field, but the main choices are usually:
I'll state up front, I prefer option #4, for several reasons:
The main downside of option #4 is that the tag won't automatically be updated if you refactor the class name. However, since TAG is generally the first line in a class, it's likely that the discrepancy will be noticed either by the person doing the refactoring or someone who reviews the code change.
Another useful tip for using option #4 is to update your new class template in Android Studio to automatically create your tag for you. Just go to Preferences -> Editor -> File and Code Templates -> Class and change the template to the following:
This will automatically create a TAG field with the same name as your class file, and then you can customize it if needed.
Happy logging!
Conventionally, developers will define a constant string field called TAG near the top of their code files. There are a variety of ways to define this field, but the main choices are usually:
- private static final String TAG = Foo.class.getName(); // "com.example.android.Foo"
- private static final String TAG = Foo.class.getCanonicalName(); // "com.example.android.Foo"
- private static final String TAG = Foo.class.getSimpleName(); // "Foo"
- private static final String TAG = "Foo"; // "Foo"
I'll state up front, I prefer option #4, for several reasons:
- #1-#3 all require the class to do some static initialization work. Yes, it's not MUCH work, but it's more work than nothing.
- #4 allows the compiler to more aggressively optimize, for example, by inlining the TAG string across all log statements at compile time.
- Log tags have a max length of 23 characters, and longer tags will be truncated.
- This means that #1 & #2 have an extremely high chance of truncation right off the bat. Having a tag of "com.example.android.in" instead of "Foo isn't very useful.
- Even with #3, if the class name is long you could still end up with a truncated log tag. With #4, you can customize your tag to ensure it's under 23 characters even if your class name is longer.
- As of Build Tools 21.0.3, there is a lint check for this, but the lint check can only detect tags created with string literals, as in option #4.
- If you're using Proguard to obfuscate your app, then class name Foo gets obfuscated and your tag becomes something like "a" instead of "Foo" if you're using #1-#3.
- #4 is what Google uses inside the AOSP codebase (though I'm not aware of any justification for Google on why they do this)
The main downside of option #4 is that the tag won't automatically be updated if you refactor the class name. However, since TAG is generally the first line in a class, it's likely that the discrepancy will be noticed either by the person doing the refactoring or someone who reviews the code change.
Another useful tip for using option #4 is to update your new class template in Android Studio to automatically create your tag for you. Just go to Preferences -> Editor -> File and Code Templates -> Class and change the template to the following:
This will automatically create a TAG field with the same name as your class file, and then you can customize it if needed.
Happy logging!
Friday, January 1, 2016
Trying out Omnifocus
Posted by
Drew Hannay,
on
January 01, 2016
After recently wrapping up the initial release of the new LinkedIn app for Android & iOS, I took a long-awaited break over the holidays to unwind and refresh. I started taking a look at all the stuff I'd been putting off for the last few months...and realized I needed a better way to stay on top of all this.
I had over 50 constantly open Chrome tabs with things I needed to eventually follow up on. I've tried using bookmarks in the past, but I've found that if it's not in my face somehow, I'll just forget about it. I also had a collection of random files in my Mac's Downloads folder, because I needed to do something with them someday and needed to have them available for that eventuality. I also had about 80 emails waiting in my Gmail inbox for some kind of follow up or future project that would require replying to the thread. There was an assortment of notes in Google Keep mentioning ideas I'd had and wanted to look into once things slowed down. And of course, there was always the mental list of stuff I needed or wanted to do that never made it into any of these "systems"...I'd just remember them periodically and wince that I hadn't done them yet before forgetting them for a few more weeks.
This was not good in a number of ways...it was stress-inducing, for one. It also made it hard to pick something to work on when I actually found myself with some free time...where should I look for a new task? And perhaps the most annoying problem....all those open Chrome tabs slowed down my computer!
So yesterday I downloaded the 14 day trial of OmniFocus. I've heard nothing but praise about this Mac app from The Omni Group so I thought I'd see if it could help me get organized. I won't go into a lot of detail here, because I'm sure others have covered the program much more thoroughly, but so far I'm hopeful that this will help keep things organized. I spent an hour or two doing a brain dump into OmniFocus's "Inbox", converting all my mental todos into "actions", and then slowly converting my open Chrome tabs and emails into actions that had a link back to the referencing web page or email thread.
So far my biggest win is being able to close all the tabs I perpetually had open because I needed to check them for something once per day. I replaced them with a set of repeating tasks to check the pages every morning when I come into work. We'll see how this system works once I get back into the swing of things this month, but for now I'm happy with just my email, calendar, and scrum board tabs open in Chrome.
If I continue to like OmniFocus as much as it seems like I will, I'll definitely be paying the relatively cheap $39.99 price tag for their Mac app. I'd encourage you to check it out if you're looking for a good way to organize your todo list and other tasks.
I had over 50 constantly open Chrome tabs with things I needed to eventually follow up on. I've tried using bookmarks in the past, but I've found that if it's not in my face somehow, I'll just forget about it. I also had a collection of random files in my Mac's Downloads folder, because I needed to do something with them someday and needed to have them available for that eventuality. I also had about 80 emails waiting in my Gmail inbox for some kind of follow up or future project that would require replying to the thread. There was an assortment of notes in Google Keep mentioning ideas I'd had and wanted to look into once things slowed down. And of course, there was always the mental list of stuff I needed or wanted to do that never made it into any of these "systems"...I'd just remember them periodically and wince that I hadn't done them yet before forgetting them for a few more weeks.
This was not good in a number of ways...it was stress-inducing, for one. It also made it hard to pick something to work on when I actually found myself with some free time...where should I look for a new task? And perhaps the most annoying problem....all those open Chrome tabs slowed down my computer!
So yesterday I downloaded the 14 day trial of OmniFocus. I've heard nothing but praise about this Mac app from The Omni Group so I thought I'd see if it could help me get organized. I won't go into a lot of detail here, because I'm sure others have covered the program much more thoroughly, but so far I'm hopeful that this will help keep things organized. I spent an hour or two doing a brain dump into OmniFocus's "Inbox", converting all my mental todos into "actions", and then slowly converting my open Chrome tabs and emails into actions that had a link back to the referencing web page or email thread.
So far my biggest win is being able to close all the tabs I perpetually had open because I needed to check them for something once per day. I replaced them with a set of repeating tasks to check the pages every morning when I come into work. We'll see how this system works once I get back into the swing of things this month, but for now I'm happy with just my email, calendar, and scrum board tabs open in Chrome.
If I continue to like OmniFocus as much as it seems like I will, I'll definitely be paying the relatively cheap $39.99 price tag for their Mac app. I'd encourage you to check it out if you're looking for a good way to organize your todo list and other tasks.
Wednesday, November 25, 2015
Gradle Performance Tips from Android Dev Summit
Posted by
Drew Hannay,
on
November 25, 2015
Yesterday was the last day of the Android Dev Summit conference by Google. The conference was packed with deep-dive tech talks on some of the most relevant topics to Android devs today, including Gradle build optimizations, performance testing tools, and data binding tips. I left the conference with a long list of things I can change to improve the performance of my build and tests, and be ready for new tooling features from Google like Instant Run.
The full list of improvements will take a while to implement, but there were three simple fixes I was able to make in a day that had a dramatic impact on the performance of my builds.
------------------------------ ------------------------------ ------------
The full list of improvements will take a while to implement, but there were three simple fixes I was able to make in a day that had a dramatic impact on the performance of my builds.
------------------------------
#1 Don't add timestamps, etc to BuildConfig fields in debug builds
It's very convenient to add extra fields to your BuildConfig file. My app adds the git commit sha and the build time so we can display this information inside the app on the developer settings screen. Unfortunately, both of these things change very frequently; timestamp obviously changes every time you build. This means the BuildConfig file needs to be regenerated on every build, and any files that depend on it (probably many files in the app) need to be recompiled as well, essentially killing the benefits of incremental builds.
The fix for this one is to only generate these fields in release builds. In debug fields, we just set them to the constant string "DEBUG".
For example:
buildTypes {
debug {
buildConfigField 'String', 'GIT_SHA', "\"DEBUG\""
}
release {
buildConfigField 'String', 'GIT_SHA', "\"${gitSha()}\""
}
}
------------------------------ ------------------------------ ------------
#2 Use the official api for generating code
Version 0.7.0 of the Android Gradle plugin added two api methods: registerJavaGeneratingTask() and registerResGeneratingTask(). These let you register a custom gradle task that will generate code, and will automatically set up the code to be generated at the proper time and indexed & recognized as source by the IDE.
My app used a custom task to generate model files from API data schemas, but we were manually inserting it into the build pipeline without using the official api. The downside of this is that the task was never recognized as "up to date", which forced all models (and all classes that depended on those models) to be recompiled on every build, removing the benefits of incremental builds. Before switching to the official API, running "gradle assembleGoogleDebug" consistently took ~2 minutes, even if there were no code changes. After switching, running the task with no code changes takes ~16 seconds.
------------------------------ ------------------------------ ------------
#3 Fix dependency conflicts between different configurations
This one isn't a performance optimization, but rather a way to increase the maintainability of your build script. I ran into this issue a long time ago on my app; Android testing works by loading your app apk and your test apk in the same classpath. If the two configurations depend on different versions of the same library, you'll run into crashes at runtime due to the conflict. The most frequent offender is the support annotations jar, since almost every modern Android library depends on it. Gradle already forces your build to use the same version of shared transitive dependencies, but this only works within the same configuration.
I fixed this previously using Gradle's resolutionStrategy option to force all configurations to resolve to the same version of a dependency, but this isn't a great solution because it requires you to hardcode the version of your dependency in a second location and there's a strong chance you'll forget to update it. The better option is to just add the dependency normally, but add it to both your prod and test configurations.
For example:
compile "com.android.support:support-annotations:${supportLibVersion}"
androidTestCompile "com.android.support:support-annotations:${supportLibVersion}"
------------------------------ ------------------------------ ------------
This is just the low hanging fruit that I was able to optimize in a day. Going forward, I'll be following up on several other lessons learned from the conference and hopefully improving build times and incremental compilation times even further. Be sure to check out the session videos from the Android Dev Summit conference if you want to learn more!
Sunday, October 11, 2015
How to ask a good question
Posted by
Drew Hannay,
on
October 11, 2015
In any technical job, you will inevitably run into situations where you simply get stuck. You’ve tried to find the next step, but aren’t making any progress. Rather than waste time spinning your wheels, you decide to ask a co-worker for help. However, you don’t want to waste anyone’s time or be the annoying employee that can’t figure out anything on his own. Here are some tips for how to ask a question while still leaving a good impression on your teammates.
Before asking, be able to answer a few questions…
- What have you tried so far? Make sure you do some initial investigation on your own before asking for help. You may find that five extra minutes of searching will get you the answer just as fast as asking someone else. When you do need to ask for help, make sure to share what you’ve already tried, so your co-worker doesn’t repeat the same steps.
- What information do you have about the problem? Keep track of anything you think might be relevant so you can share it with the person you ask for help. Did the problem start happening right after upgrading a related piece of software? That’s probably worth sharing.
- What are the next things you think you should try? It should be pretty rare that you get to the point where you’re truly out of ideas for how to investigate your problem. If your next step is “google the error message,” then you probably haven’t spent enough time investigating on your own. On the other hand, if your next step is “debug the source code of the OS kernel,” you’ve probably waited a bit too long to ask for advice. The trick is to make sure you’ve done your due diligence, but haven’t wasted too much time on a problem that your co-worker might be able to solve in five minutes.
Still stuck? Time to bring in the cavalry…
- Start with a wide audience and narrow if necessary. Does your team have a group chat room? That’s a good first place to ask, since many people will see your question and be able to either provide an answer or point you in the right direction.
- Don’t interrupt, if possible. If you need to target your question at a specific person, start with an instant message. This lets them respond to you when it’s convenient for them. Dropping by their desk unannounced may break their concentration or take them away from another important task.
- Start with a short summary of the problem. There’s always a chance your co-worker has seen this before, so give them enough information to recognize the problem, but there’s no need to give a multi-page summary right off the bat.
Once you've asked...
- Be respectful of the person's time. If you're asking about something that might take a while, like building the codebase or running tests, rather than standing at their desk while you wait to see if the fix works, you can collect a series of things to try. Write them down so you don't forget, and then go back and try them on your own. Feel free to send a message to the person who helped you to let them when and how things got resolved.
- Pay attention to how the person helping you investigates the problem. What do they do that you didn’t? Sometimes they will just “know” the answer; if this is the case, ask them how you could have figured out the solution on your own. Could documentation be improved for the process? Volunteer to record your findings so others can benefit from your solution.
- For coding-related questions, make sure your IDE/keyboard/etc are using standard shortcuts and settings. If the person helping you needs to use your computer, be ready to turn off vim mode and your Dvorak keyboard settings to make it easy for her to navigate and type.
- Pay back the favor! If someone else runs into the same problem in the future, help him or her find the answer and share your findings. This is the best way to scale knowledge and improve the skills of your entire company.
Friday, January 9, 2015
How intuitive is Android Lollipop?
Posted by
Drew Hannay,
on
January 09, 2015
My wife and I spent Christmas with my in-laws, as we've done for the past several years. When we arrived, they made their request for this year's "tech support project": they wanted to move their emails and calendar from MSN to Gmail. If you're the go-to IT person for your family, you're probably thinking "Just what I wanted to do on my Christmas break...migrate thousands of emails and contacts to Gmail"... but I was thrilled. See, what my in-laws didn't know is that we were already planning on giving them each a Nexus 4 for Christmas and having their email, contacts, and calendar already in Google's realm would make it much easier to start using their new Android devices.
Out with the old...
Their old phones were tiny-screened, heavily OEM-modified, extremely buggy Android phones running the four-year-old Gingerbread operating system. They used them for phone calls, texting, and the occasional Hangouts group chat, but they weren't really capable of much else. Doing something like email on their phone was unimaginable, because email was something that was (in their minds) tied to their laptop.
For years they have paid for MSN Premium and have continued to pay for the antiquated service because, let's be honest: it worked well enough for them and why put the energy into learning something new? But they had finally gotten fed up with waiting multiple minutes for their email to load and were ready to make the switch.
...and in with the new
I migrated all their data to Gmail, Google Calendar, and Google Contacts and then it was time to show them how to use their new tools. For the most part, the web features were pretty simple; maybe a few buttons have moved around, but an email program is an email program. I explained Gmail's label system and how it was different from folders, showed them how to send email from their MSN addresses through Gmail, and how the Social and Promotions tabs keep their inbox clear of clutter. Making new Calendar appointments was similarly simple. The most interesting insights came when it was time to set up their Nexus 4 devices.
Give me tricks that work everywhere
As I walked them through how to use the device, it was fascinating to see them interact with each new app. One of the Android Design Principles is "Give me tricks that work everywhere" and I can definitely confirm that worked for my in-laws. My father-in-law quickly latched on to the swipe-to-dismiss gesture and merrily tried to swipe away anything he didn't want to see. The cool part? It usually worked! The pattern is now pervasive enough that swiping away a card, an email, or a notification will usually do what you wanted. Another trick I showed them was tapping on a contact picture to enter multiple select mode, which worked across many of the apps they used.
Web & App consistency is a win/win
Question: Where do you find your labels in Gmail? Answer: On the left side. Wait, was I talking about the Android app, the iOS app, or the web? It doesn't matter! How do you write a new email? There's a big red Compose button. Sure, it's a pencil icon on mobile, but it's still a (relatively) big red button. This consistency helped my in-laws tremendously. All the knowledge they gained learning the web interface of Gmail, Calendar, Photos, and more could be directly applied to the app's Android counterpart. They didn't need to learn an entirely new interface, which greatly increases the chances they'll actually use these things on their phones. From the app developer's perspective, it's also great...you don't need to come up with both a web interface and a mobile interface; you can just make minor tweaks and have your app scale across screens. Interface consistency is a win for everyone.
Visual cues work
Android Lollipop's Material Design encourages using motion to gives users a hint about how to use your app. When you tap a notification on the lock screen, you get a subtle message telling you to double tap to open. When you take a photo with the camera app, the picture peeks out from the right side of the screen, letting you know you can swipe right to view your photos. When you tap the status bar (something my in-laws do frequently as they forget they should pull it down), your notifications peek out and encourage you to swipe down to open the notification drawer. These cues, at least in my limited set of users, work amazingly well. As I watched them interact with their phones, they would try to perform some action, get a visual hint from the system about what to do, and then reach success relatively quickly.
What didn't work
There are still some areas in Android that aren't very intuitive and caused some struggles for my in-laws. The thing we spent the most time reviewing was home screen widgets, which heavily utilize one of the most undiscoverable features of Android - the long press. To add a new widget, you must long press on the background, click "Widgets", then long press again on the widget you want and (don't lift your finger or you'll mess it up!) drag it to the right location. To resize a widget, you must long press it yet again and then lift your finger (which is okay this time, it won't mess things up) and the resizing grabbers will appear. To remove a widget, you must long press it and drag it to the top of my screen. I spent several painful minutes watching my father-in-law try to swipe-to-dismiss a widget before finally intervening and reminding him what to do.
Take aways for Third Party developers
On their old phones, my in-laws used the text-messaging app, Hangouts, and the Dialer. Needless to say they aren't big app users. Now they're using several more of the stock Google apps, but don't really have much interest in installing third-party apps (maybe that will be next year's Christmas project). The one exception is that my mother-in-law uses Facebook, so she installed both Facebook and Facebook Messenger. Immediately she was confused by the non-standard behavior in those apps. "Why can't I swipe away my conversation with Suzanne? Why aren't my groups on the left side like on my computer? Why doesn't it select more than one conversation when I click on a person's picture?" I'd argue that these aren't features of Google apps, they're features of Android apps. Supporting swipe-to-dismiss or a floating action button probably won't cause any damage to your brand and it can really help decrease the amount of new stuff a user has to learn to use your app. And the easier your app is to use, the more likely they are to use it, right?
Friday, September 26, 2014
When Should I Drop Support for Android 2.3 and iOS 5?
Posted by
Drew Hannay,
on
September 26, 2014
My previous company, Logos Bible Software (recently renamed to Faithlife Corporation), has just dropped support for Android 2.3 Gingerbread for their suite of mobile apps and will soon be dropping support for iOS 5 for their iOS suite. This caused a few minor complaints on the Android side, but for the most part people understood that their device was too old to be supported. On the iOS side, there are many users still using the original iPad which runs iOS 5 and they are experiencing some crashes due to the iPad 1 not having enough memory to handle the needs of the Logos apps. Obviously the ideal situation is to leave those users with a version of the app that performs well on their device, but at this point is it worth the extra developer hours to fix a problem that only exists for a small percentage of users on a soon-to-be obsolete device? It's a tricky problem.
Thinking about that problem led me to muse on how an app can get to this point in the first place. When should a developer drop support for an older operating system? This applies to desktop software programs as well, but the pace of desktop operating systems changing is so much slower than mobile, I don't think it's as big an issue.
1) You could set a hard and fast rule that you drop support for an operating system version when the percentage of users running that version drops below 5% (or some other arbitrary number).
2) Pick a cut off date and drop support for the operating system version on that date, regardless of how many users remain on that platform.
Thinking about that problem led me to muse on how an app can get to this point in the first place. When should a developer drop support for an older operating system? This applies to desktop software programs as well, but the pace of desktop operating systems changing is so much slower than mobile, I don't think it's as big an issue.
The choices
There are two main approaches I can think of:1) You could set a hard and fast rule that you drop support for an operating system version when the percentage of users running that version drops below 5% (or some other arbitrary number).
2) Pick a cut off date and drop support for the operating system version on that date, regardless of how many users remain on that platform.
Issues with the first approach
If you insist on waiting until the user base drops below some percentage, the main problem is who knows when that will happen? At the time of this writing, Android 2.3 still has over 11% of the total Android marketshare. It could be quite a while longer before it drops below 5%. Additionally, if that's the only criteria, it's easy to see how performance could start to degrade on older devices because if there's one thing you should know about developers, it's this: Developers like having the newest stuff. They run the beta version. They upgrade their devices as soon as they can convince their spouse they "need" a new phone "for work". Developers are testing their code on that top shelf, speed demon device they love, not the $50 bargain bin smartphone your parents picked up to go with their Walmart family plan. Now don't get me wrong, this is the fault of the developers. They should be testing on those devices FIRST, instead of not at all. But that's not fun, and unfortunately, many devs won't do it. So by the time an OS version drops below the magic percentage, the app might be so fragile on that platform that it's unusable anyway.
Issues with the second approach
From a developer's perspective, it feels a bit weird to release a new version of an app that drops support for iOS 5 or Android 2.3 (etc) without any real reason other than "we want to leave those users with a stable version". Granted, that's an admirable goal, but "this new release adds five new features!" and it's not necessarily unstable/unusable, so it feels like you're cheating those users out of the new features and fixes. The instability creeps in over time and is hard to notice until it's already too late. Objectively speaking, this option is probably preferable to the first choice, but when it comes time to actually make that call, it leaves both users and developers unhappy.
Other options?
What else is there? Maybe Apple has the right idea. They say that developers should support the "current version and the current version - 1" (I tried really hard to find a source for this, but had no luck. Pretty sure it was a slide in a WWDC presentation). That probably works on iOS and OS X where most users update to the new version right away (if their hardware supports it), but it's not really feasible on Android or Windows.
I'd be interested in hearing other people's thoughts. I don't know what the right answer is, but it's definitely a tough problem and one that is important to get right to avoid leaving a bad taste in the mouths of users.
Subscribe to:
Posts
(
Atom
)