Porcupine Programmer

Programming rants, random stuff and some more programming.

Android Heterogeneous Adapters Gotcha

| Comments

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 :

1
2
3
4
5
6
7
8
9
10
11
12
@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:

1
2
3
4
5
6
7
8
9
10
11
@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):

1
2
3
4
5
6
7
8
9
10
11
12
@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.swapCursor() 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
  }
}

Screen Orientation and QT Applications on Symbian Belle

| Comments

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:

1
2
3
4
5
6
7
8
9
10
11
12
Window {
    Rectangle {
        id: root
        anchors.fill: parent

        Component.onCompleted: {
            screen.allowedOrientations = Screen.Landscape
        }

        // more stuff
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
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.

Android SharedPreferences Gotcha

| Comments

I have another gotcha for you. Can you tell what’s wrong with the following code?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MyFragment extends Fragment implements OnSharedPreferenceChangeListener {
  private TextView mInfo;
  private SharedPreferences mPreferences;

  public static final String INFO_SP_KEY = "info";

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    return inflater.inflate(R.layout.my_fragment, container, false);
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mInfo = (TextView) view.findViewById(R.id.info);
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    if (key.equals(INFO_SP_KEY)) {
      updateInfo();
    }
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
  }

  @Override
  public void onPause() {
    super.onPause();
    mPreferences.unregisterOnSharedPreferenceChangeListener(this);
  }

  @Override
  public void onResume() {
    super.onResume();
    mPreferences.registerOnSharedPreferenceChangeListener(this);
    updateInfo();
  }

  protected void updateInfo() {
    mInfo.setText(getString(R.string.info_text, mPreferences.getInt(INFO_SP_KEY, 0)));
  }
}

At first glance everything looks fine and in most cases it will work fine as well. However, if you a) set android:minSdkVersion to 8 or lower and b) change the shared preference from another thread (IntentService, SyncAdapter, etc.), you’ll get the following crash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
09-05 07:16:58.993: E/AndroidRuntime(403): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.ViewRoot.requestLayout(ViewRoot.java:594)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.checkForRelayout(TextView.java:5378)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2688)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2556)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2531)
09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.BaseFragment.updateButtonText(BaseFragment.java:65)
09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.WrongFragment.onSharedPreferenceChanged(WrongFragment.java:12)
09-05 07:16:58.993: E/AndroidRuntime(403): at android.app.ContextImpl$SharedPreferencesImpl$EditorImpl.commit(ContextImpl.java:2830)
09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.BaseFragment$1$1.run(BaseFragment.java:36)
09-05 07:16:58.993: E/AndroidRuntime(403): at java.lang.Thread.run(Thread.java:1096)

Fortunately the obvious crashlog is obvious and you can solve this issue in about 5 seconds, by wrapping the UI action in Activity.runOnUiThread() method. Morbidly curious may track the root cause of this issue in GrepCode. Tl;dr: before Gingerbread the listeners are notified in the same thread as the SharedPreferences.commit() caller, in later releases commit() ensures the notifications are performed in UI thread.

Code of the sample application that demonstrates this issue is available on my github.

Custom Scrollbar Graphics in Android

| Comments

One of the UI elements you might want to customize in your app are scrollbars for ListViews, ScrollViews and other scrollable objects. It’s a minor visual change, but it might give your app more consistent and polished look and feel, especially in case of the heavily branded UI.

Anyways, changing the default scrollbar style is dead simple – you just need to specify new drawables for the track and thumb in your style:

1
2
3
4
<style name="badList">
    <item name="android:scrollbarThumbVertical">@drawable/scrollbar_handle</item>
    <item name="android:scrollbarTrackVertical">@drawable/scrollbar_track</item>
</style>

Of course, there is a small gotcha (why else would I bother to write this blog post?). Let’s say that you don’t need to customize horizontal scrollbar, so you prepare only vertical 9-patches:

On Ice Cream Sandwich everything looks fine, but on Gingerbread the graphics are not exactly what you want:

