30

I have the following Xamarin.Forms.ContentPage class structure

public class MyPage : ContentPage
{
    public MyPage()
    {
        //do work to initialize MyPage 
    }

    public void LogIn(object sender, EventArgs eventArgs)
    {
        bool isAuthenticated = false;
        string accessToken = string.Empty;

        //do work to use authentication API to validate users

        if(isAuthenticated)
        {
            //I would to write device specific code to write to the access token to the device
            //Example of saving the access token to iOS device
            NSUserDefaults.StandardUserDefaults.SetString(accessToken, "AccessToken");

            //Example of saving the access token to Android device
            var prefs = Application.Context.GetSharedPreferences("MySharedPrefs", FileCreationMode.Private);
            var prefsEditor = prefs.Edit();

            prefEditor.PutString("AccessToken", accessToken);
            prefEditor.Commit();
        }
    }
}

I would like to write platform specific code in the MyPage LogIn method to save the access token based on which device OS they are using my application on.

How do I only run device specific code when the user uses my application on their device?

Michael Kniskern
  • 23,162
  • 65
  • 156
  • 224

5 Answers5

60

This is a scenario which is easily resolved with dependency injection.

Have a interface with the desired methods on your shared or PCL code, like:

public interface IUserPreferences 
{
    void SetString(string key, string value);
    string GetString(string key);
}

Have a property on your App class of that interface:

public class App 
{
    public static IUserPreferences UserPreferences { get; private set; }

    public static void Init(IUserPreferences userPreferencesImpl) 
    {
        App.UserPreferences = userPreferencesImpl;
    }

    (...)
}

Create platform-specific implementations on your target projects:

iOS:

public class iOSUserPreferences : IUserPreferences 
{
    public void SetString(string key, string value)
    {
        NSUserDefaults.StandardUserDefaults.SetString(key, value);
    }

    public string GetString(string key)
    {
        (...)
    }
}

Android:

public class AndroidUserPreferences : IUserPreferences
{
    public void SetString(string key, string value)
    {
        var prefs = Application.Context.GetSharedPreferences("MySharedPrefs", FileCreationMode.Private);
        var prefsEditor = prefs.Edit();

        prefEditor.PutString(key, value);
        prefEditor.Commit();
    }

    public string GetString(string key)
    {
        (...)
    }
}

Then on each platform-specific project create an implementation of IUserPreferences and set it using either App.Init(new iOSUserPrefernces()) and App.Init(new AndroidUserPrefernces()) methods.

Finally, you could change your code to:

public class MyPage : ContentPage
{
    public MyPage()
    {
        //do work to initialize MyPage 
    }

    public void LogIn(object sender, EventArgs eventArgs)
    {
        bool isAuthenticated = false;
        string accessToken = string.Empty;

        //do work to use authentication API to validate users

        if(isAuthenticated)
        {
            App.UserPreferences.SetString("AccessToken", accessToken);
        }
    }
}
Michael Kniskern
  • 23,162
  • 65
  • 156
  • 224
Pedro
  • 10,594
  • 5
  • 25
  • 39
  • On the `App` class code snippet, I am getting an "Method must have a return type" when trying code it. Should it have a return type of `void`? – Michael Kniskern Jun 16 '14 at 21:02
  • The init method probably should have a `void` return type. – valdetero Jun 16 '14 at 21:09
  • This is a very nice solution that organizes the code into each platform specific project. Thank you. – Michael Kniskern Jun 16 '14 at 23:23
  • Thanks Michael and valdetero for pointing that out and for fixing it. Glad this answer fits you, I use this approach for several different platform specific features that I need to access. Also worth a look on `DependencyService` as mentioned by Stephane Delcroix. – Pedro Jun 17 '14 at 14:36
12

Xamarin.Forms 2.3.4 introduced a new method for this:

if (Device.RuntimePlatform == Device.Android)
{
    // Android specific code
}
else if (Device.RuntimePlatform == Device.iOS)
{
    // iOS specific code
}
else if (Device.RuntimePlatform == Device.UWP)
{
    // UWP specific code
}

There are also other platforms to choose from, you can type in Device. in Visual Studio and it will show you the options.

SendETHToThisAddress
  • 1,477
  • 2
  • 15
  • 29
9

There are multiple answers, depending on what you want to achieve, and the kind of project you have:

Execute different Xamarin.Forms code on different platforms.
Use this e.g. if you want different font sizes on different platforms:

label.Font = Device.OnPlatform<int> (12, 14, 14);

Execute platform specific code in a shared (PCL) project The common pattern is to use DI (dependency injection) for this. Xamarin.Forms provides a simple DependencyService for this, but use whatever you want.

Execute platform specific code in shared (Shared Asset Project) project As the code is compiled per platform, you can wrap your platform specific code in #if __PLATFORM__ #endif and have all the code in the same file. The platform project should define __IOS__, __ANDROID__ and __WINDOWS_PHONE__. Note that a shared asset project containing Xaml and code won't work well for iOS on Xamarin.Studio, and that having compiler directives makes your code harder to read and to test.

Stephane Delcroix
  • 15,420
  • 5
  • 52
  • 82
  • I am currently using option 3 "Execute platform specific code in shard (Shared Asset Project) project" with the `__IOS__` and `__ANDROID__` syntax, but the android specific code is not showing up in Intellisense. The iOS specific code is showing up in Intellisense – Michael Kniskern Jun 16 '14 at 21:26
  • 1
    @MichaelKniskern: change your build target to android, to have intellisense for it. and make sure (in your project preferences) that the symbol is exported. btw, I find it funny that you're using this answer, and accepting another one :) – Stephane Delcroix Jun 17 '14 at 06:58
  • I decided to go with the answer that was not dependent on Xamarin functionality because I have been having other issues with the Blank App (Xamarin.Forms Shared) project type. Also, having to switch the start up project each time if you want to use platform specific code is not ideal. – Michael Kniskern Jun 17 '14 at 15:38
2

This seems less about Xamarin.Forms and more about using defaults in a PCL. Check out James Montemagno's github repo for doing cross-platform defaults.

Then just call his static methods for setting/retrieving. The nuget package is Xam.Plugins.Settings.

It can be used like this:

using Refractored.Xam.Settings;

...

CrossSettings.Current.AddOrUpdateValue("AccessToken", accessToken);
var value = CrossSettings.Current.GetValueOrDefault<string>("AccessToken");
Matt
  • 70,063
  • 26
  • 142
  • 172
valdetero
  • 4,484
  • 1
  • 31
  • 44
1

Xamarin.Forms has a built-in dependency injector if you take a look at their guide in the developer area of their website (http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/)

There's also a wonderful library you can pull from NuGet/Github (https://github.com/aritchie/acr-xamarin-forms) that will handle the storage requirement you are looking for... take a look at the Settings service in there, it will even handle serialization of more complex objects.

J Geary
  • 11
  • 1