resources

Xamarin Forms, the MVVMLight Toolkit and I: loading bundled assets in the Forms project

Xamarin Forms, the MVVMLight Toolkit and I: loading bundled assets in the Forms project

The scenario …

The reason I came up with this is that I am writing on an Xamarin.Forms web reader app. It is an app that uses a  WebView to display the contents of web articles. Of course, I am using a CSS-file to style the content that gets displayed. I am using the default font of every platform, plus some platform specific settings in there. The easiest way to get it working right is to give every platform its own CSS-file. In the Xamarin.Forms project however, I just want to call one method that gets the thing done.

For this scenario, the well documented Files access in Xamarin.Forms does not work.

This post will not yet be reflected in my ongoing XfMvvmLight project on Github as I have another one building on top of this in my queue. Once the second one is written, the project will show these changes, too. This post will contain the full classes however, so you could C&P them if you want/need.

DependencyService and another interface

If you are following this series already, you might already know that the easiest way to achieve my goal is to use the built-in Xamarin.FormsDependencyService and the needed interface with the native implementations.

So let’s start with the interface:

namespace XfMvvmLight.Abstractions
{
    public interface IAssetPathHelper
    {
        string GetResourceFolderPath(string folderName, bool forWeb = false);

        string GetResourcePath(bool forWeb = false);

        string GetResourceFilePath(string folder, string fileName, bool forWeb = false);
    }
}

The interface dictates three string-returning methods that will either return the base path of the platform resources, a specific folder or the full path to the bundled file. This interface covers most usage scenarios I came across. Feel free to leave any feedback if I am missing out a common one.

The only thing left to do is to register the interface in our ViewModelLocator, like we did already before in the RegisterServices() method:

var assetPathHelper = DependencyService.Get();
SimpleIoc.Default.Register(()=> assetPathHelper);

We are getting the platform implementation via the built in DependecyService and assign in to our Xamarin.Forms interface (like we have done already before). By registering it with our SimpleIoc instance, we can now use it wherever we want in our Xamarin.Forms project.

Platform implementations

Android

If you add files in the Resources folder, you can easily access them via the Resource class in your Android project. However, files like CSS-files are normally placed within the Assets folder of your Xamarin.Android project.

Depending on the usage scenario, we have two ways to access the files in the ‘Assets’ folder. If we are residing in the Xamarin.Android project and want to access the content of those bundled assets, we are able to access them using the Android.App.Context.Assets property and assign it to the Android.Content.Res.AssetManager class. We can then use streams to get the data contained in those files.

This does not help however if we want to access those files from a WebView (both in the Android and the Xamarin.Forms project), that’s why we have to use the ‘file:///android_asset‘ uri-scheme. Here is the platform implementation:

using XfMvvmLight.Abstractions;
using XfMvvmLight.Droid.PlatformImplementation;
using System.Diagnostics;
using System.IO;

[assembly: Xamarin.Forms.Dependency(typeof(AssetPathHelper))]
namespace XfMvvmLight.Droid.PlatformImplementation;
{
    public class AssetPathHelper : IAssetPathHelper
    {
        public string GetResourceFolderPath(string folderName, bool forWeb = false)
        {
            return Path.Combine(GetResourcePath(),folderName);
        }

        public string GetResourcePath(bool forWeb = false)
        {
            //reminding ourselves to double check if this way is really necessary
            if (!forWeb)
            {
                Debug.WriteLine("**********************************");
                Debug.WriteLine("You should consider using AssetManager if you are not using this in a WebView.");
                Debug.WriteLine("See: https://developer.xamarin.com/guides/android/application_fundamentals/resources_in_android/part_6_-_using_android_assets/");
                Debug.WriteLine("**********************************");
            }
            
            //but we are always returning the uri scheme 
            return $"file:///android_asset"; 
        }

        public string GetResourceFilePath(string folder, string fileName, bool forWeb = false)
        {
            var folderPath = string.IsNullOrEmpty(folder) ? GetResourcePath() : GetResourceFolderPath(folder);

            return Path.Combine(folderPath,fileName);
        }
    }
}

