Late Reader v.1.2

Posted on Jan 27, 2012 in and tagged ,

Late Reader, my Windows Phone 7 app that allows you to keep a reading list on your phone and synchronize it with ReadItLater.com account, has been updated to v.1.2.

Actually, a lot of internal tweaks were made, though the end user probably won’t even notice them. Starting from 1.2, the local reading list and article cache are stored in SQL CE database (opposed to decoupled isolated storage files in the previous versions) which slightly improved general app performance.

Also I’ve decided to stop using getfavicon.org service to retrieve article icons – it just was not fast enough (took 2-3 seconds to get a single icon) and was not able to resolve the valid favicon for many web addresses. I’ve found http://getfavicon.appspot.com/ web app to be really fast and responsive, except that it gives you an image in GIF format and WP7 doesn’t have a native GIF decoder. So instead of making the manual bit conversion magic on the phone, I put up web service that uses GDI+ tools from BCL to convert a gif file into png. It gives you a small delay for each image request, but thanks to the modern cloud platforms it is still much faster than the old solution (I used Appharbor to host my web service, if you are interested).

Another great feature that you may find useful is list searching. From the main page, you can tap the search button and start typing the name of the article you want to find.

It is always great to hear the feedback from people who use your app. Based on user’s suggestions, ‘tap mode’ option was added. It allows you to choose what happens when you tap the article in the list – you can switch between Text (open text view by default), Web (open IE with the article address) and Hybrid (tapping the article title opens text view, while tapping the site icon or web address opens the article in IE).

And the last update – I decided to display ads in the app, but the good news for those of you who can’t stand seeing ads in the mobile app is that you can turn them off in the settings. Just remember that showing ads provides a small reward for all the effort the developer put in building the app.

You can download v.1.2 here. Don’t forget to rate the app if you like it.

In the previous post I showed how to load the large SQL Server CE database on WP7. However, if you are working with a read-only reference database, deploying it to the phone or emulator may take up to several minutes. In this post I will try to explain how to save your nerves and time, making the regular development and debugging easier.

So for now, there are 2 options for deploying the database with your app:

  • Use Isolated Storage. Upload the database to the device with the help of ISETool. For ~50 mb database it takes 4 minutes on my laptop.
  • Pack it as the application content (setting Build Action to Content). In that case, the database is packed into the XAP file on your machine and then unpacked back on the device. While this approach is a bit faster then the first one, it still takes something about 1 minute to deploy your app, which I find unacceptable.

Note that the 1st option should be used only for Debug configurations. The only way to include the reference database when delivering the app to the users is by packing it into the XAP (you may, however, copy it to Isolated Storage upon the first launch, but I don’t see any sense why should you do so).

So I can’t fasten the packaging process, but I can upload my database to the Isolated Storage once and it will be preserved between the application deploys when debugging. When I need to release the application build, I will include the database to XAP file content. Sounds easy.

Being lazy motivates me to script the uploading routine. Here is a batch file that takes the IS snapshot with ISETool, adds the database file and restores the snapshot on the emulator:

set isePath="C:Program Files (x86)Microsoft SDKsWindows Phonev7.1ToolsIsolatedStorageExplorerToolISETool.exe"
set tempPath="C:TempDatabaseIS"
set dbPath="your_db_path"
set appId=your_app_id
%isePath% ts de %appId% %tempPath%
copy %dbPath% %tempPath%
%isePath% rs de %appId% %tempPath%

In the snippet above you need to replace your_db_path with the path to your .sdf file (relative to the script location) and your_app_id with Product Id of your application (you can find it WMAppManifest.xml file in the Properties subfolder of your project).

You will need another script file that will copy database to the emulator. The only difference is that you will use “xd” argument instead of “de” in lines 8 and 10.

Not hard so far, right?

Now we need to adjust the connection string, so that it points to the isolated storage in DEBUG configuration and to the content file otherwise.

#if DEBUG
    var source = "isostore:test.sdf";
#else
    var source = "appdata:/test.sdf";
#endif
    var connectionString = String.Format(
        "Data Source='{0}'; Max Database Size = 60; File Mode= Read Only;",
        source);

The last step – configuring the project file so the database file is not packed into XAP in DEBUG mode. Unfortunately, Visual Studio doesn’t allow you to specify conditions for files in the project, so you have to modify the project file manually.

Add the database file to the project and set its Build Action to Content.

Locate database file and change its build action

Save you solution, right-click the project file and select Unload Project, then right-click again and select Edit %ProjectName%.csproj.

Locate “test.sdf” string in the project file:

Change build condition for the database file

Add the Condition attribute the the Content node, so it looks like:

<Content Include="test.sdf" Condition="'$(Configuration)' == 'Release'">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Here we specify that file test.sdf should be included in the project as Content only when the project configuration is set to ‘Release’.

Now you can reload the project in Visual Studio. You won’t see any changes in the Solution Explorer, but try building the solution – the database will be added to XAP only in Release configuration. Don’t forget to re-launch the deployment scripts every time you uninstall the application or debug it on the new phone.

Recently I ran into an issue when trying to load the large database (approx. 50 mb) from isolated storage on WP7. Whenever I tried to query the Data Context, I got the following SqlCeException:

