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

Kaustabh Kakoty 1 Reputation point Microsoft Employee
2021-05-19T14:27:34.037+00:00

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

Developer technologies | .NET | Xamarin
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. JarvanZhang 23,971 Reputation points
    2021-05-20T06:47:28.413+00:00

    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.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.