Quick Google search returned a StackOverflow thread with a description and link to Android bug tracker, but no full workaround. If you’re too lazy to click on those links, on Gingerbread and earlier releases the View asks ScrollbarDrawable for the height of horizontal scrollbar and uses it as a horizontal scrollbar height and vertical scrollbar width. Let’s modify our scrollbar graphics a bit:

And apply it as both horizontal and vertical scrollbar.

1
2
3
4
5
6
<style name="goodList">
    <item name="android:scrollbarThumbVertical">@drawable/scrollbar_handle</item>
    <item name="android:scrollbarTrackVertical">@drawable/scrollbar_track</item>
    <item name="android:scrollbarThumbHorizontal">@drawable/scrollbar_handle</item>
    <item name="android:scrollbarTrackHorizontal">@drawable/scrollbar_track</item>
</style>

Lo and behold, it works!

Note: in general case you probably want to create another graphics for horizontal scrollbar by rotating and flipping vertical scrollbar graphics. Our scrollbar graphics doesn’t have any non-symmetric elements and I’m lazy, so I used the same 9-patch for both scrollbars.

The code of the sample application can be found here.

More Content Coming Soon

| Comments

It’s been a while since my last blog post. The main reason of this unfortunate state is the fact that I’ve changed my day job. I’ve dumped my cushy corporate post in favor of more demanding position at startup. I have much less time for blogging and my personal projects, but in return I’ve learned a lot about Android. I already have about two dozens entries on my topic list and I keep adding new ones every other day.

The “write when you have some time” plan obviously doesn’t work, so I have to try something else. My current plan is “publish one post every Wednesday”. We’ll see soon how will it work out for me.

SQLite Unions Gotcha

| Comments

Recently I’ve been tracking the problem with SQLite database used in the Android application I’m working on. The starting point of the whole story is that I’ve noticed that the cursor created with the SQLiteDatabase.query() method returned smaller data set than the same query executed through sqlite3 command line interface. The query in question looked like this:

1
SELECT * FROM some_view WHERE (column_a=1 OR column_b=1);

Inside the Android app I was getting rows for the second part of OR clause (i.e. column_b=1), but no rows for the first part.

Quick search through Android sources yielded the clue – I wasn’t executing exactly the same query on the command line. Selection arguments are always bound as a strings, so the question marks in query string should be surrounded with quotes. So the Android app was executing the following query:

1
SELECT * FROM some_view WHERE (column_a="1" OR column_b="1");

So now we have another puzzle: why column_b=1 and column_b="1" give the same results, but the behavior is different for column_a? Let’s try to reproduce the problem:

1
2
3
4
5
6
7
8
sqlite> .mode column
sqlite> .headers on
sqlite> CREATE TABLE t (x INTEGER);
sqlite> INSERT INTO t VALUES(1);
sqlite> SELECT COUNT(*) FROM t WHERE x=1;
1
sqlite> SELECT COUNT(*) FROM t WHERE x="1";
1

So far so good, no surprises. Let’s create a view similar to the one which causes problems.

1
2
3
4
5
6
sqlite> CREATE VIEW v AS SELECT NULL AS a, x AS b FROM t UNION SELECT x, NULL FROM t;
sqlite> SELECT * FROM v;
a           b
----------  ----------
            1
1

Now let’s take a look at counts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sqlite> SELECT COUNT(*) FROM v WHERE b=1;
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM v WHERE b="1";
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM v WHERE a=1;
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM v WHERE a="1";
COUNT(*)
----------
0

Yay, we reproduced our bug. But why is this happening?

1
2
3
4
5
sqlite> PRAGMA TABLE_INFO(v);
cid         name        type        notnull     dflt_value  pk
----------  ----------  ----------  ----------  ----------  ----------
0           a                       0                       0
1           b           integer     0                       0

It seems that the lack of explicitly defined type of the first column prevents type conversion (please note that this is only my assumption based on the observations above; unfortunately the sqlite documentation doesn’t cover such cases in detail). How can we work around this issue?

1
2
3
4
5
6
sqlite> CREATE VIEW vfix AS SELECT x AS a, x AS b FROM t WHERE 1=0 UNION SELECT * FROM v;
sqlite> PRAGMA TABLE_INFO(vfix);
cid         name        type        notnull     dflt_value  pk
----------  ----------  ----------  ----------  ----------  ----------
0           a           integer     0                       0
1           b           integer     0                       0

As you can see the column types are correctly copied from the underlying table. Let’s check the counts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sqlite> SELECT COUNT(*) FROM vfix WHERE b=1;
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM vfix WHERE b="1";
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM vfix WHERE a=1;
COUNT(*)
----------
1
sqlite> SELECT COUNT(*) FROM vfix WHERE a="1";
COUNT(*)
----------
1

Looks OK. Pretty? No, but it does the job and that’s what matters at the end of the day.

QML Applications on Nokia Belle

| Comments

After the latest update of “Nu, Pogodi!”, I received few negative reviews saying that the game doesn’t work. I’ve tested the game thoroughly on all devices I was able to get my hands on, but I wasn’t able to reproduce the error, so I decided to wait until I get more info. Few days ago with the help of one customer I was able to pin down the problem – the game failed to display any UI on new Belle firmware with Qt 4.8.0. I don’t have such device myself, but fortunately the great Remote Device Access service allows testing on Nokia 808 PureView with latest Belle firmware. I’ve reproduced the error, wrote the Nokia Developers Support, and they sent me a very helpful link: Changes in Nokia Belle FP1. One issue listed there caught my eye:

If application does not specify a size for the root QML object and doesn’t use the Qt components Window as root element (Window component should not be used as a child item), it might cause the root window not to be shown.

Solution / Workaround:
Always declare a size for your custom QML root element.

I’ve checked my main QML file and indeed, I did not set the root element size, instead I’ve set the resize mode to SizeRootObjectToView and maximized the QDeclarativeView. I think it’s the better solution than setting the root element size explicitly, because the display resolution is not the same on all Nokia phones (I’m looking at you, E6). Instead of doing that, I wrapped my entire UI into Window element from Qt Components and lo and behold, my game displayed something, although it wasn’t exactly what I expected:

My code locked the screen orientation after loading main QML file, and it looked like the only thing that might cause this problem, so I changed the calls order. On Belle FP1 devices everything worked fine, but this change broke the display on devices with Anna and older Belle firmware:

Wat? The only solution I came up with was creating the utility method for detecting version of Qt during runtime and locking screen orientation after and before loading main QML file, depending on the Qt version. Relevant piece of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool Utils::runtimeQtAtLeast(int major, int minor, int bugfix)
{
    const QStringList v = QString::fromAscii(qVersion()).split(QLatin1Char('.'));
    if (v.count() == 3) {
        int runtimeVersion = v.at(0).toInt() << 16 | v.at(1).toInt() << 8 | v.at(2).toInt();
        int version = major << 16 | minor << 8 | bugfix;
        return version <= runtimeVersion;
    }
    return false;
}

// ...

const bool qt48 = Utils::runtimeQtAtLeast(4,8,0);
QmlApplicationViewer viewer;
if (qt48) {
    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationLockLandscape);
    viewer.setResizeMode(QDeclarativeView::SizeRootObjectToView);
}

viewer.setMainQmlFile(QLatin1String("qml/nupagadi/GameArea.qml"));

if (!qt48) {
    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationLockLandscape);
    viewer.setResizeMode(QDeclarativeView::SizeRootObjectToView);
}

This kind of incompatibility between minor versions of the Qt framework is mind boggling. It makes me think what else did Nokia screw up in Qt 4.8.0 for Symbian and what will they screw up in the next firmware updates. One thing is sure: I’ll have a lot of blogging material.

