Saturday, June 08, 2013

LongListSelector does not "play nicely" with a static DataContext

Here's a lesson from a day lost to debugging an obscure issue.

Microsoft recommend that you should "use the LongListSelector instead of ListBox for phone apps".

That's great advice, especially if upgrading an existing app where ListBox wass used previously. But there's a gotcha you may need to be aware of.

If a LongListSelector is bound to a static ItemsSource then a reference is held that stops the page being collected. This means that such a page would leak memory!
If it's a large or complex page or one that is opened a lot (so you end up with lots of copies in memory) this could be very bad for your app.

This doesn't happen when bound to a non-static source. It also doesn't happen when using a ListBox bound to either a static or non-static object.

If you're upgrading an app built several years ago when using a static view model seemed like a not unreasonable way to architect an app you may be more likely to hit this. (That's why I discovered the issue-when upgrading an app originally written in 2011.)


Don't believe me or want to see this in action for yourself?
I've put a project at https://github.com/mrlacey/WPMisc/tree/master/LlsStaticTest which demonstrates all the above.
It allows opening of pages which contain:
  • a ListBox bound to a static object; 
  • a ListBox bound to a non-static object; 
  • a LongListSelector bound to a static object; 
  • a LongListSelector bound to a non-static object; 
  • and a way to force garbage collection.
Each page also outputs creation, navigation and destruction information to the debug window.
To see this in action, open each page then tap "FORCE GC" twice and you should see the destructors for all but the static LongListSelector page being called.
You can also see the same by running the app through the memory profiler but I think it's easier and faster to see this problem via the debug output.

There are two ways to address this issue:
  1. Remove the use of a static view model. Depending on your application this may or may not be a viable solution but I would recommend against using a static view model whenever possible.
  2. Remove the binding when backing out of the page. It feels slightly imperfect but works and is easy to do.

To remove the binding when leaving the page, just do something like this.

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
 
    // The following removes the binding of the LLS to the VM
    // so the page can be collected
    if (e.NavigationMode == NavigationMode.Back)
    {
        this.DataContext = null;
    }
}

Note that this is done in OnNavigatedFrom and not in OnNavigatingFrom as if done before you have navigated away from the page the removal of the binding may cause the UI to update and you may see screen flicker before the page closes.

This discovery was found via a lot of experimentation, insight and a process of elimination.I couldn't find any indication of what was holding a reference via the profiler. If you know where this would be indicated (or even hinted at) I'd love to know.



2 comments:

  1. Thanks, I've got the same problem.

    I wonder that why LongListSelector has alot of bugs, :D, with me, it have at least 2 bugs :( (not happen with ListBox)

    ReplyDelete
  2. Anonymous3:51 am

    Thank you for posting this. Very helpful.

    ReplyDelete

I get a lot of comment spam :( - moderation may take a while.