Write device platform specific code in Xamarin.Forms - c#

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?

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(value, key);
}
public string GetString(string key)
{
return NSUserDefaults.StandardUserDefaults.StringForKey(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);
}
}
}

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.

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.

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.

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");

Related

Folder Picker .NET MAUI

I would to do a downloader app that save pictures to a folder. The app should work on windows and macos, and may be later on android and ios.
I haven't found a way to pick the target folder. Any idea on how it can be achieve either with blazor or xaml .NET MAUI app?
I've made a start implementing this for Windows and macOS. You can review the code here: https://github.com/jfversluis/MauiFolderPickerSample and wrote a little blog post about this as well here: https://blog.verslu.is/maui/folder-picker-with-dotnet-maui/
This follows kind of the basic pattern you'd want to use if you want to access platform-specific APIs:
Define an interface
Implement interface on each supported platform
Consume functionality
For this I have created a very simple but effective interface
public interface IFolderPicker
{
Task<string> PickFolder();
}
Then we create an implementation for Windows, by adding a new file FilePicker.cs to the Platforms\Windows\ folder. This makes it specific to Windows and allows us to write Windows specific code. The file contains this code:
using WindowsFolderPicker = Windows.Storage.Pickers.FolderPicker;
namespace MauiFolderPickerSample.Platforms.Windows
{
public class FolderPicker : IFolderPicker
{
public async Task<string> PickFolder()
{
var folderPicker = new WindowsFolderPicker();
// Make it work for Windows 10
folderPicker.FileTypeFilter.Add("*");
// Get the current window's HWND by passing in the Window object
var hwnd = ((MauiWinUIWindow)App.Current.Windows[0].Handler.PlatformView).WindowHandle;
// Associate the HWND with the file picker
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);
var result = await folderPicker.PickSingleFolderAsync();
return result.Path;
}
}
}
Because I chose FolderPicker as the name for my own object here, there is a naming conflict with the Windows FolderPicker that is why there is that weird using at the top. If you go for MyFolderPicker as your object name that wouldn't be needed.
Now we register this interface and implementation with the generic host builder in our MauiProgram.cs:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Note: this part was added
#if WINDOWS
builder.Services.AddTransient<IFolderPicker, Platforms.Windows.FolderPicker>();
#elif MACCATALYST
builder.Services.AddTransient<IFolderPicker, Platforms.MacCatalyst.FolderPicker>();
#endif
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<App>();
// Note: end added part
return builder.Build();
}
}
Note that I also added MainPage and App here so that our constructor injection works (have a look at MainPage.xaml.cs in the linked repository).
Now we can consume our functionality as follows:
namespace MauiFolderPickerSample;
public partial class MainPage : ContentPage
{
private readonly IFolderPicker _folderPicker;
public MainPage(IFolderPicker folderPicker)
{
InitializeComponent();
_folderPicker = folderPicker;
}
private async void OnPickFolderClicked(object sender, EventArgs e)
{
var pickedFolder = await _folderPicker.PickFolder();
FolderLabel.Text = pickedFolder;
SemanticScreenReader.Announce(FolderLabel.Text);
}
}
Implementing other platforms would require you to implement the interface for the platform you want to support and register it in the generic host builder. This should get you started for Windows and macOS.
Actually calling this should not be any different between .NET MAUI (regular) or .NET MAUI Blazor.

How to access a shared project data object from a method in iOS AppDelegate.cs?

