Tuesday, February 28, 2012

WP7 WebRequest oddity

During testing of a Windows Phone 7 application I write during my day job I noticed strange thing: when I disabled network connection, I received 404 NotFound error. More precisely, the WebRequest threw WebException with Status = UnknownError, Response.Uri = {} and Response.StatusCode = NotFound.

So you get 404 in two cases: when either connection endpoint is down or when server actually responds with 404 NotFound. It would be nice to separate those two cases though, for example to display to the user the message that actually helps them fix the problem.


Fortunately you can tell those two situations apart by checking WebException.Response.ResponseUri - in case of connection failure it contains empty Uri object (not null, just empty). Here's the extension method I use to convert the exception to the one that makes more sense to me :
public static WebResponse SaneEndGetResponse(this WebRequest request, IAsyncResult asyncResult)
{
    try
    {
        return request.EndGetResponse(asyncResult);
    }
    catch (WebException wex)
    {
        if (wex.Response != null &&
            ((HttpWebResponse)wex.Response).StatusCode == HttpStatusCode.NotFound &&
            wex.Response.ResponseUri != null &&
            String.IsNullOrEmpty(wex.Response.ResponseUri.ToString()))
        {
            throw new WebException("Network error", WebExceptionStatus.ConnectFailure);
        }
        throw;
    }
}

Friday, February 24, 2012

"Nu, Pogodi!" bought by over 2000 users

This week my "Nu, Pogodi!" game reached the 2000 downloads from Nokia Store. It might not be a lot if you compare it to the number of Angry Birds downloads, but considering that a) the game is paid, i.e. those 2000 Nokia phone users actually spent their money on my game, and b) it took about two weeks total to program it, I think it's a very good result.

Despite the fact that the game is very simple, I've learned a lot while writing and publishing it, which makes me very excited about the next, more complex programs I'm going to release and gives me enormous motivation boost. Now I just need to get to keep working before it wears off.

Thursday, February 23, 2012

Nokia Belle update and content published in Nokia Store

I've received an email from "Nu, Pogodi!" customer, saying that he cannot download my game from Nokia Store after updating his N8 phone firmware to Symbian Belle. After quick investigation in turned out that the N8 Belle device was listed as "Not compatible" in content distribution details.

When I published my game in November, the only devices running with Belle firmware were Nokia 603, 700 and 701. When the Belle update for various phones was released, the new firmwares were added to the list as "Not compatible", because that's the only reasonable default. It's prudent to prevent users from downloading application that might not work, because it's very likely that in case of any errors they'll post a negative review, and it's very hard to bump your rating from 1 or 2 stars average. I verified that everything works fine and updated the distribution metadata.

So here's the piece of advice for anyone who published some content in Nokia Store: periodically check if there are new firmwares available and update your content distribution.

Wednesday, February 22, 2012

Android LayoutInflater gotcha

Many custom Adapter tutorials contain subtle error which can be hard to find and fix. Even efficient list adapter sample from Android SDK contains this bug. If you compile and run the sample without any changes you should see something like this:


There's nothing wrong with the list on this screenshot, as long as that's the look you want. But what if you want the list items to be wider? Let's change the list item layout:

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="150dip">

    <ImageView android:id="@+id/icon"
        android:layout_width="48dip"
        android:layout_height="48dip" />

    <TextView android:id="@+id/text"
        android:layout_gravity="center_vertical"
        android:layout_width="0dip"
        android:layout_weight="1.0"
        android:layout_height="wrap_content" />

</LinearLayout>

What's changed? Nothing, nada, zilch, zip. No changes whatsoever:


You can use hierarchy viewer tool to verify that the list item height was not set correctly.


There's obviously nothing wrong with the layout xml, so let's take a look at the code:

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.view;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.ImageView;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import com.example.android.apis.R;