The database file is larger than the configured maximum database size. This setting takes effect on the first concurrent database connection only. [ Required Max Database Size (in MB; 0 if unknown) = 0 ]

After some searching over the web, I found this blog post covering some details of that parameter.

The general work-around is adding ‘Max Database Size’ value (in megabytes) to the connection string, so it looks like:

var connectionString = "Data Source='isostore:DB.sdf'; Max Database Size = 60";

Now your app should be able to init the data context.

Building cool animated tiles with Rx

Posted on Nov 15, 2011 in and tagged ,

I have a small WP7 project called Summarize. It is a fun math-based game (check it out for free).

It has a nice effect of loading tiles. Several people has asked me how I have done it, so in this post I will cover how to build something similar on your own.

Let’s start with building the UI. We have the toolkit’s WrapPanel inside the ItemsControl and a simple TileControl with TextBlock displaying the tile number.

TileControl.xaml markup:

<UserControl 
    x:Name="UserControl"
    x:Class="AnimatedTilesSample.TileControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    Loaded="OnLoaded"
    Width="76" Height="76">
    
    <UserControl.Projection>
        <PlaneProjection RotationY="-180"/>
    </UserControl.Projection>
    
    <UserControl.Resources>
        <Storyboard x:Key="LoadedStoryboard">
            <DoubleAnimation 
                Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                Storyboard.TargetName="UserControl"
                To="0"
                Duration="0:0:0.2"/>
            <ObjectAnimationUsingKeyFrames
                Storyboard.TargetProperty="UIElement.Visibility"
                Storyboard.TargetName="Text">
                <DiscreteObjectKeyFrame 
                    KeyTime="0:0:0.1">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>
    
    <Grid 
        x:Name="LayoutRoot"
        Background="{StaticResource PhoneChromeBrush}">
        <TextBlock 
            x:Name="Text"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Visibility="Collapsed"
            Style="{StaticResource PhoneTextTitle2Style}"
            Text="{Binding ., Mode=OneTime}"/>
    </Grid>
</UserControl>

In the code-behind we start the animation when the tile is loaded:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var sb = Resources["LoadedStoryboard"] as Storyboard;
    if (sb != null)
    {
        sb.Begin();
    }
}

MainPage.xaml markup:

<phone:PhoneApplicationPage 
    x:Class="AnimatedTilesSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    xmlns:local="clr-namespace:AnimatedTilesSample"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait" >
     <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid x:Name="ContentPanel" Margin="12,0,12,0">
            <ItemsControl
                x:Name="Cells">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <toolkit:WrapPanel
                            Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:TileControl Margin="2"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
            
            <Button
                HorizontalAlignment="Left"
                VerticalAlignment="Bottom"
                x:Name="PlayAnimationButton"
                Content="Play animation"
                Click="PlayAnimationHandler"/>
        </Grid>
    </Grid>
 
</phone:PhoneApplicationPage>

Clicking the button generates the observable sequence producing values every 80 milliseconds. We use only the first 25 values and when the every next value is available, the appropriate tile is created and added to the list. Adding the tile starts its ‘loaded’ animation.

private void PlayAnimationHandler(object sender, RoutedEventArgs e)
{
    Cells.Items.Clear();
    PlayAnimationButton.IsEnabled = false;


    Observable.Interval(TimeSpan.FromMilliseconds(80))
        .Take(25)
        .ObserveOnDispatcher()
        .Finally(() =>
        {
            PlayAnimationButton.IsEnabled = true;
        })
        .Subscribe(x => Cells.Items.Add(x));
}

Because changes to ObservableCollection should occur only on the thread that it was created on, I used DispatcherObservable.ObserveOnDispatcher extension method that schedules all observer actions on the runtime dispatcher.

You can download sample app with sources here.

If you have changed the marketplace region as I described in the previous post and now you try updating applications that were installed in the old region, you are probably going to get the next error:

“We’re having a problem with this app or game because it no longer appears in your purchase history. This can happen if you’ve recently switched the gamertag associated with your Windows Live ID. We’re sorry, but you’ll need to buy this app or game again to continue using it.”

And now this application is stuck on your phone, you won’t be able neither to update nor to delete it.

The only work-around I’ve found so far is reinstalling the application through the web marketplace. So go to windowsphone.com/marketplace, search for your app and hit ‘Reinstall this app’ button.

When I created Zune account for my brand new WP7 device, I selected the US region – mine was not supported at that moment. Few months later, tired from not being able to buy any apps or games (my card was not accepted by the US marketplace), I decided to switch to my local region. Thankfully, Microsoft has added support for new markets prior to the Mango update.

The process if migrating your account is not so obvious. You won’t find anything related at Zune or Live websites. That can only by done through the Xbox account management (in case you didn’t know – your account is shared between Xbox Live and Zune services).

So go to your account page at https://live.xbox.com/en-US/Account, find the Manage Account Region link.

Manage account region

You will be noted that you can change the region once in 12 months and that you will not be able to re-download all your Xbox Live or Zune content, however it will not be deleted from your devices, so you can back it up.

Read that information carefully and ensure that you understand the consequences. Click Next and select the new region, then keep moving through the confirmation pages.

If everything is fine, your account will be switched to another region. Note that it may take some time to update your Zune account.