Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a relatively simple download manager in a Windows Phone 7 application

DZone's Guide to

Building a relatively simple download manager in a Windows Phone 7 application

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Downloads inside a Windows Phone 7 applications are not something unusual. A lot of applications download all sorts of data all the time - XML content, videos, music, images - you name it. Most of those are organized to be running in the background so the user is not aware of what is going on. However, in some cases it is necessary to show how a download process progresses. That's where a simple download manager would work perfectly.

NOTE: I am using MVVM Light here to bind to a basic ViewModel that contains download data. You can download the MVVM libraries (that are required) here.

So here is how I did it.

First of all I created a Silverlight Windows Phone application. It really doesn't matter what application type you are using (unless, of course, you decide to go with XNA) - it all revolves around the code-behind and a ListBox control. Ultimately, I wanted to achieve something like this:

Easy, right? Let's see at the basic architecture for something like this.

  • ModelLocator.cs - a helper class used to locate ViewModels inside my application. It is based on the structure outlined in the default sample provided by Laurent Bugnion for MVVM Light.
  • HomePageVideModel.cs - the ViewModel I am binding to. It declares the main download collection, among some other helper elements.
  • Download.cs - a class that is able to organize downloads and process the queue.

You probably noticed that I was talking about a queue here. Indeed, it is irrational to start all downloads at once, especially if the downloaded content is pretty big. That's where you need to make sure that only a limited number of concurrent downloads are up and running.

Here is the layout for the download manager page, where the ListBox I am using is located:

<Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="DOWNLOADS" Style="{StaticResource PhoneTextNormalStyle}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox x:Name="dlList" ItemsSource="{Binding Downloads}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <TextBlock Margin="10,0,20,0" FontSize="24" Text="{Binding Name}"></TextBlock>
                            <ProgressBar Margin="0,40,60,0" Width="400" Style="{StaticResource ProgressBarStyle1}" Value="{Binding Progress}"></ProgressBar>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>

I am only using a TextBlock and a ProgressBar in the item template. Of course, this can be "tuned" to show the download speed and such. For demonstration purposes, however, this is going to be enough. After all, I am talking about a very general implementation.

You can see that I am binding multiple properties here. These are contained by the HomePageViewModel class, mentioned above. The page itself is bound to it by having the DataContext set to connect to the static class instance:

DataContext="{Binding BoundHomeModel, Source={StaticResource Locator}}"

The HomePageVideModel class is very simple:

    public class HomePageViewModel : ViewModelBase
    {
        private ObservableCollection<Download> _downloads;
        public ObservableCollection<Download> Downloads
        {
            get
            {
                return _downloads;
            }
            set
            {
                if (_downloads != value)
                {
                    _downloads = value;
                    RaisePropertyChanged("Downloads");
                }
            }
        }

        public Download CurrentDownload { get; set; }
    }

Here, I am using the ViewModelBase as the base class in order to use the RaisePropertyChanged method. That way, it is easier to bind properties to elements where the property will be updated and this should be reflected by the element's state.

The ModelLocator class is also pretty simple:

public class ModelLocator
{
    private static HomePageViewModel _home;

    public ModelLocator()
    {
        HomeStatic.Downloads = new System.Collections.ObjectModel.ObservableCollection<Models.Download>();
    }

    public static HomePageViewModel HomeStatic
        {
            get
            {
                if (_home == null)
                {
                    CreateHome();
                }

                return _home;
            }
        }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
        "CA1822:MarkMembersAsStatic",
        Justification = "This non-static member is needed for data binding purposes.")]
    public HomePageViewModel BoundHomeModel
        {
            get
            {
                return HomeStatic;
            }
        }

    public static void CreateHome()
        {
            if (_home == null)
            {
                _home = new HomePageViewModel();
            }
        }

    public static void Cleanup()
    {

    }
}

When the ModelLocator is first initialized, I am also instantiating the list of downloads in the HomePageViewModel. That collection is, in fact, the download queue - it keeps multiple instances of the Download model. Instead of simply keeping URLs there, I decided that it would be easier to keep custom instances of a class that will be able to report its activity and at the same time keep all necessary data. So I came up with this plan for the Download model:

  • A constructor with a Uri instance that sets the download location.
  • A DownloadState property that will determine whether the download is in the queue or is active.
  • The URL property that will be received from the above mentioned constructor.
  • A Name property that will define the name of the download, so that the user easily knows what is being downloaded for a specific element in the list.
  • A Progress property, that will ultimately be binded to the ProgressBar control, showing the download progress.
  • A StartDownload method, that will initiate the download process for its own instance.

For the sake of the demo, here is the entire Download class:

public class Download : ViewModelBase
{
    IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();

    public Download(Uri url)
    {
        URL = url;
        TotalLength = -1;
    }