/**
 * Demonstrates how to write an efficient list adapter. The adapter used in this example binds
 * to an ImageView and to a TextView for each row in the list.
 *
 * To work efficiently the adapter implemented here uses two techniques:
 * - It reuses the convertView passed to getView() to avoid inflating View when it is not necessary
 * - It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary
 *
 * The ViewHolder pattern consists in storing a data structure in the tag of the view returned by
 * getView(). This data structures contains references to the views we want to bind data to, thus
 * avoiding calls to findViewById() every time getView() is invoked.
 */
public class List14 extends ListActivity {

    private static class EfficientAdapter extends BaseAdapter {
        private LayoutInflater mInflater;
        private Bitmap mIcon1;
        private Bitmap mIcon2;

        public EfficientAdapter(Context context) {
            // Cache the LayoutInflate to avoid asking for a new one each time.
            mInflater = LayoutInflater.from(context);

            // Icons bound to the rows.
            mIcon1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon48x48_1);
            mIcon2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon48x48_2);
        }

        /**
         * The number of items in the list is determined by the number of speeches
         * in our array.
         *
         * @see android.widget.ListAdapter#getCount()
         */
        public int getCount() {
            return DATA.length;
        }

        /**
         * Since the data comes from an array, just returning the index is
         * sufficent to get at the data. If we were using a more complex data
         * structure, we would return whatever object represents one row in the
         * list.
         *
         * @see android.widget.ListAdapter#getItem(int)
         */
        public Object getItem(int position) {
            return position;
        }

        /**
         * Use the array index as a unique id.
         *
         * @see android.widget.ListAdapter#getItemId(int)
         */
        public long getItemId(int position) {
            return position;
        }

        /**
         * Make a view to hold each row.
         *
         * @see android.widget.ListAdapter#getView(int, android.view.View,
         *      android.view.ViewGroup)
         */
        public View getView(int position, View convertView, ViewGroup parent) {
            // A ViewHolder keeps references to children views to avoid unneccessary calls
            // to findViewById() on each row.
            ViewHolder holder;

            // When convertView is not null, we can reuse it directly, there is no need
            // to reinflate it. We only inflate a new View when the convertView supplied
            // by ListView is null.
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, null);

                // Creates a ViewHolder and store references to the two children views
                // we want to bind data to.
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.text);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);

                convertView.setTag(holder);
            } else {
                // Get the ViewHolder back to get fast access to the TextView
                // and the ImageView.
                holder = (ViewHolder) convertView.getTag();
            }

            // Bind the data efficiently with the holder.
            holder.text.setText(DATA[position]);
            holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);

            return convertView;
        }

        static class ViewHolder {
            TextView text;
            ImageView icon;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(new EfficientAdapter(this));
    }

    private static final String[] DATA = Cheeses.sCheeseStrings;
}

I've highlighted the problematic line. It turns out that you have to use another overload of LayoutInflater.inflate method:

convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);

We set the attachToRoot to false, because we just want to properly intialize LayoutParams for the LinearLayout of the item and let the ListView to add the inflated views wherever it needs. In fact, setting it to true causes exception to be thrown from AdapterView:

java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView
    at android.widget.AdapterView.addView(AdapterView.java:461)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:416)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
    at com.fu.InflaterBugActivity$EfficientAdapter.getView(InflaterBugActivity.java:77)
    at android.widget.AbsListView.obtainView(AbsListView.java:1430)
    at android.widget.ListView.makeAndAddView(ListView.java:1745)
    at android.widget.ListView.fillDown(ListView.java:670)
    at android.widget.ListView.fillFromTop(ListView.java:727)
    at android.widget.ListView.layoutChildren(ListView.java:1598)
    at android.widget.AbsListView.onLayout(AbsListView.java:1260)
    at android.view.View.layout(View.java:7175)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
    at android.view.View.layout(View.java:7175)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1254)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1130)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1047)
    at android.view.View.layout(View.java:7175)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
    at android.view.View.layout(View.java:7175)
    at android.view.ViewRoot.performTraversals(ViewRoot.java:1140)
    at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:123)
    at android.app.ActivityThread.main(ActivityThread.java:3683)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    at dalvik.system.NativeStart.main(Native Method)
Here's the application screenshot after the change:

Ugly, but that's exactly what we should get after setting layout height to 150dip.

