Displaying progress bar while loading JSON from Web Services for Windows Phone 7

In one of my previous posts I explained how to download and parse JSON response from web services. I used a simple text to show that the content is loading, but it isn’t convenient because you don’t know how long it remains to wait. In this post I will add the ProgressBar control to the project which will constantly display the number of downloaded bytes in per cents.
Here is how it looks:
wp7_json_loading

At first, I added some code to the XAML view so that it displays the ProgressBar and its description:

<StackPanel Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"
            VerticalAlignment="Center">
    <TextBlock Text="{Binding ProgressDescription}" HorizontalAlignment="Center" Style="{StaticResource PhoneTextNormalStyle}" />
    <ProgressBar Value="{Binding ProgressValue}" Maximum="1" IsIndeterminate="False" Height="50" />
</StackPanel>

After that I’ve tried to find some event of the HttpWebRequest class which can report progress, but I haven’t found it, so I implemented my own ProgressStream class which looks like a common stream but has the OnProgressChanged callback.

public class ProgressStream : Stream
{
    private readonly Stream _stream;
    private long _length;

    public ProgressStream(Stream stream, long length)
    {
        this._stream = stream;
        this._length = length;
    }

    public override bool CanRead
    {
        get { return this._stream.CanRead; }
    }

    public override bool CanSeek
    {
        get { return this._stream.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return this._stream.CanWrite; }
    }

    public override void Flush()
    {
        this._stream.Flush();
    }

    public override long Length
    {
        get { return this._length; }
    }

    public override long Position
    {
        get
        {
            return this._stream.Position;
        }
        set
        {
            this._stream.Position = value;
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int readCount = this._stream.Read(buffer, offset, count);
        this.ReportProgress();

        return readCount;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        long seekCount = this._stream.Seek(offset, origin);
        this.ReportProgress();

        return seekCount;
    }

    public override void SetLength(long value)
    {
        this._length = value;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        this._stream.Write(buffer, offset, count);
        this.ReportProgress();
    }

    public Action<double> OnProgressChanged { get; set; }

    private void ReportProgress()
    {
        if (this.OnProgressChanged != null)
        {
            var percent = this.Position / (double)this.Length;
            // System.Threading.Thread.Sleep(500); // for test purposes
            this.OnProgressChanged(percent);
        }
    }
}

After that I changed the HttpGetTask class so that it uses the new ProgressStream class if it is possible. It is necessary to check the presence of the Content-Length header, because without this header and with unknown length it is impossible to calculate the progress.

public class HttpGetTask<T>
{
    //...
    
    private void OnGetResponseCompleted(IAsyncResult ar)
    {
        //...
        
        Stream stream;
        if (this.OnProgressChanged != null && response.Headers.AllKeys.Any(key => key == "Content-Length"))
        {
            var progressStream = new ProgressStream(response.GetResponseStream(), response.ContentLength);
            progressStream.OnProgressChanged = v => this.InvokeInUiThread(() => this.OnProgressChanged(v));
            stream = progressStream;
        }
        else
        {
            stream = response.GetResponseStream();
        }

        //...
    }

    //...
    
    public Action<double> OnProgressChanged { get; set; }
}

Then you should update the view model. I added the OnProgressChanged method which updates the ProgressDescription and ProgressValue properties.
Also I changed the url of the sample data, because the website geonames.org which I used earlier doesn’t send the Content-Length header (it sends the Transfer-Encoding header with value ‘chunked’ and unknown length), so I downloaded a JSON response and uploaded it on my dropbox account here.
The ViewModel looks so:

public class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        Cities = new ObservableCollection<CityViewModel>();

        string url = "https://dl.dropbox.com/u/8047386/WordPress/testcities.json";
        
        var task = new HttpGetTask<CitiesList>(url, this.OnPostExecute);
        task.OnPreExecute = this.OnPreExecute;
        task.OnError = this.OnError;
        task.OnProgressChanged = this.OnProgressChanged;

        task.Execute();
    }
    
    //...

    private void OnProgressChanged(double value)
    {
        this.ProgressValue = value;
        this.ProgressDescription = string.Format("Loading {0:F0}%", value * 100);
    }

    //...
        
    private double _progressValue;

    public double ProgressValue
    {
        get { return _progressValue; }
        set
        {
            _progressValue = value;
            RaisePropertyChanged("ProgressValue");
        }
    }

    private string _progressDescription;

    public string ProgressDescription
    {
        get { return _progressDescription; }
        set
        {
            _progressDescription = value;
            RaisePropertyChanged("ProgressDescription");
        }
    }
}

Though the loading indicator is difficult to reproduce on the Windows Phone Emulator because it downloads the file very fast, it will be certainly displayed if the phone has a slow internet connection.
I didn’t add all code to the post, but as usual you can download the full project here:
PhoneJsonProgressBar.zip

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: