Reputation: 1646
In our xamarin.android app we are downloading a file from remote server and the client list row item is updated with the progress status.
For this we have created a pcl to do the download which is common for android and ios.
From xamarin.android app from new worker thread we have called the httpwebrequest to download the file from remote server as below,
ThreadStart downloadThreadStart = new ThreadStart(() =>
{
Stream destinationFileStream = FileUtils.GetFileStream(fileName, info, context);
// Call the static method downloadFile present in PCL library class WebRequestHandler
WebRequestHandler.downloadFile(listener, fileName, key, destinationFileStream);
});
new Thread(downloadThreadStart).Start();
From PCL after download we are calling the write file content method using await/async.
await WriteFileContentAsync(response.GetResponseStream(), destFileStream,
Convert.ToInt64(size), listener, geoidFileName);
The below code snippet which is used to read the input stream from the httpwebresponse and write the data to output stream file which is present in internal storage.
private static async Task WriteFileContentAsync(Stream sourceStream,
Stream destFileStream, long size, ResponseListener listener, string filename)
{
byte[] buffer = new byte[BUFFER_SIZE];
//Interface callback sent to the xamarin.android application
listener.UpdateDownloadProgress(filename, 0);
try
{
int currentIndex = 0;
int bytesRead = await sourceStream.ReadAsync(buffer,
0, buffer.Length);
while (bytesRead > 0)
{
await destFileStream.WriteAsync(buffer, 0, bytesRead);
currentIndex += bytesRead;
int percentage = (int)(((double)currentIndex / size) * 100);
Debug.WriteLine("Progress value is : " + percentage);
listener.UpdateDownloadProgress(filename, percentage);
bytesRead = await sourceStream.ReadAsync(buffer,
0, buffer.Length);
}
listener.UpdateDownloadProgress(filename, 100);
//Send the callback
listener.updateDownloadStatus(ResponseStatus.Success, filename);
}
catch (Exception ex)
{
Debug.WriteLine("Geoid file write exception : " + ex);
listener.updateDownloadStatus(ResponseStatus.Error, filename);
}
finally
{
if (sourceStream != null)
{
sourceStream.Flush();
sourceStream.Dispose();
}
if (destFileStream != null)
{
destFileStream.Flush();
destFileStream.Dispose();
}
}
}
Once the callback is received in xamarin.android we are updating the ui inside run on UI thread as below,
public void UpdateDownloadProgress(string filename, int progressValue)
{
//Skip the progress update if the current and previous progress value is same
if (previousProgressValue == progressValue)
{
return;
}
int position = findAndUpdateProgressInList(filename);
this.itemList[position].ProgressValue = progressValue;
RunOnUiThread(() =>
{
this.coordinateAdapter.NotifyDataSetChanged();
});
}
But the app closes abruptly and crashes. While downloading the status is updated immediately after sometime the app closes abruptly and crashes. The scroll is also not smooth.
We are calling the download APIfrom thread. While reading the Xamarin "Report download progress" reference link says that await/async internally calls use the threadpool to do the background task.
If I try without thread the UI hangs once I pressed the download icon. If i call inside thread the UI is responsive and it crashed immediately.
The PCL async/await to Xamarin android ui communication is not proper.
Help me to get rid out of this issue.
Upvotes: 1
Views: 450
Reputation: 1487
I'm doing something similar in my app (conceptually speaking).
The way I notify the download listeners on the UI-thread is by having a (view-less) DownloadManager-Fragment with RetainInstance
set to true
.
This will prevent your fragment from being destroyed on configuration changes (such as orientation changes).
Here's a short excerpt of my code:
/// <summary> A non-UI, retaining-instance fragment to internally handle downloading files. </summary>
public class DownloadFragment : Fragment
{
/// <summary> The tag to identify this fragment. </summary>
internal new const string Tag = nameof(DownloadFragment);
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RetainInstance = true;
}
/// <summary> Starts the given download. </summary>
public async void RunDownload(Context context, Download download, IDownloadListener listener,
IDownloadNotificationHandler handler = null)
{
// start your async download here, and hook listeners.
// Note that listener calls will happen on the UI thread.
}
}
So from your activity, get (or create) this fragment using its Tag
, and then start your download with your download listener(s).
Then, from your activity, you can call NotifyDataSetChanged
and similar methods.
I hope this is useful in the process of solving your problem.
Upvotes: 0