question

KaustabhKakoty-5873 avatar image
0 Votes"
KaustabhKakoty-5873 asked RobCaplan edited

How to download a file from URL and save to public external directory using Storage Access Framework in Xamarin Android?

Currently, the Xamarin Android app uses GetExternalStoragePublicDirectory (deprecated as of API level 29) with the requestLegacyExternalStorage enabled to download a file from Azure Blob Storage endpoint into the downloads directory directly.

The official way out of this is to use Storage Access Framework (SAF) and allow the user to select the destination to save the file. Registered a dependency service as follows:

FileDownloader.cs

 public void DownloadFile(string url, string fileName)
 {
     Intent intent = new Intent(Intent.ActionCreateDocument);
     intent.AddCategory(Intent.CategoryOpenable);
     intent.PutExtra(Intent.ExtraTitle, fileName);
     intent.PutExtra("download_url", url);
     intent.SetType("application/pdf");
    
     var activity = MainActivity.Instance;
     activity.StartActivityForResult(intent, CREATE_PDF_REQUEST);
 }

And then in the onActivityResult, handling the request code and result to download the data from the url

MainActivity.cs

 // rest of MainActivity.cs
    
 protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
 {
   base.OnActivityResult(requestCode, resultCode, data);
    
   try
   {
     switch (requestCode)
     {
       case CREATE_PDF_REQUEST:
         string url = data.GetStringExtra("download_url");
         Webclient client = new Webclient();
         client.DownloadFile(url, data.DataString);
         break;
          
       default:
         // do something
     }
   }
   catch (Exception ex)
   {
     //do something
   }
 }

The flow is activated on taping a button which has an event listener and calls the dependency service as follows:

ViewModel.cs

 DependencyService.Get<FileDownloaderInterface>().DownloadFile(url, fileName);

However, the url is always null. Thus leading to malformed Uri exception. What am I doing wrong here?

Also, I want to await an async call inside the onActivityResult. Is it possible or do I have to run Task.Run(() => await client.DownloadFileAysnc())?

Xamarin Forms version 4.6.0




dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered JarvanZhang-MSFT commented

Hello,​

Welcome to our Microsoft Q&A platform!

However, the url is always null. Thus leading to malformed Uri exception. What am I doing wrong here?

This is because the Intent is not the one you put the data. When calling StartActivityForResult to open the file system, this intent contains the download url. But you return back to the previous activity, the system will generate a new Intent. The intent just contains the file info you handled. This is why data.GetStringExtra("download_url") returns null in OnActivityResult method.

To fix this, you could save the data to SharedPreferences and retrive it in the OnActivityResult method. Or use Xamarin.Essentials.Preferences to store the url.

Intent intent = new Intent(Intent.ActionCreateDocument);
...
//save the download url to sharedPreference
StartActivityForResult(intent, 1001);

protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    if (requestCode == 1001 && resultCode == Result.Ok)
    {
        //retrive the url
    }
}


Best Regards,

Jarvan Zhang


If the response is helpful, please click "Accept Answer" and upvote it.

Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

This looks like it will solve the issue. But the url will be coming from the view model and is subject to change based on the user interaction with the UI. And there can be multiple download requests as well (although only a single file at a time). Will this then be a scalable option? Will SharedPreferences be an alternative to state management?

0 Votes 0 ·

But the url will be coming from the view model and is subject to change based on the user interaction with the UI.

It requires to pass the download url when calling the 'DownloadFile', just save the url in the 'DownloadFile' method.

And there can be multiple download requests as well (although only a single file at a time).

Are the files downloaded one by one? If so, it will have no problem. If you want to download multi file at one time, you could create an array to store the urls to SharedPreferences.

0 Votes 0 ·