The implementation is pretty straight forward. Although we could call all three methods, the one we use probably the most is the GetResourceFilePath method. It will give us the complete path to the resource file, which we can then use in our calling code of our Xamarin.Forms project.

By using the Path.Combine method we make sure we get a valid file path string, which is exactly what we need if we are accessing assets in this way. As most of the scenarios for accessing assets could be easily covered by using AssetManager (see above), I am printing a reminder message that it exists to the output window of VisualStudio.

Important: you have to make sure the Build Action of your files is set to AndroidAsset, otherwise you’ll see nothing, in some scenarios it will even throw exceptions. This accounts for the AssetManager as well as for the AssetPathHelper implementations.

iOS

On iOS, we are able to access bundled assets via the NSBundle class. The implementation is even easier than the one for Android, as this is the only way to get those assets. That’s why we are ignoring the forWeb parameter in this case. Here is the implementation:

using System.IO;
using Foundation;
using XfMvvmLight.Abstractions;
using XfMvvmLight.iOS.PlatformImplementation;

[assembly: Xamarin.Forms.Dependency(typeof(AssetPathHelper))]
namespace XfMvvmLight.iOS.PlatformImplementation
{
    //forWeb is ignored on iOS!
    public class AssetPathHelper : IAssetPathHelper
    {
        public string GetResourceFolderPath(string folderName, bool forWeb = false)
        {
            return Path.Combine(GetResourcePath(), folderName);
        }

        public string GetResourcePath(bool forWeb = false)
        {
            return NSBundle.MainBundle.BundlePath;
        }

        public string GetResourceFilePath(string folder, string fileName, bool forWeb = false)
        {
            var folderPath = string.IsNullOrEmpty(folder) ? GetResourcePath() : GetResourceFolderPath(folder);

            return Path.Combine(folderPath, fileName);
        }
    }
}

Important: Make sure your files have the Build Action set to BundleResource, because otherwise you will once again get some errors flying around your head.

UWP

The implementation of the UWP Assets is once again the one with the most places involved. Let’s have a look at the implementation itself first:

using System.IO;
using XfMvvmLight.Abstractions;

namespace XfMvvmLight.UWP.PlatformImplementations
{
    public class AssetPathHelper : IAssetPathHelper
    {
        public string GetResourceFolderPath(string folderName, bool forWeb = false)
        {
            return Path.Combine(GetResourcePath(forWeb),folderName);
        }

        public string GetResourcePath(bool forWeb = false)
        {
            if (forWeb)
            {
                return $"ms-appx-web:///";
            }
            else
            {
                return $"ms-appx:///";
            }
        }

        public string GetResourceFilePath(string folder, string fileName, bool forWeb = false)
        {
            var folderPath = string.IsNullOrEmpty(folder) ? GetResourcePath(forWeb) : GetResourceFolderPath(folder, forWeb);

            return Path.Combine(folderPath,fileName);
        }
    }
}

The UWP platform uses a separate uri-scheme for all web related things. That’s where the  forWeb parameter comes in handy. If we are not loading a bundled asset for the web, we can use this implementation for other resources as well (bundled placeholder images are a good example here).

The next step is to add the assembly again to the list of assemblies that must be included, like we have done before in the OnLaunched method within App.xaml.cs:

//modified for .NET Compile
//see https://developer.xamarin.com/guides/xamarin-forms/platform-features/windows/installation/universal/#Target_Invocation_Exception_when_using_Compile_with_.NET_Native_tool_chain
List<Assembly> assembliesToInclude =
    new List<Assembly>
    {
        typeof(OsVersionService).GetTypeInfo().Assembly,
        typeof(PlatformDialogService).GetTypeInfo().Assembly, 
        typeof(AssetPathHelper).GetTypeInfo().Assembly
    };

The last step involved in the UWP project is to register the implementation with the DependencyService
after the Xamarin.Forms framework is initialized:

Xamarin.Forms.DependencyService.Register<AssetPathHelper>();

The resources should be packed with a Build Action of Content for the UWP platform.

Back to the Xamarin.Forms project

Now that we have everything in place on our platform projects as well as our Xamarin.Forms project, we finally can start using these methods. Here is an example of loading a CSS-file into a string. We can pass this string together with an HTML-string into a HtmlWebViewSource:

private static string GetCssString(string cssFileName)
{
    var resourcePath = SimpleIoc.Default.GetInstance<IAssetPathHelper>().GetResourceFolderPath("HtmlResources", true);

    return $"<link rel=\"stylesheet\" href=\"{resourcePath}/{cssFileName}\">";
}

Summary

Using the DependencyService of Xamarin.Forms allows us once again to use platform specific functionality very easily. When we are working with WebView and HTML, this comes in handy. If you have other valid scenarios for this implementations or even ideas on how to improve them, feel free to leave a comment below or ping me on my social network accounts. Otherwise, I hope this post is helpful for some of you.

As this is the last post before xmas, I wish you all a merry xmas in addition to my traditional

Happy coding, everyone!

Posted by msicc in Android, Dev Stories, iOS, UWP, Xamarin, 1 comment
[Updated] A workaround for Xamarin Forms 2.5 bug that prevents resource declaration in App.xaml

[Updated] A workaround for Xamarin Forms 2.5 bug that prevents resource declaration in App.xaml

Update: Xamarin appearently solved this problem with Service Release 3 for Xamarin Forms 2.5. I can confirm it works in the app that caused me to write this post.

Additional note: the forms:prefix is no longer needed, just insert the <ResourceDictionary>tag.


If you have a Windows background like I do, one of the most normal things for applications is to create keyed Resources in App.xaml to make them available throughout the app. Something like this should look familiar:

<forms:ResourceDictionary >
    <viewModels:ViewModelLocator x:Key="Locator"></viewModels:ViewModelLocator>
    <forms:Color x:Key="MainAccentColor">#1e73be</forms:Color>
    <forms:Color x:Key="LightAccentColor">#61a1f1</forms:Color>
    <forms:Color x:Key="DarkAccentColor">#00488d</forms:Color>
    <forms:Color x:Key="MainBackgroundColor">#f4f4f4</forms:Color>
</forms:ResourceDictionary>

This is also possible in Xamarin.Forms. Sadly, Xamarin.Forms 2.5 introduced an ugly bug where this declarations throw an ArgumentException, telling us the key(s) already exist in the dictionary (see Bugzilla here). I can confirm that this bug affects at least UWP, Android and iOS applications which use such an implementation.

As this is a show-stopping bug, I had to find a way to work around it for the moment. In such cases, I always try to find a way that has only very little impact. For this particular bug, I just moved the declaration of the resources into the code-behind file, which keeps the rest of my code unchanged. I just created a method that does the work I originally had in the .xaml-file:

//needed because of Xamarin Bug  https://bugzilla.xamarin.com/show_bug.cgi?id=60788
private void CreateResourceDictionary()
{
    //making sure there is only one dictionary
    if (this.Resources == null)
        this.Resources = new ResourceDictionary();

    //making sure there is only one key
    if (!this.Resources.ContainsKey("Locator"))
    {
        this.Resources.Add("Locator", ViewModels.ViewModelLocator.Instance);
    }

    if (!this.Resources.ContainsKey("MainAccentColor"))
    {
        this.Resources.Add("MainAccentColor", Color.FromHex("#1e73be"));
    }

    if (!this.Resources.ContainsKey("LightAccentColor"))
    {
        this.Resources.Add("LightAccentColor", Color.FromHex("#61a1f1"));
    }

    if (!this.Resources.ContainsKey("DarkAccentColor"))
    {
        this.Resources.Add("DarkAccentColor", Color.FromHex("#00488d"));
    }

    if (!this.Resources.ContainsKey("MainBackgroundColor"))
    {
        this.Resources.Add("MainBackgroundColor", Color.FromHex("#f4f4f4"));
    }
}