I'm trying to pass some data values from my shared project for access via an iOS AppDelegate.cs method. I don't want to go into too much detail here, as I don't want to limit the reach of this question. But the Method could be called at any point and is used to gain state information about the app, e.g. isLoggedIn etc.
We're using GalaSoft.MvvmLight.Ioc.SimpleIoc and have a CustomViewModelbase, but that probably not too relevant.
The values are mostly part of our CustomViewModelbase, I thought I could create some kind of global object on App.Xaml.cs, which would be accessibily in AppDelegate.cs
Here's what I have...
using GalaSoft.MvvmLight.Ioc;
using ourapp.DTO;
using ourapp.Interfaces;
using ourapp.ViewModels;
namespace ourapp.Helpers
{
public class UITestingHelper : CustomViewModelBase, IUITestingHelper
{
[PreferredConstructor]
public UITestingHelper(
ICustomNavigationService navigationService,
IApiClient apiClient,
IDependencyService dependencyService)
: base(
navigationService,
apiClient,
dependencyService)
{
}
//
UITestingBackdoor _status;
public UITestingBackdoor Status
{
get
{
//var vm = (CustomViewModelBase)App.ViewModelLocator.Resolve(
// typeof(CustomViewModelBase));
_status = new UITestingBackdoor()
{
WillShowAccountPopup = base.HasMoreThanOneAccount,
AppUpdateAvailable = base.AppUpdateAvailable,
IsLoggedIn = App.IsLoggedIn,
IsConnected = App.Connectivity.IsConnected,
};
return _status;
}
}
public string GetAppStatus()
{
string json = Newtonsoft.Json.JsonConvert
.SerializeObject(Status);
return json;
}
}
}
Here's my AppDelegate.cs method...
[Export("UITestBackDoor:")]
public NSString UITestBackDoor(NSString value)
{
var status = App.UITestingStatus.GetAppStatus();
return (NSString)status;
}
However, the object is basically a view model in it's own rights and has dependancy injection to initialise it. However, it isn't registered against a specific view and therefore can not be resolved.
My exact issue is that although a property on my CustomViewModelbase is getting it's value set. When the values is accessed in my global object, the value is empty.
I believe this is related to dependancy injection. However, I'm starting to think there must be a simpler solution?
Yes, I will want to do this for Android as well, but first things first.
Create a global variable in App.cs , initialize it in constructor.
Return the value from a public method and access it in iOS project .
Forms App.cs
UITestingHelper helper;
public string GetAppStatus()
{
return helper.UITestingStatus.GetAppStatus;
}
iOS AppDelegate.cs
public NSString UITestBackDoor(NSString value)
{
return (App.Current as App).GetAppStatus();
}

Passing values to Specflow Scenario methods from feature file

I have a custom tag defined in my Hook.cs file like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade()
{
// Code execution here
}
What I want to do is I want to change its method definition like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade(bool flag)
{
if(flag)
// Code execution here
else
//Do a clean up
}
And I want to use this in feature file as something like
#AfterUpgradeTag(bool val = false)
I have searched alot for this. I want to know is this possible using Specflow or if there are any alternatives
I am not sure if you can pass parameters like that in feature file but you can utilize tags to achieve your goal
In feature file do this
#upgrade #false
Scenario: testing upgrade
In binding class
public static ScenarioContext _scenarioContext;
and binding class constructor
public BindingClass(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
and your BeforeScenario method is defined like this in the class BindingClass
[BeforeScenario("upgrade")]
public void BeforeScenarioUpgradeFalseorTrue()
{
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("false"))
{
log.Info("upgrade is false..");
}
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("true"))
{
log.Info("upgrade is true..");
}
}
when you want to pass true in feature file just do
#upgrade #true
Scenario: testing upgrade
You can follow the documentation from specflow to achieve this.

Xamarin.GooglePlayServices.Ads: how to add a bundle to the ad request

