How to read Asset Files using VS & Monodroid - xamarin

I've been trying to implement this example using C# and Monodroid, but am having difficulties reading and writing an Asset file:
http://docs.xamarin.com/android/advanced_topics/using_android_assets
I am using the emulator, not a device.
First of all, I am having trouble finding the namespace for Assets.Open. What I ultimately found was
const string lfn = MyAssetFile.txt;
System.IO.StreamReader(Android.Content.Res.Resources.System.Assets.Open(lfn);
Is this the correct namespace?
Second of all, my Asset file is marked as AndroidAsset and "Copy Always" in the VS "Properties" pane, but my attempts to read the file always fail (File Not Found) using this statement:
string settings = "";
using (StreamReader sr = new System.IO.StreamReader (Android.Content.Res.Resources.System.Assets.Open(lfn))) settings = sr.ReadToEnd();
Do I have my VS settings wrong so that the asset file not being copied onto the emulator, or is it being copied OK but my code to open/read it is wrong?

You must use:
const string lfn = "MyAssetFile.txt";
string settings = string.Empty;
// context could be ApplicationContext, Activity or
// any other object of type Context
using (var input = context.Assets.Open(lfn))
using (StreamReader sr = new System.IO.StreamReader(input))
{
settings = sr.ReadToEnd();
}
If I remember correctly, as I haven't got an IDE at the moment, Android.Content.Res.Resources.System.Assets references the Android assets not your project assets. You want to use your projects assets so you need to use the AssetManager from your Activities or Contexts.
For example: the Activity class has a property called Assets. This is what you use. OR you use a View's Context.Assets property.

Or just simply add at the top:
using System.IO;
and it will work.
Xamarin team forget sometimes to mention using stuff.

Related

How to save a csv or txt in Xamarin UWP app?

Today I've got a question about saving a .csv or .txt file within a Xamarin app on UWP platform. I am trying to save a file I create in my code call tags.csv. My goal is to have no .csv's saved initially, I create an instance in my code, then save it and create a new .csv file when my code executes. The creation and filling of the .csv occurs in one function which triggers based on a Button instance in my app. Also, ideally I could make it save in a location determined by a file explorer popup.
I have tried two routes so far to make and save a .csv file, the CSVExport package and CSVhelper package. Both I have been able to download and add to my project from NuGet successfully.
I have tried separately a simple implementation of each, basically just taking their Example code to see if it would work in my UWP app. Here is the respective code
// CSVExport methods, two ways to save
var myExport = new CsvExport();
...
File(myExport.ExportToBytes(), "text/csv", "results.csv"); // method 1
myExport.ExportToFile("./results.csv"); // method 2
// CSVhelper method
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
};
using (var writer = new StreamWriter("./tags.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(records);
}
Here is the error I am receiving: System.UnauthorizedAccessException: 'Access to the path C:...(filepath)...BLE.Client.UWP\bin\x86\Debug\AppX\tags.csv' is denied.'
Whenever the code reaches my saving of the .csv file, it crashes the app and Visual Studio 2022 gives me this error message. The same exact error occurs whether I am using CSVExport or CSVhelper.
Attempted Solutions:
My attempted solutions are mainly in regards to giving the app the permissions it needs to save. If an alternative like getting a different CSV package is better, I would take that advice too.
One solution I saw on StackOverflow linked to this page. The issue is I cannot load StorageFolder or Windows.Storage in my Xamarin app, it just won't recognize it and won't compile cause it's a missing load action.
Another solution I saw was changing your Capabilities in the Package.appxmanifest file and changing your Package header. I have done so, so mine looks like the following code sample. I need the internetClient and bluetooth and location for the app itself, so I added broadFilesystemaccess and even documents and pictures just to see if that would work too.
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
...
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="broadFileSystemAccess" />
<uap:Capability Name="documentsLibrary"/>
<uap:Capability Name="picturesLibrary" />
<DeviceCapability Name="bluetooth" />
<DeviceCapability Name="location"/>
</Capabilities>
Another solution was making sure the UWP app had permissions, which I went into system settings and allowed, so it should have full access now.
I am not sure where to go from here, so any advice about UWP or saving files within Xamarin UWP apps would be appreciated.
Based on your requirement, you could try to use the DependencyService feature of Xamarin.Forms.
DependencyService enables you to invoke native platform functionality from shared code. For your scenario, you could pass the stream of the file to the DependencyService first. then you could call the UWP FileSavePicker using DependencyService in your Forms app and save the stream as a file.
Here are some code snippets about how to implement the interface.
SaveFileAsync
public async Task SaveFileAsync(Stream data)
{
FileSavePicker savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
// Dropdown of file types the user can save the file as
savePicker.FileTypeChoices.Add("Text", new List<string>() { ".txt" });
savePicker.FileTypeChoices.Add("CSV", new List<string>() { ".csv" });
// Default file name if the user does not type one in or select a file to replace
savePicker.SuggestedFileName = "New Document";
StorageFile file = await savePicker.PickSaveFileAsync();
if (file!= null)
{
using (IRandomAccessStream dataStream= data.AsRandomAccessStream())
{
using (var reader = new DataReader(dataStream.GetInputStreamAt(0)))
{
await reader.LoadAsync((uint)dataStream.Size);
var buffer = new byte[(int)dataStream.Size];
reader.ReadBytes(buffer);
await Windows.Storage.FileIO.WriteBytesAsync(file, buffer);
}
}
}
}
ISaveFileServicecs interface
public interface ISaveFileServicecs
{
Task<Stream> SaveFileAsync(Stream stream);
}
Usage
await DependencyService.Get<ISaveFileServicecs>().SaveFileAsync(stream);

How to access file stored in shared project in Xamarin Forms?

I have a folder called Documentation inside the shared project, named App2 in this case. How do I access the files stored inside the Documentation folder? Attached image below shows the project structure.
Visual Studio Solution Page
I have tried out following commands but they aren't working :
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
AppDomain.CurrentDomain.BaseDirectory
System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
If it's troublesome to access the file in that folder, I'm open to hearing other alternatives.
This is how I have done it for JSON files in my shared project (using PCL). As Jason pointed out in the comments, if you are using .NET Standard, you can simply define the GetSharedFile method in your shared project instead of creating platform specific references.
Add the file to the shared project and set it as Embedded Resource
Create an IFileHelper interface in your shared project
public interface IFileHelper {
Stream GetSharedFile(string fileResourceName);
}
Create a new FileHelper class in each project (Android and iOS) with the following
public class FileHelper : IFileHelper {
public Stream GetSharedFile(string fileResourceName) {
Type type = typeof(IFileHelper); // We could use any type here, just needs to be something in your shared project so we get the correct Assembly below
return type.Assembly.GetManifestResourceStream(fileResourceName);
}
}
Add a documentation handler class in your shared project. Something like below (make sure to change the App namespace to match yours):
public class Documentation {
private const string ResourcePath = "App.Documentation.index.html"; // App would be your application's namespace, you may need to play with the Documentation path part to get it working
public string GetDocs() {
IFileHelper helper = DependencyService.Get<IFileHelper>(); // Your platform specific helper will be filled in here
Stream stream = helper.GetSharedFile(ResourcePath);
using (stream)
using (StreamReader reader = new StreamReader(stream)) {
return reader.ReadToEnd(); // This should be the file contents, you could serialize/process it further
}
}
}
I wrote this mostly by hand so let me know if something is not working. If you cannot load your file, I suggest trying to put it into the root of your shared project and then changing ResourcePath in the code above to the following (again using your app's namespace instead of App):
private const string ResourcePath = "App.index.html";

Azure (C#) System.IO.FileNotFoundException: Could not find file

I've created a ML model with Visual Studio. I uploaded the web app to Azure with Visual Studio too. However, when I fill the fields for my ML model and click "run" on the website, I get this error which I copied directly from Azure App Service Editor.
I only get this error while trying to run the ML model on Azure website, if I run the web app on my computer I have no errors at all.
Thank you :)
The error:
2020-07-18 01:12:59.138 +00:00 [Error] Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware: An unhandled exception has occurred while executing the request.
System.IO.FileNotFoundException: Could not find file 'C:\Users\X\X\X\fileML.Model\MLModel.zip'.
File name: 'C:\Users\X\X\X\fileML.Model\MLModel.zip'
____________________
My code:
// This file was auto-generated by ML.NET Model Builder.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.ML;
using fileML.Model;
namespace fileML.Model
{
public class ConsumeModel
{
private static readonly Lazy<PredictionEngine<ModelInput, ModelOutput>> PredictionEngine = new Lazy<PredictionEngine<ModelInput, ModelOutput>>(CreatePredictionEngine);
// For more info on consuming ML.NET models, visit https://aka.ms/mlnet-consume
// Method for consuming model in your app
public static ModelOutput Predict(ModelInput input)
{
ModelOutput result = PredictionEngine.Value.Predict(input);
return result;
}
public static PredictionEngine<ModelInput, ModelOutput> CreatePredictionEngine()
{
// Create new MLContext
MLContext mlContext = new MLContext();
// Load model & create prediction engine
string modelPath = #"C:\Users\X\X\X\fileML.Model\MLModel.zip";
ITransformer mlModel = mlContext.Model.Load(modelPath, out _);
var predEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
return predEngine;
}
}
}
Nathan, welcome to the stackoverflow. Here is thing you are missing:
You are trying to access local path from your computer but on Azure there is no local machine so whenever you code tries to access the same path which you have hard coded it's resulting in error.
My recommendation would be to add your zip file to your project, once added right click on that file and mark Copy to Output Directory - Copy always. Please see below
This will help to get the local file path from output directory.
Now it's time to change your code to get file dynamically.
You can use
string directoryPath = Directory.GetCurrentDirectory();
string modelPath= Path.Combine(directoryPath ,"MLModel.zip");
This will get you the file path. Just do a test your code on your local and deploy the app.
The good thing is now your model file will get deployed along with your code. Every time you change your model just replace the file and deploy code again.
Hint to make it more dynamic:- You can also use Azure Blob Storage to keep your zip file, by using this you do not need to deploy your code again and again . Just need to replace the file in side blob.
you are trying to load file MLModel.zip from C:\Users\X\X\X\fileML.Model. Now that's your local computer path. That path not exists into Azure Web App.
There are 2 ways you can do if you really want to store in local directory:
HOMe environment variable in your Azure Web App that resolves to the
equivalent of inetpub for your site. Your app data folder is located
at %HOME%\site\wwwroot\AppData.
TEMP environment both on Azure Web Apps and on your local machine.
public static PredictionEngine<ModelInput, ModelOutput> CreatePredictionEngine()
{
// Create new MLContext
MLContext mlContext = new MLContext();
// Load model & create prediction engine
string directoryPath = Directory.GetCurrentDirectory();
string modelPath = Path.Combine(#"C:\Users\Admin\source\repos\ShanuASPMLNETML.Model\MLModel.zip","MLModel.zip");
ITransformer mlModel = mlContext.Model.Load(modelPath, out _);
var predEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
return predEngine;
}
}

Embedding database file in Shared project or Android asset doesn't work [duplicate]

I have started using the Xamarin plugin for Visual Studio to create an Android app.
I have a local SQL database, and I want to call it to display data. I don't see how I can do this. Is it possible?
After thinking this was a trivial thing to do, I was proven wrong when I tried setup a quick test project. This post will contain a full tutorial on setting up a DB for an Android App in Xamarin that will come in handy as a reference for future Xamarin users.
At a glance:
Add Sqlite.cs to your project.
Add your database file as an Asset.
Set your database file to build as an AndroidAsset.
Manually copy the database file out of your apk to another directory.
Open a database connetion using Sqlite.SqliteConnection.
Operate on the database using Sqlite.
Setting up a local database for a Xamarin Android project
1. Add Sqlite.cs to your project.
Start by going to this repository and downloading Sqlite.cs; this provides the Sqlite API that you can use to run queries against your db. Add the file to your project as a source file.
2. Add DB as asset.
Next, get your DB and copy it into the Assets directory of your Android project and then import it into your project so that it appears beneath the Assets folder within your solution:
I'm using the Chinook_Sqlite.sqlite database sample renamed to db.sqlite from this site throughout this example.
3. Set DB to build as AndroidAsset.
Right click on the DB file and set it to build action AndroidAsset. This will ensure that it is included into the assets directory of the APK.
4. Manually copy DB out of your APK.
As the DB is included as an Asset (packaged within the APK) you will need to extract it out.
You can do this with the following code:
string dbName = "db.sqlite";
string dbPath = Path.Combine (Android.OS.Environment.ExternalStorageDirectory.ToString (), dbName);
// Check if your DB has already been extracted.
if (!File.Exists(dbPath))
{
using (BinaryReader br = new BinaryReader(Android.App.Application.Context.Assets.Open(dbName)))
{
using (BinaryWriter bw = new BinaryWriter(new FileStream(dbPath, FileMode.Create)))
{
byte[] buffer = new byte[2048];
int len = 0;
while ((len = br.Read(buffer, 0, buffer.Length)) > 0)
{
bw.Write (buffer, 0, len);
}
}
}
}
This extracts the DB as a binary file from the APK and places it into the system external storage path. Realistically the DB can go wherever you want, I've just chosen to stick it here.
I also read that Android has a databases folder that will store databases directly; I couldn't get it to work so I've just ran with this method of using an existing DB.
5. Open DB Connection.
Now open a connection to the DB through the Sqlite.SqliteConnection class:
using (var conn = new SQLite.SQLiteConnection(dbPath))
{
// Do stuff here...
}
6. Operate on DB.
Lastly, as Sqlite.net is an ORM, you can operate on the database using your own data types:
public class Album
{
[PrimaryKey, AutoIncrement]
public int AlbumId { get; set; }
public string Title { get; set; }
public int ArtistId { get; set; }
}
// Other code...
using (var conn = new SQLite.SQLiteConnection(dbPath))
{
var cmd = new SQLite.SQLiteCommand (conn);
cmd.CommandText = "select * from Album";
var r = cmd.ExecuteQuery<Album> ();
Console.Write (r);
}
Summary
And that's how to add an existing Sqlite database to your Xamarin solution for Android! For more information check out the examples included with the Sqlite.net library, its unit tests and the examples in the Xamarin documentation.
Here is the one that I'm using and it's working
install the Sqlite plugin
create interface to access different platforms services
create a model for the table
implement the interface that you created earlier on all of the
platform you want to use
use the plugin to create, get, insert, etc on your table
for more detailed information check this

How to decode a bitmap from a local file deployed with Android application

I am going through http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/images/
and trying to get the Local Images working with Android however I am experiencing some issues when attempting to do something in normal monodroid just as a further test. I'm using a SharedProject, just for reference.
I have added the test image at Resources/drawable (test1.png), and also setting the Build Action as AndroidResource as it describes, and if I do the following in Xamarin.Forms it works:-
Xamarin.Forms.Image myimage = new Image();
myimage.Source = ImageSource.FromFile("test1.png");
However, if I try and retrieve the same file via the following it comes back as null.
Android.Graphics.Bitmap objBitmapImage = Android.Graphics.BitmapFactory.DecodeFile("test1.png")
Does anyone know why this is null and how to correct?
The Xamarin.Forms FileImageSource has a fallback mode as it covers two different scenarios.
First it will check whether the file exists in the file system via the BitmapFactory.DecodeFile.
Should the file not exist as specified in the File property, then it will use BitmapFactory.DecodeResource to try and load the resource content as a secondary alternative.
FileImageSource for Android therefore isn't only just for files that exist in the file system but also for retrieving named resources also.
This link indicates that Android Resources are compiled into the application and therefore wouldn't be part of the file system as physical files.
Since test1.png is a Drawable you should use BitmapFactory.DecodeResource ()
string scr = "#drawable/test1.png";
ImageView iv = new ImageView(context);
Bitmap bm = BitmapFactory.DecodeFile(src);
if (bm != null)
iv.SetImageBitmap(bm);

Resources