This makes the application running again like it did before. Once the bug in Xamarin.Forms is fixed, I just have to delete this method and uncomment the XAML-declarations to get back to the state where I was prior to Xamarin.Forms 2.5.

If you are experiencing the same bug, I recommend to also comment on the Bugzilla-Entry (link).

As always, I hope this post is helpful for some of you.

Happy coding!

 

 

Posted by msicc in Android, Dev Stories, iOS, UWP, Xamarin, 4 comments

Xamarin: Resources of an Android app project

As I mentioned already in my first blog post about Xamarin, Android has a different project structure – even if you use Xamarin.

A very important part of this structure are resources. We have different kind of resources in an Android app:

  • Icons & Images
  • Layout (XML files)
  • Values (string resource files)

Let’s start with Icons & Images.

As you can see in the solution window, there is a folder called ‘Resources’. Here you will put all kind of resources, like images, icons, layouts and so on.

The corresponding class for images in Android is called ‘drawable’, that holds a reference to’ Icon.png’. The project structure is based on that, that’s why the resources folder has all images inside the folder ‘drawable’. As Android has various screen sizes, you may have a folder for structure like ‘drawable’, ‘drawable-hdpi, ‘drawable-ldpi’ and so on. Android scales your image resources to match the screen automatically if you do not define alternate layouts.

To make your files available in your app, you need to set the Build Action to ‘Android Resource’:

Screenshot (278)

Let’s have a look to the Layout files:

Layout files are XML files that contain the UI of a page, control or a view. You can use these XML files for example to define items of a ListView or simply the application page. Every new project has a ‘Main.axml’ file.

There are two ways to create a layout. The first one is using the visual designer:

Screenshot (280)

This works for basic layouts like adding the button above. Also, if you don’t know the properties of a control you added, you will be able to add it here to get started.

If you are familiar with writing your UI in code (like XAML)  and want to do so in your Android app, just click the ‘Source’ tab at the bottom in the visual designer. You will see something like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/myButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>

If you want to add and modify a control, but don’t know how the properties are, this page has a list of all controls, which are called ‘widgets’ in Android. That’s also the corresponding namespace: android.widget.

Like in an Windows Phone app, you also have a string resource file in Android projects. This file is once again a XML file, but with a different structure:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, Click Me!</string>
<string name="app_name">gettingstarted</string>
</resources>

All strings need to be declared inside the <resources> tag. The definition is always like <string name=”yourstringname”>stringcontent</string>. Empty strings are not supported and will throw an error at building your project.

Let’s have a look on how we can work with our resources, based on our gettingstarted project. We have the following code inside our MainActivity class:

int count = 1;

		protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);

			// Set our view from the "main" layout resource
			SetContentView (Resource.Layout.Main);

			// Get our button from the layout resource,
			// and attach an event to it
			Button button = FindViewById<Button> (Resource.Id.myButton);
			button.Text = GetString (Resource.String.hello);

			button.Click += delegate {
				button.Text = string.Format ("{0} clicks!", count++);
			};
		}

As you can see, the we are defining our view from our Layout file ‘Main.axml’ by using the SetContentView() method. The file is added to our resources list as Layout with the name we defined.

Our MainActivity does not know that we have a button inside our layout. To make the button visible to our MainActivity, we need to reference it. This is done by declaring a Button instance and using the FindViewById<T>(ResourceName) method.

If you have not given your button a name, now is the right time to do so. In our example the button has the name “myButton”.  The syntax is very important, make sure you add “@+id/” to the name.

android:id="@+id/myButton"

Now our button is visible to our MainActivity code page and can be accessed. The sample project references the button content in the Layout file:

android:text="@string/hello"

After referencing our button, we could also use the following code to get the button content from the resource file:

button.Text = GetString (Resource.String.hello);

Whenever your want to get a string from the resource file in an Activity, the GetString() method is used.

I hope this post helps you to understand how resources are used in an Android app and how to handle it a Xamarin project.

Happy coding everyone!

Posted by msicc in Android, Dev Stories, Xamarin, 0 comments