Hello Friends, A few weeks ago we talk about downloading files bytes by bytes in Xamarin iOS. That post explained how to use NSUrl to download large files, monitor the download’s progress, and a few more interesting things. Today, we will switch from iOS to Android, and see how to properly download files of all sizes with the Xamarin Android Download Manager.
What we will Cover
- Configuring the download manager
- Monitoring download progress inside your app
- Listening to download completion with broadcast receivers
Configuring the Xamarin Download Manager
When you want to download a file in your app, first you need to configure permissions. There are basically 3 permissions to add to your app’s manifest. Two of these permissions are requested at runtime.
1 2 3 4 | <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
After adding these permissions to your manifest, go to your code and request the “READ_EXTERNAL_STORAGE” and “WRITE_EXTERNAL_STORAGE” permissions. To do this, we use Xamarin Essentials (Yeah, this plugin makes life sooo easy 🙂 ). This is how we do it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | async Task RequestPermissions() { var status = await Permissions.CheckStatusAsync<Permissions.StorageWrite>(); var status2 = await Permissions.CheckStatusAsync<Permissions.StorageRead>(); if (status != PermissionStatus.Granted) { await Permissions.RequestAsync<Permissions.StorageWrite>(); } if (status2 != PermissionStatus.Granted) { await Permissions.RequestAsync<Permissions.StorageRead>(); } } |
Then, we have to tell the download manager which URL to download from, where to save the downloaded file, display the download notification, and how to monitor the download progress in our app. To know more custom properties for the download manager, Check this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var manager = DownloadManager.FromContext(Android.App.Application.Context); var request = new DownloadManager.Request(Android.Net.Uri.Parse(downloadUrl)); var fileName = "MyFileName.{extension}"; request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted); request.SetDestinationInExternalFilesDir(Platform.CurrentActivity, Environment.DirectoryDownloads, fileName); request.SetTitle("Title of Item"); request.SetDescription(downloadDescription); long downloadId = manager.Enqueue(request); _logger.LogInformation($"Download manager successfully started the download."); _logger.LogInformation($"Started download monitor"); MonitorDownload(downloadId, fileId); |
Note: You should save the download id if you are willing to access your download info later from Android.
Note: From Android 13 and above, access to files has been restricted, so the download path should not be the app’s folder else Android will throw an unauthorized access exception. You should use the method above to tell the download manager a path to a directory accessible by your app (Downloads, Pictures…). Later, if you want the download to be in your app’s private folder, you will have to move it when the download completes.
Monitoring The Download Progress In Your App
One of the principal features we want when implementing a download functionality is to let the user visualize the progress of the download. The Xamarin Android Download manager allows us to display a notification with the progress. But this might not be sufficient. You might want to monitor this download progress in your code and inform the user of every download progress.
While reading the Android documentation and going through a lot of examples (Recent and old, Java & Kotlin etc) I noticed that querying the download manager’s database for progress isn’t difficult. The complex part is determining the right time to query this database exactly when the download percentage changes. You can find online examples using ContentObservers and ContentProviders. I experimented with these and they didn’t work for me If you managed to make this work, please share in the comments section.
To know the right time to query the download progress of the Xamarin download manager, we will use Xamarin’s Timer. This API will use Android’s underlying native timer functionalities and perform our queries every n second we configure it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private void MonitorDownload(long downloadId, int itemId) { Device.StartTimer(TimeSpan.FromSeconds(1), () => { try { var downloadMonitor = new DownloadMonitor(); var downloadStatus = downloadMonitor.ComputeDownloadStatus(downloadId); return downloadStatus != DownloadStatus.Failed && downloadStatus != DownloadStatus.Successful; } catch (Exception e) { logger.LogError(e, $"Error computing download percentage for prodct id : {itemId}"); return true; } }); } |
The download monitor we use in the code above is an object that contains all the logic to query our download’s progress. This it’s implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | internal class DownloadMonitor { public DownloadStatus ComputeDownloadStatus(long downloadId) { long downloadedBytes = 0; long totalSize = 0; int status = 0; DownloadManager.Query query = new DownloadManager.Query(); query.SetFilterById(downloadId); var downloadManager = DownloadManager.FromContext(Android.App.Application.Context); var cursor = downloadManager.InvokeQuery(query); if (cursor != null && cursor.MoveToFirst()) { String downloadFilePath = (cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri))).Replace("file://", ""); var ids = AndroidUtilities.GetUserAnditemIdFromFilePath(downloadFilePath); try { downloadedBytes = cursor.GetLong(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnBytesDownloadedSoFar)); totalSize = cursor.GetInt(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnTotalSizeBytes)); status = cursor.GetInt(cursor.GetColumnIndex(DownloadManager.ColumnStatus)); } finally { if (cursor != null) { cursor.Close(); } } var percentage = (new decimal(downloadedBytes) / new decimal(totalSize)) * 100; NotifyDownloadProgress(ids.itemId, (float)percentage); } return (DownloadStatus) status; } void NotifyDownloadProgress(int itemId, float percentage) { var args = new DownloadProgressEventArg() { itemId = itemId, Percentage = percentage }; MessagingCenter.Instance.Send<object, DownloadProgressEventArg>(this, args.MessageName, args); } } |
Listenning to Download Completion With a Broadcast Receiver
You might want to just listen to the download completion and take appropriate actions when this occurs. First, you will need to create a broadcast receiver. Below, you can see how to do just that. Learn more about broadcast receivers here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | [BroadcastReceiver] [IntentFilter(new string[] { DownloadManager.ActionDownloadComplete })] public class DownloadCompletedBroadcastReceiver : Android.Content.BroadcastReceiver { public override async void OnReceive(Context context, Intent intent) { string action = intent.Action; if (DownloadManager.ActionDownloadComplete.Equals(action) && intent.Extras != null) { Bundle extras = intent.Extras; DownloadManager.Query q = new DownloadManager.Query(); long downloadId = extras.GetLong(DownloadManager.ExtraDownloadId); q.SetFilterById(downloadId); var cursor = ((DownloadManager)context.GetSystemService(Context.DownloadService)).InvokeQuery(q); if (cursor != null && cursor.MoveToFirst()) { int status = cursor.GetInt(cursor.GetColumnIndex(DownloadManager.ColumnStatus)); String downloadFilePath = (cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri))).Replace("file://", ""); String downloadTitle = cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnTitle)); if (status == (int)DownloadStatus.Successful) { //Do what you want } else if (status == (int)DownloadStatus.Failed) { var code = cursor.GetInt(cursor.GetColumnIndex(DownloadManager.ColumnReason)); //Report download failure } cursor.Close(); } } } } |
Then register your download broadcast receiver as shown below.
1 2 | DownloadCompletedBroadcastReceiver receiver = new DownloadCompletedBroadcastReceiver(); RegisterReceiver(receiver, new IntentFilter(DownloadManager.ActionDownloadComplete)); |
Conclusion
Above we have the complete code to configure the Xamarin Download Manager, Monitor Download Progress, and receive download completed broadcasts. You might also link to know how to implement this functionality with Xamarin iOS find it here.
References
https://www.programmersought.com/article/2905971463/
Follow me on social media and stay updated