Sunday, February 19, 2012

On CachePolicy, UriBuilder and mobile .NET

In the Windows Phone 7 application I was working on there was a problem with some WebRequest caching. I'm not sure if it should have been fixed on the client side or server side, but since it's just a matter of setting proper CachePolicy property of WebRequest I was going to add it to mobile application.

Imagine my surprise when I found out that CachePolicy is not supported on Windows Phone 7. The obvious workaround is adding an URI param "no-cache=MS_FROM_EPOCH" or something like that.

Now, if you'd ask me what is the cardinal example of stuff that shouldn't be done using string concatenation, but is notoriously done that way, I'd answer: URI building. It's highly structured string with RFC, so it stands to reason to create and use a dedicated builder and parsers API to make sure everything is well-formed.

Let's take a look at UriBuilder class and URIs in general. All the stuff between ? and # (VERIFY) is called query string, which contains key-value pairs separated by &s. What kind of API for query string would make sense? An IDictionary<String, String>! What API is presented by UriBuilder? A string! Take a look at the documentation. Here's the best part:

Note Do not append a string directly to this property. If the length of Query is greater than 1, retrieve the property value as a string, remove the leading question mark, append the new query string, and set the property with the combined string.

Ridiculous. Seriously, MS, I don't care it's transformed to a string, it's a key-value dictionary, so let me treat it as such!

Fortunately C# has this nice feature called extension methods, meaning we can "add" a method to a class. There are no extension properties, but we can have a method like this:

UriBuilder WithQueryParam(this UriBuilder uri, String key, String value)

But now we need to check for duplicate keys and for that we have to parse existing query string. Fortunately there is a method for this: HttpUtility.ParseQueryString.

Except it's not available on mobile .NET. Again. But why?

The same thing irritate me when I worked on Blackberry apps and I had to put up with Java ME no String.format, no date handling methods, no generics and sane collections. But I understand that Java ME is supposed to work on wider range of devices, some of them with very limited CPU and RAM resources, so every cycle and every byte of memory matters.

Now let's take a look at Windows Phone 7 hardware requirements for manufacturers: 256MB of RAM and 800MHz CPU. I'm not an .NET expert, but I'd hazard a guess that supporting  full desktop .NET would not make any difference on those powerhouses. Hell, you should be able to send a man to the Moon using this hardware, so please, can I have a query string parser?

Is it some kind of Windows Mobile 6 legacy, or just a very bad MS joke that non-MS people don't get?

Thursday, February 16, 2012

The quest for paramerized converter for Windows Phone 7

Warning: this is going to be a long post, because I want to provide full background for the issues I found. Also, none of the code posted here works in 100% of cases.

Recently I was involved in refactoring a program written by interns, which included an over engineered and messed up solution for application settings. I had plenty of time and despite of the messed up implementation the original idea might be useful for any form filled by user, so instead of scraping the whole thing I decided to clean it up.

Every type of setting is represented by different class derived from common base. The model of settings consist of a collection of base class objects. That collection is bound to ItemsSource property of ListBox and instead of single data template I use template selector, which creates different UI controls for different setting type.

There is a BooleanSetting, which is represented by a single ToggleSwitch from Silverlight from Windows Phone Toolkit. There is a ClosedListSetting, represented by ListPicker, which is used for a setting where user can choose one of many options from a predefined list (hence the name). Finally there is a OpenListSetting, which is just like a ClosedListSetting, except there is the "Other" option which allows user to enter any value. On web forms such field is usually represented as a radio button group with an input box beside the "Other" option.

For UI consistency I wanted to represent the OpenListSetting using ListPicker, and when the user would select the "Other" option, additional entry field will appear. It seemed really easy, I thought I'll just bind the visibility of the custom entry field to ListPicker's SelectedIndex property with a value converter.

The first complication is that the position of custom option might be different for a different settings - usually it's the last one, so it depends on the number of predefined options; for maximum flexibility it should be possible to have many custom options in one OpenListSetting. The obvious solution is to bind some value to converter's parameter property.

