I have a Xamarin.Forms application which utilizes dependency services to access Android specific features, in this case file access on the storage.
The file we are accessing is a serialized .xml file that contains the data for our specific class, called ConfigInfo, whose implementation isn't necessary for this review.
The goal is to open an .xml file with the Android file picker and then de-serialize it to our object. There are a few things to note
We get the file as a URI, in my example the URI is represented as
content://com.android.externalstorage.documents/document/primary%3Aconfig_V01_14.xml
, with aPath
element of/document/primary:config_V01_14_vtest.xml
.We also utilize a dependency service, which is exposed through
IDataStore
. In order to appropriately alert the user that the data from the file has been loaded, I am utilizing an event that I need to fire manually using a public function (Bad practice, I know) which I can then respond to in the Xamarin.Forms view model.I am firing the event by getting the
IDataStore
dependency service instance, which is implemented byMainActivity
, but it appears that theDependencyService
might create a different global instance, because I am currently required to use the (apparently obselete)Xamarin.Forms.Forms.Context
to callStartActivityForResult
without getting a null pointer exception. I am wondering if, perhaps, implementing the dependency service interface in the main activity is a bad idea. If I decide to implement the dependency service in a separate class, how do I launch the file activity from that class?
So here are the functions at hand (again, this code is working, I just don't like it)
// Function declared in IDataStore and implemented in MainActivity
// Calling example:
// IDataStore<ConfigItem> dataStore = DependencyService.Get<IDataStore<ConfigItem>>();
// await dataStore.LoadConfig();
public async Task<bool> LoadConfig()
{
Intent intent = new Intent(Intent.ActionOpenDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("text/xml");
((Activity)Forms.Context).StartActivityForResult(intent, READ_REQUEST_CODE);
return true;
}
The code that receives the intent result is as follows:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == Result.Ok && data != null && requestCode == READ_REQUEST_CODE)
{
ParcelFileDescriptor parcelFileDescriptor = ContentResolver.OpenFileDescriptor(data.Data, "r");
Java.IO.FileDescriptor fileDescriptor = parcelFileDescriptor.FileDescriptor;
Java.IO.FileReader reader = null;
Java.IO.BufferedReader bufferedReader = null;
StringBuilder text = null;
try
{
reader = new Java.IO.FileReader(fileDescriptor);
bufferedReader = new Java.IO.BufferedReader(reader);
text = new StringBuilder();
String line;
while ((line = bufferedReader.ReadLine()) != null)
{
text.Append(line);
}
}
catch (Exception)
{
return;
}
finally
{
bufferedReader?.Close();
reader?.Close();
parcelFileDescriptor.Close();
}
if (text != null)
{
using (var stringReader = new StringReader(text.ToString()))
{
XmlSerializer serializer = new XmlSerializer(typeof(ConfigInfo));
object obj = serializer.Deserialize(stringReader);
if (obj is ConfigInfo info)
{
IDataStore<ConfigItem> dataStore = Xamarin.Forms.DependencyService.Get<IDataStore<ConfigItem>>();
dataStore.ConfigInfo = info;
dataStore.FireConfigInfoLoaded();
Console.WriteLine("Successfully read {0} with {1} items", data.Data.ToString(), dataStore.ConfigInfo.Items.Count);
}
}
}
}
}