Uno Platform: Reading JSON configuration file in Android project - xamarin

I'm trying to integrate IConfiguration with the Uno Platform this way:
public class Startup
{
internal static void Init()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(f => f.Contains("appsettings.json"));
if (!string.IsNullOrEmpty(resourceName))
{
using (var s = assembly.GetManifestResourceStream(resourceName))
{
//var content = s.ReadToEnd();
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
c.AddJsonStream(s);
})
.ConfigureServices((c, x) => ConfigureServices(c, x))
.ConfigureLogging(l => l.AddConsole(abc =>
{
abc.DisableColors = true;
}))
.Build();
}
}
}
I defined appsettings.json in my Shared project as Embedded Resource and copy always.
When running with UWP it runs successfully, but with Android it fails with the following error:
System.ArgumentException: 'The path must be absolute. Parameter name: root'
In my (other) Xamarin project, it works as expected with this addition:
.ConfigureHostConfiguration(c =>
{
c.AddCommandLine(new string[] { $"ContentRoot={Xamarin.Essentials.FileSystem.AppDataDirectory}" });
c.AddJsonStream(stream);
})
but Xamarin Essentials is out of the scope of the Uno Platform as I understand...

I ended up with the following
var resourcePrefix = "";
#if __IOS__
resourcePrefix = "UnoApp.iOS.";
#endif
#if __ANDROID__
resourcePrefix = "UnoApp.Droid.";
#endif
#if WINDOWS_UWP
resourcePrefix = "UnoApp.";
#endif
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(Startup)).Assembly;
Stream stream = assembly.GetManifestResourceStream
(resourcePrefix + "appsettings.json");
var conf = new ConfigurationBuilder()
.AddJsonStream(stream);
Configuration = conf.Build();
services.AddSingleton(typeof(IConfiguration), Configuration);

Related

Can't advertise on bluetooth

I want to create a Gatt Server in my Xamarin.Forms app so that other devices can scan for it via bluetooth. I am using this plugin:
https://github.com/aritchie/bluetoothle
This is my code to create a Gatt Server and advertise data:
server = CrossBleAdapter.Current.CreateGattServer();
var service = server.AddService(serviceGuid, true);
var characteristic = service.AddCharacteristic(
characteristicGuid,
CharacteristicProperties.Read |
CharacteristicProperties.Write | CharacteristicProperties.WriteNoResponse,
GattPermissions.Read | GattPermissions.Write
);
var notifyCharacteristic = service.AddCharacteristic
(
notifyCharacteristicGuid,
CharacteristicProperties.Indicate | CharacteristicProperties.Notify,
GattPermissions.Read | GattPermissions.Write
);
IDisposable notifyBroadcast = null;
notifyCharacteristic.WhenDeviceSubscriptionChanged().Subscribe(e =>
{
var #event = e.IsSubscribed ? "Subscribed" : "Unsubcribed";
if (notifyBroadcast == null)
{
notifyBroadcast = Observable
.Interval(TimeSpan.FromSeconds(1))
.Where(x => notifyCharacteristic.SubscribedDevices.Count > 0)
.Subscribe(_ =>
{
Debug.WriteLine("Sending Broadcast");
var dt = DateTime.Now.ToString("g");
var bytes = Encoding.UTF8.GetBytes("SendingBroadcast");
notifyCharacteristic.Broadcast(bytes);
});
}
});
characteristic.WhenReadReceived().Subscribe(x =>
{
var write = "HELLO";
// you must set a reply value
x.Value = Encoding.UTF8.GetBytes(write);
x.Status = GattStatus.Success; // you can optionally set a status, but it defaults to Success
});
characteristic.WhenWriteReceived().Subscribe(x =>
{
var write = Encoding.UTF8.GetString(x.Value, 0, x.Value.Length);
Debug.WriteLine("in WhenWriteReceived() value: " + write);
// do something value
});
await server.Start(new AdvertisementData
{
LocalName = "DariusServer",
ServiceUuids = new List<Guid>() { serverServiceGuid }
});
I am using this app to scan for my advertisement data:
https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp
I can't discover my app with it. I don't know what I'm doing wrong? I am testing with a real device, SM-T350 tablet
I spent countless hours to get this plugin to work with no luck. But this native code works for anyone else who has the same problem:
private async Task AndroidBluetooth()
{
try
{
await Task.Delay(5000); // just to make sure bluetooth is ready to go, this probably isn't needed, but good for peace of mind during testing
BluetoothLeAdvertiser advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.Balanced)
//.SetTimeout(10000)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(Java.Util.UUID.FromString("your UUID here"))).Build();
MyAdvertiseCallback callback = new MyAdvertiseCallback();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
// put a break point here, in case something goes wrong, you can see why
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
}
Just to note, it wouldn't work if if I included the device name, because the bluetooth transmission would be too large in that case with a service UUID (max 31 bytes I believe).