Git Killer Feature: Content Tracking

| Comments

At my current day job I was the first adopter of git and one of the most frequently question I’m asked as “the git guy” is the variation of “why do you use git instead of mercurial?”. I usually responded that git was the first distributed VCS I used, and I played for a while with hg and bazaar, but I haven’t discovered any significant feature that would make me change my initial choice. Today I have found the case that allows me to give a better answer.

About a month ago I released the Word Judge application – an offline word validator for Scrabble and other word games. Initially I was working only on one version of Word Judge with polish dictionary, so I had one line of code in my git repository:

When polish version was ready, I decided that I might as well release the english version, because it would require only minor changes: changing app icon and dictionary file. It turned out that I also have to change the application package, so both versions of Word Judge could be installed at the same time. The changes were in fact minor, but included a lot of renaming (package name is mirrored by the directory structure):

Later on I decided to add ads to my application. First, I’ve added the necessary code to master (i.e. polish) branch:

I switched to english branch, typed git merge master --no-commit, and was very surprised to see just a few conflicts in binary files (icons). All the changes made in java code, in the renamed files, were automatically merged. If you still don’t get how awesome this is, consider what would I had to do without it:

  1. Start the merge
  2. Resolve the conflict by selecting “keep deleted files” for every java file changed in the master branch
  3. Manually merge every java file from “pl” package and “en” package
  4. Delete “pl” package
  5. Commit changes

And I would have to do this every single time I port the changes between language branches. It’s a tedious and error-prone job, and git automatically (automagically?) does it for me. How cool is that? Mightily cool IMHO. Of course if I add some java files in one branch, I have to do move them to correct package in the other branch (hence the --no-commit switch in git command above), but most of the conflicts are caused by binary files.

It’s also more common case than you might think, i.e. you don’t have to write a dictionary supporting multiple languages to run into it. For example if you have a lite and full version of an app, you have exactly the same situation.

Right now my repository looks like this (or it would look like this if the git gui tools didn’t suck, but that’s another story):

I can’t imagine supporting such structure without automatic merges provided by git, because it tracks content instead of files. And that’s the real killer feature that distinguishes git from other DVCS.

The Dark Side of LINQ: LINQ to SQL on Windows Phone

| Comments

In case you don’t know what’s LINQ and you use C#, I suggest you drop everything you do and enlighten yourself. Be warned: when you learn LINQ, you won’t be able to work with Java collections (Guava makes them bearable, but barely) or Qt/STL containers without throwing in your mouth every now and then.

Here’s tl;dr for the non-enlightened: LINQ is a sane way to query and alter the data. Instead of this:

1
2
3
4
5
6
7
8
9
10
private void PrintSortedEvenNumbers(IList<int> unfiltered)
{
    List<int> filtered = new List<int>();
    foreach (int i in unfiltered)
        if (i % 2 == 0)
            filtered.Add(i);
    filtered.Sort();
    foreach (int i in filtered)
        Console.Write(i + " ");
}

You can just write this:

1
2
3
4
5
private void PrintSortedEvenNumbers(IList<int> unfiltered)
{
    foreach (int i in unfiltered.Where(num => num % 2 == 0).OrderBy(n => n))
        Console.Write(i + " ");
}

This is a trivial example, but the more complicated code, the more benefit you get from using LINQ.

I started using it for operations on collections and XML files and I immediately fell in love with it. Imagine my joy when I learned that Windows Phone 7.1 finally supports local relational database which can be queried through LINQ to SQL!

I’ve read the tutorial.aspx), thought a bit about the application I was writing at the time and decided that I need many-to-many relationship. Oops, that’s not supported. Well, it’s “kinda” supported, meaning you can create a data structure and insert some data, but when you remove the data the foreign key constraints won’t be verified and cascade triggers won’t work. I think I can simplify the above statement and just call it “not supported feature”.