Quick search on Google reveals the major flaw in that plan, to wit, you cannot use the binding for converter parameter. There is a Multibinding, which sounds like a likely solution (after all I want to bind visibility to two properties and an operation between those properties), but apparently it's not available in Silverlight for Windows Phone 7. Yay, it's Java ME crap all over again!

The suggested workaround is creating a converter as a non-visual FrameworkElement, with parameter as a dependency property, which can be used in binding. I created a base class:

public abstract class ConverterWithBindingParameter<T, TConverter> : FrameworkElement, IValueConverter
{
    public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register(
        "BindingParameter",
        typeof(T),
        typeof(TConverter),
        null);

    public T BindingParameter
    {
        get { return (T)GetValue(ParameterProperty); }
        set { SetValue(ParameterProperty, value); }
    }

    abstract public object Convert(object value, Type targetType, object parameter, CultureInfo culture);
    abstract public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
And then derived the concrete converter for a problem at hand:
public class EqualToVisibilityConverter : ConverterWithBindingParameter<int, EqualToVisibilityConverter>
{
    override public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value.Equals(BindingParameter)) ? Visibility.Visible : Visibility.Collapsed;
    }

    override public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Edit XAML, rebuild, run, kaboom:
System.ArgumentException was unhandled
    StackTrace:
    at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)
    at System.Reflection.RuntimePropertyInfo.InternalSetValue(PropertyInfo thisProperty, Object obj, Object value, Object[] index, StackCrawlMark& stackMark)
    at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
    at MS.Internal.XamlMemberInfo.SetValue(Object target, Object value)
    at MS.Internal.XamlManagedRuntimeRPInvokes.SetValue(XamlTypeToken inType, XamlQualifiedObject& inObj, XamlPropertyToken inProperty, XamlQualifiedObject& inValue)
    at MS.Internal.XcpImports.MeasureOverrideNative(IntPtr element, Single inWidth, Single inHeight, Single& outWidth, Single& outHeight)
    at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
    at MS.Internal.XcpImports.Measure_WithDesiredSizeNative(IntPtr element, Single inWidth, Single inHeight, Single& outWidth, Single& outHeight)
    at MS.Internal.XcpImports.UIElement_Measure_WithDesiredSize(UIElement element, Size availableSize)
    at System.Windows.UIElement.Measure_WithDesiredSize(Size availableSize)
    at System.Windows.Controls.VirtualizingStackPanel.MeasureChild(UIElement child, Size layoutSlotSize)
    at System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
    at MS.Internal.XcpImports.MeasureOverrideNative(IntPtr element, Single inWidth, Single inHeight, Single& outWidth, Single& outHeight)
    at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
    at System.Windows.Controls.ScrollContentPresenter.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
    at MS.Internal.XcpImports.MeasureNative(IntPtr element, Single inWidth, Single inHeight)
    at MS.Internal.XcpImports.UIElement_Measure(UIElement element, Size availableSize)
    at System.Windows.UIElement.Measure(Size availableSize)
    at System.Windows.Controls.ScrollViewer.MeasureOverride(Size constraint)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
    at MS.Internal.XcpImports.MeasureOverrideNative(IntPtr element, Single inWidth, Single inHeight, Single& outWidth, Single& outHeight)
    at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
    at MS.Internal.XcpImports.MeasureOverrideNative(IntPtr element, Single inWidth, Single inHeight, Single& outWidth, Single& outHeight)
    at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
    at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
On a hunch I created non-generic version of my converter and it seemed to work. Oh, so I can't use generic base for UI controls? Nice job MS.
public class EqualToVisibilityConverter : FrameworkElement, IValueConverter
{
    public static readonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached(
        "BindingParameter",
        typeof(int),
        typeof(EqualToVisibilityConverter),
        new PropertyMetadata(0));