Exception when converting local realm to synced realm in Xamarin.iOS

Be assured I have seen all the answer for the very same questions .
following this link enter switch-from-local-to-synced-realm
I have a local realm from where I want to copy to synced realm .
public void InitiateSyncForPost()
{
realm = Realm.GetInstance(ConfigForSync);
//realm.WriteAsync((Realm obj) =>
//{
// var realmOld = Realm.GetInstance(Config);
// var ding = realmOld.All<Post>().ToList();
// var dang = realmOld.All<Comment>().ToList();
// var ting = realmOld.All<ImageData>().ToList();
// foreach (var t in dang)
// {
// obj.Add(t, false);
// }
// foreach (var t in ding)
// {
// obj.Add(t, false);
// }
//foreach (var t in ting)
// {
// obj.Add(t, false);
// }
//});
realm.Write(() => {
var realmOld = Realm.GetInstance(Config);
var ding = realmOld.All<Post>().ToList();
var dang = realmOld.All<Comment>().ToList();
var ting = realmOld.All<ImageData>().ToList();
realmOld.Dispose();
foreach (var t in ting)
{
realm.Add(t, true);
}
foreach (var t in dang)
{
realm.Add(t, true);
}
foreach (var t in ding)
{
realm.Add(t, true);
}
});
}
as you can see I tried both async write and write method .
both are giving me exception
When I am using WriteAsync
I get exception something like condition not met array length == 1 (don't exactly remember )
When I am using current code I am getting this exception
Realms.Exceptions.RealmObjectManagedByAnotherRealmException: Cannot start to manage an object with a realm when it's already managed by another realm
Is there anything wrong I am doing in copying the object from local realm to synced realm ?
You need to create a non-managed copy of your RealmObject subsclass before adding it to the new Realm.
You can do this manually or via a helper extension.
Via NonManagedCopy extension from RealmJson.Extensions(1):
var nonSyncedRealm = Realm.GetInstance(nonSyncedRealmConfig);
var syncedRealm = Realm.GetInstance(syncedRealmConfig);
var nonSyncedAll = nonSyncedRealm.All<ARealmClass>();
syncedRealm.Write(() =>
{
foreach (var realmObject in nonSyncedAll)
{
var syncedObject = realmObject.NonManagedCopy<ARealmClass>();
syncedRealm.Add(syncedObject, true);
}
});
Manual Copy:
Assign each property of your RealmObject subclass to a newly instanced non-managed object.
syncedRealm.Write(() =>
{
foreach (var realmObject in nonSyncedAll)
{
var syncedObject = new ARealmClass
{
Id = realmObject.Id,
Name = realmObject.Name,
~~~~~~
~~~~~~
~~~~~~
~~~~~~
};
syncedRealm.Add(syncedObject, true);
}
});
(1) Disclaimer RealmJson.Extensions is an extension I wrote, available via nuget package or source:
re: https://sushihangover.github.io/Realm.Json.Extensions/

Detecting native API calls in javascript for Object

I am trying to build an analysis program for static analysis on the java script file provided to the program using esprima and estraverse. I want to make out and differentiate between the local functions defined by user and calls to the native library for Objects like 'Object.prototype() , Object.getOwnProperties()'.
var fs = require('fs');
var esprima = require('esprima');
var estraverse = require('estraverse');
var functionsStats = {}; //1
var addStatsEntry = function(funcName) { //2
if (!functionsStats[funcName]) {
functionsStats[funcName] = {calls: 0, declarations:0};
}
};
var filename = process.argv[2];
console.log('Processing', filename);
var ast = esprima.parse(fs.readFileSync(filename));
estraverse.traverse(ast, {
enter: function(node) {
if (node.type === 'FunctionDeclaration') {
addStatsEntry(node.id.name); //4
functionsStats[node.id.name].declarations++;
} else if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
addStatsEntry(node.callee.name);
functionsStats[node.callee.name].calls++; //5
}
}
});
Not sure how to differentiate the native API calls.
Any hint would help. Thanks in anticipation.

How can I create a MetadataWorkspace using metadata loading delegates?