Fortunately I didn’t absolutely had to use many-to-many relationship. It would be nicer and would allow us to relax some constraints, but the current data could be as well represented using a nested one-to-many relationships. I’ve wrote the code based on aforementioned LINQ to SQL tutorial, wrote the tests, run them and watched in amazement as they fail. After googling a lot and experimenting I was able to make my code work, but it was quite different than the crap they posted on MSDN as tutorial. If you want to use LINQ to SQL, take a look at this code on github.

I spent about two days reading about LINQ to SQL and experimenting with the code and in the end I didn’t even had the data structure I wanted to. And we’re not talking here about rocket science, the SQLite scheme I needed was something like this:

1
2
3
4
5
6
7
create table x (id INTEGER PRIMARY KEY AUTOINCREMENT, text STRING);
create table y (id INTEGER PRIMARY KEY AUTOINCREMENT, text STRING);
create table z (id INTEGER PRIMARY KEY AUTOINCREMENT,
                x_id INTEGER NOT NULL,
                y_id INTEGER NOT NULL,
                FOREIGN KEY(x_id) REFERENCES x(id) ON DELETE CASCADE,
                FOREIGN KEY(y_id) REFERENCES y(id) ON DELETE CASCADE);

Which leads me to conclusion: LINQ to SQL for Windows Phone just doesn’t work. Consider also the amount of boilerplate code I had to write for simple foreign key relation: in case you didn’t looked at the github link that’s whooping 80 lines of code for every one-to-many relationship. I don’t know, maybe there are some tools that generate this stuff for you, but in this case why does the official tutorial even mention writing the table classes by hand? And where are those tools?

Recently I was also playing with Django which also features an ORM for the model definition. You need the foreign key? You use something called ForeignKey. You need the many-to-many relationship? You use the ManyToManyField. Dirt simple. I’m sure there are some dark corners you have to be aware of, but the basic stuff just works.

Background Operations on Windows Phone 7

| Comments

Few weeks ago I was complaining to another developer that Windows Phone applications cannot perform tasks in background when they are not running. That was true few months ago when I learned about Windows Phone 7.0, but he pointed me to MSDN documentation of new WP 7.1 feature: Background Agents.aspx).

I clicked the link with my hopes up, but I was immediately shot down with the highlight on the first page: “Background agents are not supported on 256-MB devices”. I proceeded to the overview page.aspx) and it turned out the highlight from the first page was just the tip of the iceberg. The constraints listed there are just staggering.

First there are the registration issues: you can register background task for the next two weeks and after that period your application have to reschedule the task. I’m not sure why do I have to do this, and at the first glance it looks only like a minor nuisance, until you take into account two other constraints: tasks cannot reschedule themselves and there is a hard limit of scheduled periodic tasks, which can be ridiculously low. Relevant quote from MSDN:

To help maximize the battery life of the device, there is a hard limit on the number of periodic agents that can be scheduled on the phone. It varies per device configuration and can be as low as 6.

Not a minor nuisance anymore, huh?

This limit is only imposed on periodic agents, which are intended for short, periodic tasks like polling some service or uploading a data. There are also Resource Intensive Agents which can be used for longer tasks like data synchronization, but they have their own set of constraints: the device have to be charging, the battery have to be almost fully charged and there should be a Wi-Fi or PC connection (no cellular data). I think the MSDN note summarizes it quite well:

Due to the constraints on the device that must be met for resource-intensive agents to run, it is possible that the agent will never be run on a particular device.

(…)Also, resource-intensive agents are run one at a time, so as more applications that use resource-intensive agents are installed on a device, the likelihood of an agent running becomes even less. You should consider this when designing your application.

I’m going to add to the comment above my own observation: every application can register only one background agent, which can be both periodic agent and resource intensive agent. It means that if you need both types of agents, your resource intensive agent is also affected by the periodic agent hard limit.

It all boils down to this: you can’t rely on the background agents. You don’t have the guarantee that you’ll be able to register the agent, which means that you can’t use them for critical functionality. So we’re exactly where we were after 7.0 release.