    private static void OnBindingParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }

    public int BindingParameter
    {
        get { return (int)GetValue(ParameterProperty); }
        set { SetValue(ParameterProperty, value); }
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int bp = BindingParameter;
        return (value.Equals(BindingParameter)) ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Note that I wrote "seemed to work". It means only that the code didn't explode in my face, but it didn't work quite as I expected. Here's the quick breakdown: when the visibility converter is called for the first time, the bound parameter is null. Then the dependency value is changed (at least the callback is called; curiously, the setter is not) and the bound parameter is initialized with a proper value, but there is no way to invalidate the conversion result.

That gave me another idea: why use IValueConverter interface at all? Let's create an UI element with 2 dependency properties for arguments and one property for result of binary operation:
public class EqualToVisibilityConverter : FrameworkElement, INotifyPropertyChanged
{
    public static readonly DependencyProperty LeftArgumentProperty = DependencyProperty.Register(
        "LeftArgument",
        typeof(int),
        typeof(EqualToVisibilityConverter),
        new PropertyMetadata(OnArgumentChanged)
        );

    public static readonly DependencyProperty RightArgumentProperty = DependencyProperty.Register(
        "RightArgument",
        typeof(Visibility),
        typeof(EqualToVisibilityConverter),
        new PropertyMetadata(OnArgumentChanged)
        );

    private static void OnArgumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        EqualToVisibilityConverter converter = d as EqualToVisibilityConverter;
        converter.Result = (converter.LeftArgument == converter.RightArgument) ? Visibility.Visible : Visibility.Collapsed;
    }

    public int LeftArgument
    {
        get { return (int)GetValue(LeftArgumentProperty); }
        set { SetValue(LeftArgumentProperty, value); }
    }

    public int RightArgument
    {
        get { return (int)GetValue(RightArgumentProperty); }
        set { SetValue(RightArgumentProperty, value); }
    }

    private Visibility _result;
    public Visibility Result
    {
        get { return _result; }
        set
        {
            if (value != _result)
            {
                _result = value;
                OnPropertyChanged("Result");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (null != PropertyChanged)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Now the callbacks of dependency properties can trigger the result recalculation and everything will be fine and dandy. Edit XAML, rebuild, run, KABOOM! This time application blew up, but I didn't even get a call stack, it just closed without any message.

This. Is. Fucking. Ridiculous.

At this moment I decided I already lost enough time on this crap, so I just said "screw it" and added additional property to OpenListSetting class.

This is when I finally got why the MVVM pattern got so much traction with XAML developers. I always assumed that it's supposed to be used for big model adaptations, and all minor stuff should be done in binding converters. However it seems that you have to use ViewModel for anything remotely useful. Don't get me wrong: I agree that preventing pollution of Model by stuff needed by View is a Good Thing TM, but I think a separation of View into View and ViewModel is necessary only because of limitations of XAML.

Thursday, February 2, 2012

MessageBox crash on Windows Phone 7

Guess what's wrong with this Application Bar button handler:
private void OnAboutButtonClick(object sender, EventArgs e)
{
    MessageBox.Show("Blah blah blah, our app v1.0");
}
If you answered "you're using hardcoded string instead of application resource" you are of course right, but that's not the worst problem with it, so no cookie for you. If the user clicks the application bar button twice before the message box is shown it crashes:
System.Exception was unhandled
  Message=0x8000ffff
  StackTrace:
    at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
    at MS.Internal.XcpImports.MessageBox_ShowCore(String messageBoxText, String caption, UInt32 type)
    at System.Windows.MessageBox.ShowCore(String messageBoxText, String caption, MessageBoxButton button)
    at System.Windows.MessageBox.Show(String messageBoxText)
    at PhoneApp1.MainPage.OnAboutButtonClick(Object sender, EventArgs e)
    at Microsoft.Phone.Shell.ApplicationBarItemContainer.FireEventHandler(EventHandler handler, Object sender, EventArgs args)
    at Microsoft.Phone.Shell.ApplicationBarIconButton.ClickEvent()
    at Microsoft.Phone.Shell.ApplicationBarIconButtonContainer.ClickEvent()
    at Microsoft.Phone.Shell.ApplicationBar.OnCommand(UInt32 idCommand)
    at Microsoft.Phone.Shell.Interop.NativeCallbackInteropWrapper.OnCommand(UInt32 idCommand)
    at MS.Internal.XcpImports.MessageBox_ShowCoreNative(IntPtr context, String messageBoxText, String caption, UInt32 type, Int32& result)
    at MS.Internal.XcpImports.MessageBox_ShowCore(String messageBoxText, String caption, UInt32 type)
    at System.Windows.MessageBox.ShowCore(String messageBoxText, String caption, MessageBoxButton button)
    at System.Windows.MessageBox.Show(String messageBoxText)
    at PhoneApp1.MainPage.OnAboutButtonClick(Object sender, EventArgs e)
    at Microsoft.Phone.Shell.ApplicationBarItemContainer.FireEventHandler(EventHandler handler, Object sender, EventArgs args)
    at Microsoft.Phone.Shell.ApplicationBarIconButton.ClickEvent()
    at Microsoft.Phone.Shell.ApplicationBarIconButtonContainer.ClickEvent()
    at Microsoft.Phone.Shell.ApplicationBar.OnCommand(UInt32 idCommand)
    at Microsoft.Phone.Shell.Interop.NativeCallbackInteropWrapper.OnCommand(UInt32 idCommand)
The interesting thing about this stack trace is that it contains two calls to MessageBox.Show(). WTF? Since MessageBox.Show() returns the value, I assumed it's a synchronous call, i.e. no stuff would happen until the user clicks "OK". Apparently MessageBox opens internal event loop (which kind of makes sense, since something has to handle the user clicking "OK"), which handles the second application bar click, tries to open the second message box and the whole application blows up.

Now, you may say "It's only a problem if the user intentionally bangs the poor phone like an ADD afflicted monkey on speed". I say "Fix your god damn app" (and in case anyone from Microsoft would ever read this, I also say "Fix your god damn framework").

So what can we do? Using lock is out of the question, since everything is executed in the same thread. So the only option I came up with is a Crappy Boolean Flag Pattern:
private bool _msgboxShown = false;
private void OnAboutButtonClick(object sender, EventArgs e)
{
    if (!_msgboxShown)
    {
        _msgboxShown = true;
        MessageBox.Show("Blah blah blah, our app v1.0");
        _msgboxShown = false;
    }
}
Not elegant, but it works. Of course if you use message boxes for other stuff, create some helper class for this crap.

Wednesday, February 1, 2012

Handling QML errors 101

Qt SDK comes with great documentation and examples, but there is hardly any information about handling QML errors. All examples assume that nothing can go wrong during runtime, which is a bit naive approach. Even if your app is completely bug free, the users will find a way to shoot themselves in the foot. They will install your app and then uninstall key plugins ignoring all warnings from system. They will install your app on SD card and then delete some files for some unfathomable reason. And then they will blame you.

Let's consider what happens if you have an error in your main QML file? User sees the black screen, gets pissed off, uninstalls your app and gives it a negative review in Nokia store. Not good. Let's try something different:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);   
    QDeclarativeView canvas;

    canvas.setSource(QLatin1String("main.qml"));
    if (!canvas.errors().empty()) {
        // handle errors
        QMessageBox msgBox;
        msgBox.setText("Uh oh, something went terribly wrong!");
        msgBox.setInformativeText(
            "We're sorry, but it seems there "
            "are some problems with running "
            "our application on your phone.");
        msgBox.exec();

        return -1;
    }

    // optionally set the screen orientation
    // call show/showFullscreen/showMaximized

    return app.exec();
}

Few notes about the snippet above:

  • If you're using the QML Application template from the recent version of Qt Creator you probably have a generated QmlApplicationViewer class instead of raw QDeclarativeView, but the general idea stays the same.
  • Error handling section above is only a stub. You may ask user to reinstall the application. You may display more information about the error along with some contact information. If your application uses network connection, you should probably ask user if he wants to send an error report.
  • If you use the QMessageBox or other modal dialog and lock the screen orientation, you mustn't lock the orientation before showing the dialog. In current version of Qt (4.7.4) there is a bug and in case of forced orientation change only dialog buttons are displayed.