    private DownloadState _state;
    public DownloadState State
    {
        get
        {
            return _state;
        }
        set
        {
            if (_state != value)
            {
                _state = value;
                RaisePropertyChanged("State");
            }
        }
    }

    public Uri URL { get; private set; }
    public string Name { get; set; }

    private int _progress;
    public int Progress
    {
        get
        {
            return _progress;
        }
        set
        {
            if (_progress != value)
            {
                _progress = value;
                RaisePropertyChanged("Progress");
            }
        }
    }

    private long TotalLength { get; set; }

    IsolatedStorageFileStream streamToWriteTo;
    HttpWebRequest request;
    public void StartDownload()
    {
        streamToWriteTo = new IsolatedStorageFileStream(FilenameFormatter.Format(Name) + ".wmv", FileMode.Create, file);
        request = (HttpWebRequest)WebRequest.Create(URL);
        request.AllowReadStreamBuffering = false;
        request.BeginGetResponse(new AsyncCallback(GetData), request);
    }

    
    void GetData(IAsyncResult result)
    {
        HttpWebRequest request = (HttpWebRequest)result.AsyncState;
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
        Stream rStream = response.GetResponseStream();

        IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();

        byte[] data = new byte[16 * 1024];
        int read;

        long totalValue = response.ContentLength;
        long sum = 0;

        while ((read = rStream.Read(data, 0, data.Length)) > 0)
        {
            sum += read;

            App.DownloadDispatcher.BeginInvoke(new Action(() => Progress = (int)((sum * 100) / totalValue)));

            streamToWriteTo.Write(data, 0, read);
        }
        streamToWriteTo.Close();
        streamToWriteTo.Dispose();

        if (ModelLocator.HomeStatic.Downloads.Count != 0)
        {
            foreach (Download d in ModelLocator.HomeStatic.Downloads)
            {
                if (d.State == DownloadState.Pending)
                {
                    d.State = DownloadState.InProgress;
                    d.StartDownload();
                    break;
                }
            }
        }

        App.DownloadDispatcher.BeginInvoke(new Action(() => ModelLocator.HomeStatic.Downloads.Remove(this)));
    }

}

And here is the explanation of each and every part of it. The first thing I am doing here is instantiating a IsolatedStorageFile class - that's because every single file that is downloaded is directly passed to the isolated storage without any intermediary links (like MemoryStream).

The DownloadState enum only contains two labels:

public enum DownloadState
{
    InProgress,
    Pending,
} 

When the download is started, I am creating a HttpWebRequest to the URL specified in the constructor. You might be wondering - why not use WebClient instead? HttpWebRequest is a way more "raw" class and surprisingly, in some specific cases it works better than WebClient because it is able to read a continuous stream.

That being said, I am setting AllowStreamBuffering to false to continuously receive downloaded bytes instead of getting a large chunk of binary data that will need to stored. 

The request is then processed in an asynchronous manner. Once the stream is received (not the entire stream - just the initiating part), I am starting the process of constantly asking for more data until I download the entire file.

The byte array I am using for a buffer is set to be 16KB. You can adjust this value depending on the size of the file you are trying to download. If it is a small file, set the buffer to a lower value. For bigger files - a bigger buffer.

The while loop is that "always ask for more data" process. Inside it, I am calculating the progress by taking totalValue - the length of the entire stream to be received, to the amount of data that is downloaded (incremented each time for every buffer read).

I am also using a dispatcher to change the Progress property. I am doing this because the property is tied to the UI and changing it directly would cause a cross-thread exception.

When the file is downloaded, I am closing the stream and checking the queue for any pending downloads (that are marked with a Pending download state). Once one is found, I am starting the download and removing the current instance from the queue.

You might be wondering how come the entire queue is not processed at once.  Take a look at the HomePageViewModel class - there is a CurrentDownload property. It is set when a download is initiated from anywhere in the app. So the download is not initiated when the URL is passed.

Instead, when the download manager page is loaded, I am using this:

if (ModelLocator.HomeStatic.CurrentDownload != null)
{
    if (ModelLocator.HomeStatic.Downloads.Count > 0)
    {
        ModelLocator.HomeStatic.CurrentDownload.State = Models.DownloadState.Pending;
        ModelLocator.HomeStatic.Downloads.Add(ModelLocator.HomeStatic.CurrentDownload);
    }
    else
    {
        ModelLocator.HomeStatic.CurrentDownload.State = Models.DownloadState.InProgress;
        ModelLocator.HomeStatic.Downloads.Add(ModelLocator.HomeStatic.CurrentDownload);
        ModelLocator.HomeStatic.CurrentDownload.StartDownload();
    }
}

If there are items in the queue, I am setting the state of the current download to Pending and adding it to the queue. If the queue is empty, I am adding the download to the queue and starting it.

Congratulations! Now you have a download manager core for your Windows Phone application.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}