We are writing a Visual Studio (Xamarin) cross-platform application that will share quite a bit of functionality with the next application we write, so we wanted to put that shared functionality into a "library" so we could test it, share it easily, etc.
The only way we found to write a cross-platform library, is to create it using the standard Xamarin DependencyService paradigm, turn it into a NuGet package, and then load that package into our main app. For better or worse, we did this before Microsoft provided a template for generating NuGet libraries like this, so we had to roll it ourselves.
This works fine for Android, but now I'd like to get the same code working for iOS and it's simply not working. No errors, no warnings, but when I run the app and call
Client = DependencyService.Get<IClient>();
It simply returns null. If I look at the Output/Debug window, it is loading the 'Company.Client.Abstractions.dll' (the root DLL containing the definition of IClient) but not the iOS-specific 'Company.Client.dll' (which contains the iOS-specific implementation of IClient).
Here's my nuspec file:
<?xml version="1.0"?>
<package >
<metadata>
<id>Client</id>
<version>0.0.35</version>
<authors>Me</authors>
<owners>Company</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Stuff</description>
<releaseNotes>Initial release</releaseNotes>
<copyright>Copyright 2017 Company</copyright>
<dependencies>
<group targetFramework="MonoAndroid">
<dependency id="Xamarin.Forms" version="2.3.4.247" />
</group>
<group targetFramework="Xamarin.iOS10">
<dependency id="Xamarin.Forms" version="2.3.4.247" />
</group>
<group targetFramework="uap">
<dependency id="Xamarin.Forms" version="2.3.4.247" />
</group>
</dependencies>
</metadata>
<files>
<!-- Cross-platform reference assemblies -->
<file src="Company.Client.Abstractions\bin\Debug\Company.Client.Abstractions.dll" target="lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Company.Client.Abstractions.dll" />
<file src="Company.Client.Abstractions\bin\Debug\Company.Client.Abstractions.pdb" target="lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Company.Client.Abstractions.pdb" />
<!-- iOS reference assemblies -->
<file src="Company.Client.iOS\bin\iPhone\Debug\Company.Client.dll" target="lib\Xamarin.iOS10\Company.Client.dll" />
<file src="Company.Client.iOS\bin\iPhone\Debug\Company.Client.pdb" target="lib\Xamarin.iOS10\Company.Client.pdb" />
<!-- Android reference assemblies -->
<file src="Company.Client.Android\bin\Debug\Company.Client.dll" target="lib\MonoAndroid10\Company.Client.dll" />
<file src="Company.Client.Android\bin\Debug\Company.Client.pdb" target="lib\MonoAndroid10\Company.Client.pdb" />
<!-- UWP reference assemblies -->
<file src="Company.Client.UWP\bin\Debug\Company.Client.dll" target="lib\UAP10\Company.Client.dll" />
<file src="Nuvectra.Client.UWP\bin\Debug\Company.Client.pdb" target="lib\UAP10\Company.Client.pdb" />
</files>
</package>
I'm thinking there are two classes of possible problems:
I'm generating the DLL wrong
I'm generating the application wrong
Looking at the generated DLLs, the Android DLL is 28k long, the iOS DLL is 23k, so it doesn't look like the iOS DLL is empty. Is there a tool that would let me inspect the iOS DLL and make sure it has the necessary entry point(s)?
The Project that generates the iOS DLL has these settings:
Target framework: Xamarin.iOS
Output type: Class Library
Condition compilation symbols: __ UNIFIED__;__ MOBILE__;__ IOS__
Platform: Active (Any CPU)
The application that is using the DLL has these settings for the iOS Project:
SDK Version: Default
Linker Behavior: Don't Link
Platform: Active (iPhone)
Supported Architectures: ARMv7 + ARM64
Target framework: Xamarin.IOS
Output type: Console Application
Conditional compilation symbols: __ UNIFIED__;__ MOBILE__;__ IOS__
The interface definition in my 'Abstractions' project looks like this:
namespace Company.Client
{
public abstract class IClient
{
void abstract function();
}
}
and the implementation in my iOS project looks like this:
using Company.Client.iOS;
[assembly: Xamarin.Forms.Dependency (typeof (IosClient))]
namespace Company.Client.iOS
{
public class IosClient : IClient
{
void override function();
}
}
We tried many different ways to create a NuGet package with platform-specific code loaded using DependencyService and every single way had the same problem - worked fine under Android, failed under iOS.
The solution is to create a class in the iOS library that is not loaded using DependencyService, and then in the iOS application, create an instance of that class. That's enough to convince the iOS application that it actually needs to load the iOS library DLL, and then the rest of the DependencyService magic works correctly.
Related
There is a 3rd party library I need to use, that includes a class decorated with the [Application] attribute. This causes compiler errors since I have my own application class that uses an [Application] attribute. I would like my application class to inherit from the 3rd party lib's application class.
public class MyApplication : ThirdPartyApplication
{
}
however since I can't decorate my class with the [Application] attribute I have no way to specify in the Manifest that it should run "MyApplication" and not "ThirdPartyApplication".
If I manually add an entry into AndroidManifest.xml
<application
android:name="com.your.packagename.MyApplication"
android:icon="#drawable/luncher_icon"
android:label="#string/app_name">
It will get replaced after the project gets built with
<application
android:name="mdxxx.ThirdPartyApplication"
android:icon="#drawable/luncher_icon"
android:label="#string/app_name">
Does anyone know how to handle this situation in Xamarin Android?
Keep in mind that the 3rd party library can not be modified.
An alternate solution would be a way to disable all manifest generating attributes and manually create the AndroidManifest. There does not seem to be any way to do this either.
The below post is the exact situation I am having but in pure Android. Note that due to the above issues that this solution will not work for Xamarin.
how-to-handle-multiple-application-classes-in-android
So I was able to figure out a solution but it boarders on being a hack.
In short the android manifest is going to be modified using MSBuild.
Step 1: Have your custom application class inherit from the the Third Party lib's custom application and decorate your class with the "Register" tag.
[Register("com.namespace.MyApplication")]
public class MyApplication : ThirdPartyApplication
{
}
Step 2: Have your project include the RoslynCodeTaskFactory NuGet package
Step 3: Unload your project, then add the following in the Project tag
<UsingTask TaskName="UpdateManifest" TaskFactory="CodeTaskFactory" AssemblyFile="$(RoslynCodeTaskFactory)" Condition=" '$(RoslynCodeTaskFactory)' != '' ">
<ParameterGroup>
<AndroidManifestFilename ParameterType="System.String" Required="true" />
<ApplicationName ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs"><![CDATA[
XmlDocument doc = new XmlDocument();
doc.Load(AndroidManifestFilename);
XmlNode node = doc.DocumentElement.SelectSingleNode("/manifest/application");
node.Attributes["android:name"].InnerText = ApplicationName;
doc.Save(AndroidManifestFilename);
]]></Code>
</Task>
</UsingTask>
<Target Name="CleanManifest" AfterTargets="_GenerateJavaStubs">
<UpdateManifest AndroidManifestFilename="$(ProjectDir)\obj\$(Configuration)\android\AndroidManifest.xml" ApplicationName="com.namespace.MyApplication" />
</Target>
Step 4: Reload the project and build. The manifest should now point to custom application class. If you get a class not found runtime exception most likely you forgot to add [Register] from Step 1.
How can I expose composite components to JBoss through modules? The component works correctly in the same project, the project can correctly refer to other classes within the depended on module, but attempting to use a composite component throws out an error like
/common/common.xhtml #30,36 <NAMESPACE:COMPONENT> Tag Library supports namespace: http://java.sun.com/jsf/composite/NAMESPACE, but no tag was defined for name: COMPONENT
I've currently modified my jboss-deployment-structure.xml to the following, but it hasn't made any difference.
<module name="deployment.CompositeComponentProject.war" export="true" meta-inf="export">
<imports>
<include path="**"/>
</imports>
</module>
I have a problem with NLog for logging its internal logs with this configuration
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwExceptions="true"
internalLogFile="${basedir}/App_Data/NLog.log"
internalLogLevel="Trace">
<targets>
<target name="debug"
xsi:type="File"
fileName="${basedir}/App_Data/Site.log" />
</targets>
<rules>
<logger name="*"
writeTo="debug" />
</rules>
</nlog>
The target "debug" is working well, but the internalLogFile is only working if I set it for exemple to "D:/NLog.log".
Any idea why this happening?
You can't use layout renderers ${...} in the internalLogFile property. They are for a target's layout only:
<target layout="${...}" />
Try to use relative path like "..\App_Data\NLog.log"
Update NLog 4.6 enables some simple layouts.
The internalLogFile attribute needs to be set to an absolute path and the executing assembly needs to have permission to write to that absolute path.
The following worked for me.
Create a folder somewhere - e.g. the route of your c: drive, e.g. c:\logs
Edit the permissions of this folder and give full control to everyone
Set your nlog config: internalLogFile="C:\logs\nlog.txt"
Remember to clean up after yourself and not leave a directory with those sorts of permissions on
NLog ver. 4.6 add support for environment-variables like %appdata% or %HOME%, and using these basic layouts in internalLogFile=:
${currentdir}
${basedir}
${tempdir}
NLog ver. 4.7 also adds this:
${processdir}
See also: https://github.com/NLog/NLog/wiki/Internal-Logging
from this link I think the path is absolute
I read a really cool blog about using Autofac to completely decouple an application. But try as I might (and being horribly new to all this), I just couldn't get Autofac to gel.
I turned to Unity from the MS Patterns & Practices Enterprise Library and that went a whole lot better. To make things unnecessarily hard for myself, I separated out all my stuff into projects as:
UnityDi (Console app)
UnityDi.Contracts (Interfaces)
UntiyDi.Domain (Classes)
UnityDi.Repositories (Data Access)
UnityDi.Services (Access to repository through a service layer)
I used XML configuration to pony up Unity:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<assembly name="UnityDi.Contracts" />
<assembly name="UnityDi.Domain" />
<assembly name="UnityDi.Services" />
<assembly name="UnityDi.Repositories" />
<namespace name="UnityDi.Contracts" />
<namespace name="UnityDi.Domain" />
<namespace name="UnityDi.Services" />
<namespace name="UnityDi.Repositories" />
<container>
<register type="IUser" mapTo="User"></register>
<register type="IUserService" mapTo="UserService"></register>
<register type="IUserRepository" mapTo="UserRepository"></register>
</container>
</unity>
</configuration>
And got that into a running app, no worries:
private static readonly IUnityContainer Container = new UnityContainer();
...
Container.LoadConfiguration();
BUT in order to do so, I need a reference to all the above projects from my console app.
Is there a way to make the app only ever have a reference to UnityDi.Contracts (the interfaces)? Then the app is well and truly decoupled (admittedly with a sledgehammer).
I hope that is enough of an explanation, I'm totally new to this and I'm being extreme like this to facilitate better learning.
I suspect the reason it looks like you need project references is that without them, VS won't copy the assemblies into your apps bin folder when you hit F5. How would it, it has no way of knowing you need them!
The project references are the quickest solution to the problem. The other thing you could do is add a post-build step to copy the appropriate DLLs to end up in the right directory so you can run the app.
I've created a modular application where a parent SWF loads a number of other SWFs on demand. For optimization, I've created a standard RSL.
I've compiled common code into a swc, and recompiled the application swfs to reference this swc, using the following in my build.xml ANT task (for each swf in my application):
<static-link-runtime-shared-libraries>false</static-link-runtime-shared-libraries>
<runtime-shared-library-path path-element="${lib.loc}/RSL.swc">
<url rsl-url="RSL.swf"/>
</runtime-shared-library-path>
I've extracted RSL.swf from RSL.swc and put this on my webserver in the same directory as the application swfs and container html file.
Now when I load my application, I get the message:
VerifyError: Error #1014: Class mx.core:BitmapAsset could not be found.
I can see this class included in the classes in RSL.swc / RSL.swf.
I've used fiddler to observe what is happening and I can see my Application swf file is loaded, but no attempt is made to get the RSL.swf.
Having set up the Application.swf file to use RSLs, I would have expected it to attempt loading RSL.swf before initialising, however this doesn't happen. Can anyone suggest why?
From http://livedocs.adobe.com/flex/3/html/help.html?content=rsl_02.html:
"You cannot use RSLs in ActionScript-only projects if the base class is Sprite or MovieClip. RSLs require that the application's base class, such as Application or SimpleApplication, understand RSL loading."
As my base class was Sprite, I had this error.
In my case, it was better to compile all the required classes into my Application swf file, using the following steps:
use compc to create a SWC with the files I want to include in my Application swf file
use mxmlc with include-libraries pointing to the SWC file to include. Generate a linked file report (xml) using link-report
compile each additional child swf with load-externs pointing to the linked file report (xml) - this excludes the files linked to the Application.swf from being compiled into each of the child swfs
To achieve step 1:
<!-- We define the global classes which will be compiled into the parent Application
swf, but excluded from the tool swfs. As pure actionscript projects with base
class of Sprite can't usually use RSLs, we are forcing these classes to be loaded
into the parent application, and excluded from the child applications, allowing an
"Rsl-like" optimisation -->
<fileset id="rsl.inclusions" dir="${main.src.loc}">
<include name="${main.src.loc}/path1/**/*.as"/>
<include name="${main.src.loc}/path2/**/*.as"/>
...
</fileset>
<pathconvert property="rsl.classes" pathsep=" " refid="rsl.inclusions">
<chainedmapper>
<globmapper from="${main.src.loc}\*" to="*"/>
<mapper type="package" from="*.as" to="*"/>
</chainedmapper>
</pathconvert>
<!-- Compile SWC -->
<compc output="${lib.loc}/MySwc.swc"
include-classes="${rsl.classes}">
<static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries>
<source-path path-element="${main.src.loc}"/>
</compc>
To achieve step 2:
<mxmlc file="${main.src.loc}/pathToApp/Application.as"
output="${bin.loc}/Application.swf"
debug="${debug}"
use-network="true"
link-report="WorkbenchLinkReport.xml"
fork="true">
<compiler.source-path path-element="${main.src.loc}" />
<static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries>
<include-libraries dir="${lib.loc}" append="true">
<include name="MySwc.swc" />
</include-libraries>
</mxmlc>
To achieve step 3:
<mxmlc file="${main.src.loc}/pathToChildSwf1/Child1.as"
output="${bin.loc}/Child1.swf"
debug="${debug}"
load-externs="WorkbenchLinkReport.xml"
fork="true">
<compiler.source-path path-element="${main.src.loc}" />
<static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries>
<compiler.headless-server>true</compiler.headless-server>
</mxmlc>
Another handy tip: using fork="true" prevents the Java VM running out of memory where many swfs are being compiled.
Hope this is helpful!