I followed this example Changing schema name on runtime - Entity Framework where I can create a new EntityConnection from a MetaDataWorkspace that I then use to construct a DbContext with a different schema, but I get compiler warnings saying that RegisterItemCollection method is obsolete and to "Construct MetadataWorkspace using constructor that accepts metadata loading delegates."
How do I do that? Here is the code that is working but gives the 3 warnings for the RegsiterItemCollection calls. I'm surprised it works since warning says obsolete not just deprecated.
public static EntityConnection CreateEntityConnection(string schema, string connString, string model)
{
XmlReader[] conceptualReader = new XmlReader[]
{
XmlReader.Create(
Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(model + ".csdl")
)
};
XmlReader[] mappingReader = new XmlReader[]
{
XmlReader.Create(
Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(model + ".msl")
)
};
var storageReader = XmlReader.Create(
Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(model + ".ssdl")
);
//XNamespace storageNS = "http://schemas.microsoft.com/ado/2009/02/edm/ssdl"; // this would not work!!!
XNamespace storageNS = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl";
var storageXml = XElement.Load(storageReader);
foreach (var entitySet in storageXml.Descendants(storageNS + "EntitySet"))
{
var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
if (schemaAttribute != null)
{
schemaAttribute.SetValue(schema);
}
}
storageXml.CreateReader();
StoreItemCollection storageCollection =
new StoreItemCollection(
new XmlReader[] { storageXml.CreateReader() }
);
EdmItemCollection conceptualCollection = new EdmItemCollection(conceptualReader);
StorageMappingItemCollection mappingCollection =
new StorageMappingItemCollection(
conceptualCollection, storageCollection, mappingReader
);
//var workspace2 = new MetadataWorkspace(conceptualCollection, storageCollection, mappingCollection);
var workspace = new MetadataWorkspace();
workspace.RegisterItemCollection(conceptualCollection);
workspace.RegisterItemCollection(storageCollection);
workspace.RegisterItemCollection(mappingCollection);
var connectionData = new EntityConnectionStringBuilder(connString);
var connection = DbProviderFactories
.GetFactory(connectionData.Provider)
.CreateConnection();
connection.ConnectionString = connectionData.ProviderConnectionString;
return new EntityConnection(workspace, connection);
}
I was able to get rid of the 3 warning messages. Basically it wants you to register the collections in the constructor of the MetadataWorkspace.
There are 3 different overloads for MetadataWorkspace, I chose to use the one which requires to to supply a path (array of strings) to the workspace metadata. To do this I saved readers to temp files and reloaded them.
This is working for me without any warnings.
public static EntityConnection CreateEntityConnection(string schema, string connString, string model) {
var conceptualReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".csdl"));
var mappingReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".msl"));
var storageReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(model + ".ssdl"));
XNamespace storageNS = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl";
var storageXml = XElement.Load(storageReader);
var conceptualXml = XElement.Load(conceptualReader);
var mappingXml = XElement.Load(mappingReader);
foreach (var entitySet in storageXml.Descendants(storageNS + "EntitySet")) {
var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
if (schemaAttribute != null) {
schemaAttribute.SetValue(schema);
}
}
storageXml.Save("temp.ssdl");
conceptualXml.Save("temp.csdl");
mappingXml.Save("temp.msl");
MetadataWorkspace workspace = new MetadataWorkspace(new List<String>(){
#"temp.csdl",
#"temp.ssdl",
#"temp.msl"
}
, new List<Assembly>());
var connectionData = new EntityConnectionStringBuilder(connString);
var connection = DbProviderFactories.GetFactory(connectionData.Provider).CreateConnection();
connection.ConnectionString = connectionData.ProviderConnectionString;
return new EntityConnection(workspace, connection);
}
Not wanting to create temp files which slows the process down, I found an alternate answer to this is fairly simple. I replaced these lines of code -
//var workspace2 = new MetadataWorkspace(conceptualCollection, storageCollection, mappingCollection);
var workspace = new MetadataWorkspace();
workspace.RegisterItemCollection(conceptualCollection);
workspace.RegisterItemCollection(storageCollection);
workspace.RegisterItemCollection(mappingCollection);
with this one line of code -
var workspace = new MetadataWorkspace(() => conceptualCollection, () => storageCollection, () => mappingCollection);
and that works fine.

Transforming T4 from command line doesn't add generated files to csproj

