Most of the items in the Android 4.2 APIs list are kind of "meh" to me, but there is one item that's make me very happy: nested fragments. Fragments were supposed to be reusable UI components, but for some unfathomable reason the initial release of the fragments API didn't allow composing a fragment from other fragments. Even during the 2012 Google I/O one of the Google developers said that they have discussed this idea, but they're not sure if it's a good one, which is an absolute surprise to me, because I don't think anyone sane would consider making the ViewGroup which isn't a subclass of a View.
Fragment nesting was a topic I kept on my topic list for a day when I feel I need to bitch about something, but now I'm happy to remove it and write this ecstatic post. So now launch your SDK managers and download the r11 of support package! (or wait for half a year and hope it will be added to Maven Central)
Tuesday, November 13, 2012
Wednesday, October 17, 2012
Android protip: remove debug logs from release builds with Proguard
I use the android.util.Log extensively - often it's faster than starting the debugger and (unlike debugging) it's always on, which is invaluable when you're trying to track the root cause of some hard to reproduce bugs. Logging is also nice for release candidate builds you give to the QA team - if they find some bugs you'll have more information than just a stack trace.
On the other hand you don't want to keep all those logs for release builds, mostly for performance and privacy reasons. If you google around for a solution to this issue you'll probably find a dozen ideas like using a wrapper for logging class, using a dedicated logging framework or even some sed/awk scripts to ant build process. Among those ideas is the one I'd like to recommend: customizing Proguard configuration.
Proguard is automatically run in release builds by ant and Export wizards from Eclipse and there is an optimization option, which can be used to disable logs:
You can of course keep some of the priority levels by removing lines from this config.
The nice thing about this solution is that you have to set it only once and it will just work for all further release builds.
UPDATE (August 2013): do not assume that Proguard will remove any code other than the methods listed in assumenosideeffects setting. See "Proguard gotcha" post for more details.
On the other hand you don't want to keep all those logs for release builds, mostly for performance and privacy reasons. If you google around for a solution to this issue you'll probably find a dozen ideas like using a wrapper for logging class, using a dedicated logging framework or even some sed/awk scripts to ant build process. Among those ideas is the one I'd like to recommend: customizing Proguard configuration.
Proguard is automatically run in release builds by ant and Export wizards from Eclipse and there is an optimization option, which can be used to disable logs:
-assumenosideeffectsI wouldn't use this tool for the purpose stated in the manual, because even if my code doesn't have any side effects, the methods called from it might have ones; I'd rather use some static code analysis tool to find the unnecessary calls and manually remove them. It looks perfect for suppressing the logs though and it's very simple to set up - just add the following lines to the proguard-project.txt file generated by android command line tool:
Specifies methods that don't have any side effects (other than maybe returning a value). In the optimization step, ProGuard will then remove calls to such methods, if it can determine that the return values aren't used.
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
You can of course keep some of the priority levels by removing lines from this config.
The nice thing about this solution is that you have to set it only once and it will just work for all further release builds.
UPDATE (August 2013): do not assume that Proguard will remove any code other than the methods listed in assumenosideeffects setting. See "Proguard gotcha" post for more details.
Tuesday, October 9, 2012
Android font metrics for dummies
Recently I was working on a custom View with overridden onDraw method and at some point I needed to center some text vertically. Unfortunately Paint.setTextAlign() supports only horizontal orientation, so I tried calculating the y-coordinate of the origin myself, but I couldn't get it exactly right. After two failed attempts I decided that I need a program which visualizes the available font metrics, because it seems that I do not understand the FontMetrics documentation, or the aforementioned class and it's documentation sucks.
You can find the source code on my GitHub, and here's the screenshot for other typographically challenged programmers:
You can find the source code on my GitHub, and here's the screenshot for other typographically challenged programmers:
(BTW: the word "Żyłę" used there is not a complete gibberish, it's an accusative case of word "vein" in Polish; it's nice, because it's short, but it covers all cases of the metrics class).
Let's get back to vertical alignment. In general you should center text vertically either on x-height or on half the cap height above the baseline (at least that's the info I found). Neither metric is directly available in FontMetrics class, but you can approximate the cap height as a (textSize - descent) or calculate x-height yourself using Rect height returned by Paint.getTextBounds for string "x".
Wednesday, October 3, 2012
Screensaver blocking on Symbian
In my latest game the players spend significant amount of time just watching the screen and trying to figure the puzzle out. The first few levels are obvious and most players will sole them in a few seconds,m but as the difficulty of the puzzles increases the players stare at the screen longer and longer. At some point the screensaver would kick in and piss the player off (fortunately my ragtag QA/betatester team found this issue before launch).
Google search results are (as usual) helpful, but the most promising lead, the QSystemScreenSaver class is not a solution. There are three problems with it:
(BTW: these three points sums up pretty much every experience with Qt Mobility package I had. Qt devs should either kill this festering boil with fire or fix it and rename it, because I learned to dread everything remotely related to Qt Mobility, and I suspect I'm not the only one).
Anyways, let's get back to the core of the problem, i.e. "how to block the screensaver". Qt Mobility failed, but the task doesn't seems like a rocket science to me. Slightly different Google search suggested using native Symbian's User::ResetInactivityTime() method. Few minutes and one QTimer later, everything worked:
If you want to use this object in your QML UI just register it with qmlRegisterType and add the registered import and QML element to your root element.
Google search results are (as usual) helpful, but the most promising lead, the QSystemScreenSaver class is not a solution. There are three problems with it:
- The API of the class itself is terrible.
- The API of the related QML element is even worse.
- Last, but not least, it doesn't work (at least not in the Qt Mobility version shipped with the Qt SDK).
(BTW: these three points sums up pretty much every experience with Qt Mobility package I had. Qt devs should either kill this festering boil with fire or fix it and rename it, because I learned to dread everything remotely related to Qt Mobility, and I suspect I'm not the only one).
Anyways, let's get back to the core of the problem, i.e. "how to block the screensaver". Qt Mobility failed, but the task doesn't seems like a rocket science to me. Slightly different Google search suggested using native Symbian's User::ResetInactivityTime() method. Few minutes and one QTimer later, everything worked:
#ifndef SCREENSAVERBLOCKER_H
#define SCREENSAVERBLOCKER_H
#include <QObject>
#include <QApplication>
#include <QTimer>
class ScreenSaverBlocker : public QObject
{
Q_OBJECT
public:
explicit ScreenSaverBlocker(QObject *parent = 0) : QObject(parent) {
mTimer.setInterval(1000);
connect(&mTimer, SIGNAL(timeout()), this, SLOT(blockScreenSaver()));
changeScreenSaverState(QApplication::activeWindow() != 0);
if (qApp) {
qApp->installEventFilter(this);
}
}
void changeScreenSaverState(bool blockScreenSaver) {
if (blockScreenSaver && !mTimer.isActive()) {
mTimer.start();
} else {
mTimer.stop();
}
}
protected:
bool eventFilter(QObject *obj, QEvent *event) {
Q_UNUSED(obj)
if (event->type() == QEvent::ApplicationActivate
|| event->type() == QEvent::ApplicationDeactivate) {
changeScreenSaverState(event->type() == QEvent::ApplicationActivate);
}
return false;
}
private slots:
void blockScreenSaver() {
#ifdef Q_OS_SYMBIAN
User::ResetInactivityTime();
#endif
}
private:
QTimer mTimer;
};
#endif // SCREENSAVERBLOCKER_H
The important thing in the code above is watching the ApplicationActivate and ApplicationDeactivate events - after all, when your app is in background, you shouldn't affect the phone behavior. I'm not sure if the app would fail the Nokia's QA process without this feature, but it seemed prudent to write the code this way.If you want to use this object in your QML UI just register it with qmlRegisterType and add the registered import and QML element to your root element.
Labels:
Game,
Nokia,
Printer 1D,
QML,
Qt,
Qt Mobility,
Symbian
Wednesday, September 26, 2012
Android sticky broadcast perils
I might have called this blog post "Android sticky broadcasts considered harmful", but "Considered harmful" texts are considered harmful, so I opted for a less linkbaity title.
I was working on a bug in a legacy code which used sticky broadcasts for publishing the sync service state (BTW: if you thought "Hello, ContentResolver.isSyncActive?" you should subscribe to my blog feed - I'm planning to write about the problems with sync state methods in ContentResolver). The bug manifested as a minor UI issue - sometimes the UI indicated that sync is in progress, even in situations when the sync could not be in progress, for example because the network connection was down. The QA team found an easy reproduction steps for it: start sync and reinstall the app before the sync finishes.
I found much more troubling reproduction steps: start sync, uninstall app while sync is in progress and then install it again. The difference between those steps and the ones found by QA is the fact that I'm performing uninstall, which means that all information about my app should be wiped from the system.
I dug deeper and found out two scary facts:
- Sticky broadcasts are not connected in any way to app package, which means they are not removed on uninstallation. They are removed on phone restart, but that's not a scenario you should rely upon.
- Any application with BROADCAST_STICKY permission may alter your sticky broadcasts.
As long as you don't use sync status for anything more complicated than showing a spinner in Action Bar, you might get away with only minor UI issues, but if you want to use it for business logic, you're entering the world of pain.
The sticky broadcasts might be garbage left over by the old version of your app or some other app if you've chosen the action string poorly (protip: if you really have to use sticky broadcasts, include your app's package name in action string). Even if you ignore the latter case (and the very unlikely scenario of other app maliciously tinkering with your app's intents), I consider the former case to be a deal breaker - each sticky broadcast is an additional state which has to be migrated between app versions and app installations which further increases cognitive load of programming, which is high enough already.
Let's summarize this blog post: remove BROADCAST_STICKY permission from your app's AndroidManifest.xml, test the app, fix all crashes from SecurityExceptions and never look back.
Wednesday, September 19, 2012
Android heterogeneous Adapters gotcha
Unless you were writing your Android apps under some kind of digital rock, you heard about Mark Murphy a.k.a. commonsguy. Today I'd like to write about a gotcha related to heterogeneous Adapters in general, which recently bit me in the rear when I used (misused?) one of Mark's Android components - MergeAdapter.
As you can read on the project's site, "MergeAdapter accepts a mix of Adapters and Views and presents them as one contiguous whole to whatever ListView it is poured into". This means of course that this is a heterogeneous adapter, i.e. the one which returns integer > 1 from getViewTypeCount(). The implementation of this method is pretty straightforward - it just iterates through the list of adapters it consists of and returns the sum of getViewTypeCount()s :
The root cause is the undocumented fact that the getViewTypeCount() is called only once after attaching it with ListView.setAdapter(). In the example above, the MergeAdapter contains only one item type when the setAdapter() is called, getViewTypeCount() will return 1, and adding the second adapter with another item type won't change this.
Why doesn't this crash right away? The ListView will call getView() in correct adapters, but then it will try to reuse items created by one adapter for items in the second adapter, because it assumes there is only one view type (getViewTypeCount() returned 1).
So what's the lesson here? Do not change the MergeAdapter in loader callbacks, either construct it before setAdapter() call (for example add empty CursorAdapters and call CursorAdapter.changeCursor() later) or postpone the setAdapter() call until you load all the parts. The more general rule is that you may not calculate the number of item types from the actual data, for example the following code won't work:
As you can read on the project's site, "MergeAdapter accepts a mix of Adapters and Views and presents them as one contiguous whole to whatever ListView it is poured into". This means of course that this is a heterogeneous adapter, i.e. the one which returns integer > 1 from getViewTypeCount(). The implementation of this method is pretty straightforward - it just iterates through the list of adapters it consists of and returns the sum of getViewTypeCount()s :
@Override
public int getViewTypeCount() {
int total=0;
for (PieceState piece : pieces.getRawPieces()) {
total+=piece.adapter.getViewTypeCount();
}
return(Math.max(total, 1)); // needed for
// setListAdapter() before
// content add'
}
Everything is fine and dandy if you use the code like this:@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
MergeAdapter adapter = new MergeAdapter();
adapter.addView(someView);
adapter.addAdapter(someAdapter);
setListAdapter(adapter);
}
But sometimes you might want to attach the MergeAdapter to ListView and add fill it later (the real scenario for this case is adding stuff in onLoadFinished callback, I'm using contrived example for sake of simplicity):@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
MergeAdapter adapter = new MergeAdapter();
adapter.addAdapter(someAdapter);
setListAdapter(adapter);
adapter.addAdapter(someOtherAdapter);
}
This code will work as long as the adapter's contents fit on one screen, but if you start scrolling the list and the item recycling kicks in your app will crash with ClassCastException from your adapters' getView(). If by some chance you use the same IDs for the Views of the same type the app won't crash, but your items probably won't look exactly as they should. Either way, you won't be happy.The root cause is the undocumented fact that the getViewTypeCount() is called only once after attaching it with ListView.setAdapter(). In the example above, the MergeAdapter contains only one item type when the setAdapter() is called, getViewTypeCount() will return 1, and adding the second adapter with another item type won't change this.
Why doesn't this crash right away? The ListView will call getView() in correct adapters, but then it will try to reuse items created by one adapter for items in the second adapter, because it assumes there is only one view type (getViewTypeCount() returned 1).
So what's the lesson here? Do not change the MergeAdapter in loader callbacks, either construct it before setAdapter() call (for example add empty CursorAdapters and call CursorAdapter.changeCursor() later) or postpone the setAdapter() call until you load all the parts. The more general rule is that you may not calculate the number of item types from the actual data, for example the following code won't work:
public class MyCursorAdapter extends CursorAdapter {
// Implementation of bindView, newView, etc. skipped
private int mCalculatedItemTypeCount;
@Override
public int getViewTypeCount() {
return Math.max(mCalculatedItemTypeCount, 1);
}
@Override
public void changeCursor(Cursor cursor) {
mCalculatedItemTypeCount = /* some calculations */;
super.changeCursor(cursor);
}
}
Wednesday, September 12, 2012
Screen orientation and QT applications on Symbian Belle
Let's take a break from Android gotchas and do some mobile necrophilia, i.e. let's talk about Symbian.
Recently I received an email from Nokians saying that they are testing Nokia Store content with new firmware update and my app won't work after update. After few quick email exchanges we narrowed down the problem to screen orientation locking code I wrote about some time ago. It turns out that things can be done much simpler:
I'm very positively surprised with Nokians' approach, responsiveness and this whole experience. Of course I wouldn't be me if I didn't bitch a little bit, namely: why did I have this problem in the first place? I mean, locking the screen orientation is not a rocket science and should be well documented. It should, but unfortunately it's not, like so many things about QML.
Recently I received an email from Nokians saying that they are testing Nokia Store content with new firmware update and my app won't work after update. After few quick email exchanges we narrowed down the problem to screen orientation locking code I wrote about some time ago. It turns out that things can be done much simpler:
Window {
Rectangle {
id: root
anchors.fill: parent
Component.onCompleted: {
screen.allowedOrientations = Screen.Landscape
}
// more stuff
}
}
int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
QmlApplicationViewer viewer;
viewer.setMainQmlFile(QLatin1String("qml/nupagadi/GameArea.qml"));
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationLockLandscape);
viewer.setResizeMode(QDeclarativeView::SizeRootObjectToView);
viewer.showExpanded();
return app->exec();
}
Less code, no need to comment it as a gotcha/workaround, and it's supposedly futureproof.I'm very positively surprised with Nokians' approach, responsiveness and this whole experience. Of course I wouldn't be me if I didn't bitch a little bit, namely: why did I have this problem in the first place? I mean, locking the screen orientation is not a rocket science and should be well documented. It should, but unfortunately it's not, like so many things about QML.
Subscribe to:
Posts (Atom)
