I'm trying to implement Azure Active Directory B2C in Xamarin.Forms. If I just copy their example, I can get it to work without a problem. But when I try to use Prism, I run into problems.
I took this code that was sitting in the codebehind of the XAML:
protected override async void OnAppearing ()
{
base.OnAppearing ();
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e)
{
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty, UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
and moved it to the ViewModel's OnNavigatedTo:
public async void OnNavigatedTo (NavigationParameters parameters)
{
if (parameters.ContainsKey ("title"))
Title = (string)parameters ["title"];
listen2asmr.App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await listen2asmr.App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
This is in the Bootstrapper:
protected override Xamarin.Forms.Page CreateMainPage ()
{
return Container.Resolve<LoginPage> ();
}
protected override void RegisterTypes ()
{
Container.RegisterTypeForNavigation<LoginPage>();
}
OnNavigatedTo never seems to get called though. Is there some other method I should be using, or did I miss something else? The only other thing I could think of was to call the code in OnNavigatedTo from the ViewModel constructor, but the async/await does work with the constructor.
This has been fixed in the latest preview version of Prism for Xamarin.Forms. Try using these packages instead:
https://www.nuget.org/packages/Prism.Forms/6.1.0-pre4
https://www.nuget.org/packages/Prism.Unity.Forms/6.2.0-pre4
Also the bootstrapping process has changed. Read this for more information:
Prism.Forms 5.7.0 Preview - http://brianlagunas.com/first-look-at-the-prism-for-xamarin-forms-preview/
Prism.Forms 6.2.0 Preview - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview/
Prism.Forms 6.2.0 Preview 3 - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview-3/
Preview 4 Post Coming Soon
My advice is use your View events as triggers for your ViewModel.
For Instance:
View.xaml.cs
protected override async void OnAppearing () {
base.OnAppearing ();
viewModel.OnAppearing();
}
async void OnSignUpSignIn(object sender, EventArgs e) {
viewModel.OnSignUpSignIn(sender, e);
}
ViewModel.cs
protected override async void OnAppearing () {
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty,
AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e) {
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty,
UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
Reasons:
View should only involve visuals, and the events your page receives. Logic should be forwarded to the ViewModel, unless it deals with representation of information (for instance, logic to use a toggle-box for 2 choices but a combo-box for 3+).
Nearly vice-versa, the ViewModel should keep track of "model state" (ex. the user still needs to enter their payment information) as opposed to "view state" (ex. the user has navigated to the payment page).
Related
How can I avoid invoking of the same event multiple times when a button is clicked rapidly.
Below is the code:
I've created a Custom Delegate Command as below
View Model
namespace TestProject.ViewModels
{
public class TestViewModel
{
public CustomDelegateCommand MenuButtonClickCommand { get; set; }
public TestViewModel()
{
MenuButtonClickCommand = new CustomDelegateCommand (async () => await ShowMenuAction());
}
private async Task ShowMenuAction()
{
//await some stuff
}
}
}
CustomDelegateCommand.cs
public class CustomDelegateTimerCommand : DelegateCommand
{
public CustomDelegateTimerCommand(Action executeMethod, Func<bool> validateMethod, Action onBusy = default(Action)) : base(executeMethod)
{
BackgroundTaskWaitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
_validateMethod = validateMethod;
_onBusy = onBusy;
}
}
The problem I'm facing is whenever a user clicks on the button rapidly, the menu list popup is opening multiple times.
I have lot of commands in my project and I need a solution that would work globally.
I tried to resolve the issue like below using ObservesCanExecute() but I don't like the idea of creating a separate variable for every command as I've a lot of commands in my project and I don't want the button to go in to disabled state when CanExecute = false.
ViewModel
MenuButtonClickCommand = new CustomDelegateCommand (async () => await ShowMenuAction().ObservesCanExecute(() => CanExecute );
private async Task ShowMenuAction()
{
CanExecute = false;
//await some stuff
CanExecute = true;
}
Any help is much appreciated!
There are 2 solutions to it. One is when you use, MVVM other is when you dont.
The non MVVM solution is delaying the execution of method for certain amount of time, like this:
public class SingleClickListener
{
private bool hasClicked;
private Action<object, EventArgs> _setOnClick;
public SingleClickListener(Action<object, EventArgs> setOnClick)
{
_setOnClick = setOnClick;
}
public void OnClick(object v, EventArgs e)
{
if (!hasClicked)
{
_setOnClick(v, e);
hasClicked = true;
}
reset();
}
private void reset()
{
Android.OS.Handler mHandler = new Android.OS.Handler();
mHandler.PostDelayed(new Action(() => { hasClicked = false; }), 500);
}
}
And then when you subscribe the onclick event:
var buttonNa = new Button { Text = "Test Button" };
buttonNa.Clicked += new SingleClickListener((sender, e) =>
{
//DO something
}).OnClick;
The Mvvm solution is bit more complicated, but its not as hacky.
TestCommand = new Command(
execute: async () =>
{
IsEditing = true;
RefreshCanExecutes();
//Fire Method
TestMethod();
},
canExecute: () =>
{
return !IsEditing;
});
public void RefreshCanExecutes()
{
(TestCommand as Command).ChangeCanExecute();
}
public void TestMethod()
{
//DO something
IsEditing = false;
RefreshCanExecutes();
}
Obviously dont forget to bind your commands to xaml :)
also second solution actually disables the button, so user cannot even tap it, first one however only ignores further taps, till time delay has finished.
Create a new class which inherits from Xamarin.Forms.Button with delay in click event, than add it to your xmal.
public class DelayedButton : Xamarin.Forms.Button
{
public DelayedButton()
{
this.Clicked += DelayedButton_Clicked;
}
async private void DelayedButton_Clicked(object sender, EventArgs e)
{
this.IsEnabled = false;
await Task.Delay(Delay);
this.IsEnabled = true;
}
public int Delay { get; set; } = 500;
}
In XAML:
<yourNameSpace:DelayedButton Delay="300" Text="DelayedButton" Command="{Binding ClickCommand}"/>
i wanna use simple database in Xamarin Forms. So i used this code;
public partial class DBExample : ContentPage
{
string _dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),"myDB.db3");
public DBExample ()
{
InitializeComponent ();
}
private async void InsertButton(object sender, EventArgs e)
{
SQLiteConnection db=null;
try
{
db = new SQLiteConnection(_dbPath);
}
catch (Exception ex)
{
await DisplayAlert(null, ex.Message, "OK");
}
db.CreateTable<Users>();
var maxPk = db.Table<Users>().OrderByDescending(c => c.Id).FirstOrDefault();
Users users = new Users()
{
Id = (maxPk == null ? 1 : maxPk.Id + 1),
Name = userName.Text
};
db.Insert(users);
await DisplayAlert(null,userName.Text + "saved","OK");
await Navigation.PopAsync();
}
}
and i have problem. You can see it in Headtitle.
"Exception of type 'System.Collections.Generic.KeyNotFoundException' was thrown"
im waiting your support. Thanks for feedback.
I'm stuck on getting my DocumentPicker fully working. Right now it presents the view controller but I can't figure out how to wait or get the result.
In swift you just write the void documentPicker(UIDocumentPickerViewController controller, didPickDocumentAtUrl... method and when it's finished it goes to there.
But in Xamarin it must not be that simple. I've written that method, from the class I'm calling it from as well as in my AppDelegate.cs class and as well as in my Main.cs class. None seem to work, unless I've written it wrong.
What I have is this ....
public async Task<string> pickResume()
{
string path = string.Empty;
var controller = new UIViewController();
var docVC = new UIDocumentPickerViewController(new string[] { "org.openxmlformats.wordprocessingml.document", "com.microsoft.word.doc" }, UIDocumentPickerMode.Import);
UIViewController topController = getTopViewController();
topController.PresentViewController(docVC, true, null);
return path;
}
void documentPicker(UIDocumentPickerViewController controller, NSUrl didPickDocumentAtURL)
{
Console.WriteLine("done");
}
getTopViewController() is just a helper method to get the top view controller so I can present the DocumentPicker
Figured it out, and it's a lot easier than I was making it out to be.
The UIDocumentPickerViewController has two EventHandlers, DidPickDocument and WasCancelled so I just assigned those to two different methods and done.
public async Task<string> pickResume()
{
string path = string.Empty;
var controller = new UIViewController();
var docVC = new UIDocumentPickerViewController(new string[] { "org.openxmlformats.wordprocessingml.document", "com.microsoft.word.doc" }, UIDocumentPickerMode.Import);
docVC.DidPickDocument += DocVC_DidPickDocument;
docVC.WasCancelled += DocVC_WasCancelled;
UIViewController topController = getTopViewController();
topController.PresentViewController(docVC, true, null);
return await GetDocPath(new CancellationTokenSource());
}
private void DocVC_WasCancelled(object sender, EventArgs e)
{
//Handle being cancelled
}
private void DocVC_DidPickDocument(object sender, UIDocumentPickedEventArgs e)
{
//Handle document selection
}
I'd like to share an image in my app. However, the image is not located in a folder but it is "taken dynamically". Basically i have an Image object
Image i = new Image() { Source = await CreateBitmapFromElement(stackpanel1) };
where CreateBitmapFromElement is defined as follows
private async Task<RenderTargetBitmap> CreateBitmapFromElement(FrameworkElement uielement)
{
try
{
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(uielement);
return renderTargetBitmap;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
return null;
}
The Windows Phone Share Contract allows to share images located in the Picture Library (for example), but what should i use in this case?
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += OnShareDataRequested;
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested -= OnShareDataRequested;
base.OnNavigatedFrom(e);
}
private void OnShareDataRequested(DataTransferManager sender, DataRequestedEventArgs _dataRequestedEventArgs)
{
DataRequest request = _dataRequestedEventArgs.Request;
request.Data.Properties.Title = "KeyTreat Sticker";
request.Data.Properties.Description = "KeyTreat Sticker: " + StickerName;
// Because we are making async calls in the DataRequested event handler,
// we need to get the deferral first.
DataRequestDeferral deferral = request.GetDeferral();
// Make sure we always call Complete on the deferral.
try
{
request.Data.SetStorageItems(storageItemsObject);
request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromFile(StorageFileObject);
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(StorageFileObject));
}
finally
{
deferral.Complete();
}
}
I'm developing an app and have run into a problem with asynchronous calls... Here's what i'm trying to do.
The app consumes a JSON API, and, when run, fills the ListBox within a panorama item with the necessary values (i.e. a single news article). When a user selects a ListBox item, the SelectionChanged event is fired - it picks up the articleID from the selected item, and passes it to an Update method to download the JSON response for the article, deserialize it with JSON.NET, and taking the user to the WebBrowser control which renders a html page from the response received.
The problem with this is that I have to wait for the response before I start the NavigationService, but I'm not sure how to do that properly. This way, the code runs "too fast" and I don't get my response in time to render the page.
The event code:
private void lstNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstNews.SelectedIndex == -1)
{
return;
}
ShowArticle _article = new ShowArticle();
ListBox lb = (ListBox)sender;
GetArticles item = (GetArticles)lb.SelectedItem;
string passId = ApiRepository.ApiEndpoints.GetArticleResponseByID(item.Id);
App.Current.JsonModel.JsonUri = passId;
App.Current.JsonModel.Update();
lstNews.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
}
OnNavigatedTo method in the View:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
long sentString = long.Parse(NavigationContext.QueryString["id"]);
string articleUri = ApiRepository.ApiEndpoints.GetArticleResponseByID(Convert.ToInt32(sentString));
//this throws an error, runs "too fast"
_article = App.Current.JsonModel.ArticleItems[0];
}
The update method:
public void Update()
{
ShowArticle article = new ShowArticle();
try
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
var deserialized = JsonConvert.DeserializeObject<ShowArticle>(q.Result);
_articleItems.Clear();
_articleItems.Add(deserialized);
}
};
}
catch (Exception ex)
{
//ignore this
}
webClient.DownloadStringAsync(new Uri(jsonUri));
}
async callback pattern:
public void Update(Action callback, Action<Exception> error)
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
// do something
callback();
}
else
{
error(q.Error);
}
};
webClient.DownloadStringAsync(new Uri(jsonUri));
}
call:
App.Current.JsonModel.Update(() =>
{
// executes after async completion
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
},
(error) =>
{
// error handling
});
// executes just after async call above