Considering I have an AdView in a Xamarin.Android project:
private AdView _bannerAd;
I usually initialize it like this:
_bannerAd = new AdView(this)
{
AdSize = AdSize.SmartBanner,
AdUnitId = adUnitId,
Visibility = ViewStates.Visible
};
Then, when I load the banner, I have to build the request. In this case I'd like to add an extra bundle, but when I have to call requestbuilder.AddCustomEventExtrasBundle, I don't know what to put as the first parameter.
var requestbuilder = new AdRequest.Builder();
var extras = new Bundle();
extras.PutString("npa", "1");
requestbuilder.AddCustomEventExtrasBundle(***, extras);
_bannerAd.LoadAd(requestbuilder.Build())
By reading the method definition, I really don't understand what could be an "adapter class".
[Register("addCustomEventExtrasBundle", "(Ljava/lang/Class;Landroid/os/Bundle;)Lcom/google/android/gms/ads/AdRequest$Builder;", "")]
public Builder AddCustomEventExtrasBundle(Class adapterClass, Bundle customEventExtras);
You need to pass the Java Class (via Java.Lang.Class.FromType) of your custom event (ICustomEventBanner).
In my case, I have one called SushiHangoverTextEventBanner that is registered with AdMob.
You need to implement ICustomEventBanner, assumably this is a stand alone object (in my case it is) as AdMob will instance it, inherit it from Java.Lang.Object so Xamarin will create the ACW (JNI/Java wrapper) for it.
public class SushiHangoverTextEventBanner : Java.Lang.Object, ICustomEventBanner
{
SushiHangoverTextAdView customAdView;
public void OnDestroy()
{
customAdView?.Dispose();
}
public void OnPause()
{
~~~
}
public void OnResume()
{
~~~
}
public void RequestBannerAd(Context context, ICustomEventBannerListener listener, string serverParameter, AdSize size, IMediationAdRequest mediationAdRequest, Bundle customEventExtras)
{
customAdView = new SushiHangoverTextAdView(context);
~~~
}
}
I also have a custom ad (SushiHangoverAdView based on a TextView) that is used within that ICustomEventBanner implementation.
Once you register it and implement the AdMob callbacks, you can just pass it to your AdRequest.Builder:
using (var requestbuilder = new AdRequest.Builder())
{
var extras = new Bundle();
extras.PutString("npa", "1");
requestbuilder.AddCustomEventExtrasBundle(Java.Lang.Class.FromType(typeof(SushiHangoverTextEventBanner)), extras);
}
I help recommend going through the Admob custom event example:
https://developers.google.com/admob/android/custom-events
It is expecting a class that extends from CustomEvent, per the Documentation
public AdRequest.Builder addCustomEventExtrasBundle (Class<? extends
CustomEvent> adapterClass, Bundle customEventExtras)
Here is a great tutorial on getting started with custom events, directly from Google, where they go over using the CustomEventBanner. It is in Java, but should be easy enough to port to C#

ASP.NET is it possible to use Session & HttpContext.Application in a library

I have the following code in a HttpGet method in a Controller
Session["var1"] = "someval1";
HttpContext.Application["var2"] = "someval2";
I wish to put away this code in a library [dll] so that in the library I have
// Inside DLL Library
// namespace MyNS, class MyCl
public void InitVars()
{
Session["var1"] = "someval1";
HttpContext.Application["var2"] = "someval2";
}
And the call this from my controller Get method
// In controller class HttpGet
InitVars();
How do I access the Session & the Application objects in the Library
I get the errors
The name Session does not exist in the current context
The name HttpContext does not exist in the current context
How can this be done?
You just need to open up the code library .csproj in Visual Studio and set a reference to System.Web.dll and the same code will work in the DLL.
You can get a reference to the current HttpContext using the following code:
var context = System.Web.HttpContext.Current;
after which you can simply call
context.Session["var1"] = "someval1";
context.Application["var2"] = "someval2";
This works
void InitLogin(System.Web.HttpSessionStateBase Session,
System.Web.HttpApplicationStateBase Application)
{
Session["var1"] = "someval1";
Application["var2"] = "someval2";
}
and call it as
InitVars(Session, Application);
How do I access the Session & the Application objects in the Library
Don't do it directly, you'll couple your code. I recommend using the Adapter Pattern. Something like this (untested):
Class Library:
public interface IStorage
{
T GetSession<T>(string key);
void SetSession<T>(string key, T value);
T GetGlobal<T>(string key);
void SetGlobal<T>(string key, T value);
}
public void InitVars(IStorage storage)
{
storage.SetSession("var1", "someval1");
storage.SetGlobal("var2", "somval2");
}
Web App:
public class WebStorage : IStorage
{
public T GetSession<T>(string key)
{
var result = Session[key] as T;
return result;
}
public void SetSession<T>(string key, T value)
{
Session[key] = value;
}
// etc with Global
}
InitVars(new WebStorage);
Now you have no dependencies on any web classes. If down the road you decide to use asp.net core (which has no HttpContext.Current etc etc) you can easily modify your WebStorage class without having to change your class library.

Categories

Resources