I am trying to transform a T4 template from command line using TextTransform.exe with the following command line:
"%ProgramFiles(x86)%\Common Files\Microsoft Shared\TextTemplating\10.0\TextTransform.exe"
-out .\MyProj\MyT4.cs
-I "%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\Templates\Includes"
-a !NamespaceHint!MyNameSpace
-dp T4VSHost!Microsoft.Data.Entity.Design.VisualStudio.Directives.FallbackT4VSHostProcessor!"%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\Microsoft.Data.Entity.Design.dll"
.\MyProj\MyT4.tt
Results:
No error messages
The %ERRORLEVEL% is 0 on completion.
The files are generated
The .csproj does not change
The problem is point 4. This may be expected, since the .csproj isn't a part of the above command line, however, I can't find any parameters which can accept it.
What am I doing wrong or what should I be doing instead?
P.S. When I use the button in Visual Studio the process works as excepted (new files are added to project).
Solved using the following method:
Added these parameters to command line:
-a !!ProjPath!.\MyProj\MyProj.csproj -a !!T4Path!.\MyProj\MyT4.tt
Changed the include directory parameter to a local path:
-I ".\Dependencies"
Copied EF.Utility.CS.ttinclude to that path and made the following changes:
3.1. Replaced:
public static EntityFrameworkTemplateFileManager Create(object textTransformation)
{
DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
IDynamicHost host = transformation.Host;
#if !PREPROCESSED_TEMPLATE
if (host.AsIServiceProvider() != null)
{
return new VsEntityFrameworkTemplateFileManager(transformation);
}
#endif
return new EntityFrameworkTemplateFileManager(transformation);
}
with
public static EntityFrameworkTemplateFileManager Create(object textTransformation)
{
DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
IDynamicHost host = transformation.Host;
#if !PREPROCESSED_TEMPLATE
if (host.AsIServiceProvider() != null)
{
return new VsEntityFrameworkTemplateFileManager(transformation);
}
#endif
return new EFTemplateFileManagerPlus(transformation);
}
(Last return has the change)
Add added this class to the file:
private sealed class EFTemplateFileManagerPlus : EntityFrameworkTemplateFileManager
{
private Action<IEnumerable<string>> projectSyncAction;
private readonly string _projPath;
private readonly string _t4Name;
public EFTemplateFileManagerPlus(object textTemplating)
: base(textTemplating)
{
var projPath = _textTransformation.Host.ResolveParameterValue("", "", "ProjPath");
var t4Path = _textTransformation.Host.ResolveParameterValue("", "", "T4Path");
_projPath = System.IO.Path.GetFullPath(projPath);
_t4Name = System.IO.Path.GetFileName(t4Path);
projectSyncAction = files => SyncCsProjFile(_projPath, _t4Name, files);
}
public static void SyncCsProjFile(string csProjFilePath, string t4FileName, IEnumerable<string> files)
{
files = files.Select(f => System.IO.Path.GetFileName(f)).Distinct().ToList();
var csProjDocument = new XmlDocument();
csProjDocument.Load(csProjFilePath);
var root = csProjDocument.DocumentElement;
XmlElement itemGroup = root.ChildNodes.OfType<XmlElement>()
.Where(n => n.Name == "ItemGroup")
.SelectMany(n => n.ChildNodes.OfType<XmlNode>()
.Where(c => c.Name == "Compile")
)
.Select(c => c.ParentNode)
.FirstOrDefault() as XmlElement;
if (itemGroup == null)
{
itemGroup = csProjDocument.CreateNode(XmlNodeType.Element, "ItemGroup", null) as XmlElement;
root.AppendChild(itemGroup);
}
var codeFiles = itemGroup.ChildNodes.OfType<XmlElement>()
.Where(c =>
c.Name == "Compile"
&& c.HasAttribute("Include") && !String.IsNullOrEmpty(c.GetAttribute("Include")))
.ToList();
var dependantFiles = codeFiles
.Where(f =>
f.ChildNodes.OfType<XmlElement>().Any(c =>
c.Name == "DependentUpon"
&& c.InnerText == t4FileName)
).ToList();
// Remove redundant files
foreach (var node in dependantFiles)
{
if (!files.Contains(node.GetAttribute("Include")))
itemGroup.RemoveChild(node);
}
// Add missing files
foreach (var name in files)
{
if (!dependantFiles.Any(node => node.GetAttribute("Include") == name))
{
var node = csProjDocument.CreateNode(XmlNodeType.Element, "Compile", null) as XmlElement;
node.SetAttribute("Include", name);
itemGroup.AppendChild(node);
var node2 = csProjDocument.CreateNode(XmlNodeType.Element, "DependentUpon", null) as XmlElement;
node2.InnerText = t4FileName;
node.AppendChild(node2);
}
}
SaveClean(csProjDocument, csProjFilePath);
}
static private void SaveClean(XmlDocument doc, string path)
{
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
settings.Indent = true;
settings.IndentChars = " ";
settings.NewLineChars = "\r\n";
settings.NewLineHandling = NewLineHandling.Replace;
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
using (XmlWriter writer = XmlWriter.Create(sb, settings))
{
doc.Save(writer);
}
var newXml = sb.ToString().Replace("encoding=\"utf-16\"", "encoding=\"utf-8\"").Replace(" xmlns=\"\"", string.Empty);
System.IO.File.WriteAllText(path, newXml, Encoding.UTF8);
}
public override IEnumerable<string> Process(bool split)
{
var generatedFileNames = base.Process(split);
projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));
return generatedFileNames;
}
}
Now the project file sync works using TextTransform.exe too.
I believe the command line host cannot change the .csproj. Only the VS host can do it, through the access to the DTE object.

Resources