I am having a issue accesing a text box in a view controller .cs file
async partial void loginUser(UIButton sender)
{
// Show the progressBar as the MainActivity is being loade
Console.WriteLine("Entered email : " + txtEmail.Text);
// Create user object from entered email
mCurrentUser = mJsonHandler.DeserialiseUser(txtEmail.Text);
try
{
Console.WriteLine("Starting network check");
// Calls email check to see if a registered email address has been entered
if (EmailCheck(txtEmail.Text) == true)
{
await CheckPassword();
}
else
{
UIAlertView alert = new UIAlertView()
{
Title = "Login Alert",
Message = "Incorrect email or password entered"
};
alert.AddButton("OK");
alert.Show();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("An error has occured: '{0}'", ex);
}
It is within this funciton that it complains it cannot access a text box which is on a aynsc method
public Task CheckPassword()
{
return Task.Run(() =>
{
// Creates instance of password hash to compare plain text and encrypted passwords.
PasswordHash hash = new PasswordHash();
// Checks password with registered user password to confirm access to account.
if (hash.ValidatePassword(txtPassword.Text ,mCurrentUser.password)==true)
{
Console.WriteLine("Password correct");
UIAlertView alert = new UIAlertView()
{
Title = "Login Alert",
Message = "Password Correct Loggin In"
};
alert.AddButton("OK");
alert.Show();
//insert intent call to successful login here.
}
else
{
UIAlertView alert = new UIAlertView()
{
Title = "Login Alert",
Message = "Incorrect email or password entered"
};
alert.AddButton("OK");
alert.Show();
}
Console.WriteLine("Finished check password");
});
}
Its this line the error occurs:
txtPassword.Text
The error is as follows:
UIKit.UIKitThreadAccessException: UIKit Consistency error: you are
calling a UIKit method that can only be invoked from the UI thread.
Also my Password Correct does not show even though if it is a correct password.
Do i have to run the UI Alerts on a seperate thread?
Any UIKit methods must be called from the UI thread (or Main thread, Main queue, etc.). This ensures consistency in the UI. Xamarin adds a check to all UIKit methods in debug mode and throws that exception if you try to use a background thread to change the UI.
The solution is simple: only modify the UI from the UI thread. That essentially means if you're using a class with "UI" in front it, you should probably do it from the UI thread. (That's a rule of thumb and there are other times to be on the UI thread).
How do I get my code on this mythical UI thread? I'm glad you asked. In iOS, you have a few options:
When in a subclass of NSObject, InvokeOnMainThread will do the trick.
From anywhere, CoreFoundation.DispatchQueue.MainQueue.DispatchAsync will always work.
Both of those methods just accept an Action, which can be a lambda or a method.
So in your code, if we add an InvokeOnMainThread (because I think this is in your UIViewController subclass)...
public Task CheckPassword()
{
return Task.Run(() =>
{
// Creates instance of password hash to compare plain text and encrypted passwords.
PasswordHash hash = new PasswordHash();
// Checks password with registered user password to confirm access to account.
InvokeOnMainThread(() => {
if (hash.ValidatePassword(txtPassword.Text ,mCurrentUser.password)==true)
{
Console.WriteLine("Password correct");
UIAlertView alert = new UIAlertView()
{
Title = "Login Alert",
Message = "Password Correct Loggin In"
};
alert.AddButton("OK");
alert.Show();
//insert intent call to successful login here.
}
else
{
UIAlertView alert = new UIAlertView()
{
Title = "Login Alert",
Message = "Incorrect email or password entered"
};
alert.AddButton("OK");
alert.Show();
}
});
Console.WriteLine("Finished check password");
});
}
Maybe this helps someone. So I will add what solve my issue, that was the same of rogue.
Follow the code that avoid this error of consistency in xamarin forms when used in iOS
await Task.Run(async () =>
{
await Device.InvokeOnMainThreadAsync(async () =>
{
await MaterialDialog.Instance.SnackbarAsync(message: "Bla bla bla",
msDuration: MaterialSnackbar.DurationShort).ConfigureAwait(false);
}).ConfigureAwait(false);
}).ConfigureAwait(false);
Related
I am using Firebase to allow the user to login to a Unity game via Facebook. It is working fine, but I cannot get access to the user's email by using this parameter auth.CurrentUser.Email. Also, the email is not stored in Firebase Authentication Console. The email can be stored/accessed succussfully when I use other sign-in methods, such as email and google.
Here is my code:
public void SignInFacebook()
{
var perms = new List<string>() { "public_profile", "email", "user_friends" };
FB.LogInWithReadPermissions(perms, AuthCallback);
}
private void AuthCallback(ILoginResult result)
{
if (FB.IsLoggedIn)
{
// AccessToken class will have session details
var aToken = Facebook.Unity.AccessToken.CurrentAccessToken;
// Print current access token's User ID
Debug.Log(aToken.UserId);
// Print current access token's granted permissions
foreach (string perm in aToken.Permissions)
{
Debug.Log(perm);
}
Credential credential = FacebookAuthProvider.GetCredential(aToken.TokenString);
auth.SignInWithCredentialAsync(credential).ContinueWithOnMainThread(task => {
if (task.IsCanceled)
{
Debug.LogError("SignInWithCredentialAsync was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("SignInWithCredentialAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.LogFormat("User signed in successfully: {0} - {2} - ({1})",
newUser.DisplayName, newUser.UserId, newUser.Email);
});
}
else
{
Debug.Log("User cancelled login");
}
}
And this is what it looks like in the console ("-" is where the email is supposed to be stored. If I use another sign-in method, such as email or google, the email is stored without any issues)
Similar questions were asked about this issue and it was suggested that I change the Account email address setting in Firebase to Prevent creation of multiple accounts with the same email address, but it did not solve the issue.
Thanks!
If your Facebook app is in test mode you must login from your Facebook ID. Go to the settings, scroll down, select Apps and Websites, and click on your app. From there, make sure email address require is enabled.
you can try this
private void FacebookAuthCallback(ILoginResult result)
{
if (FB.IsLoggedIn)
{
FB.API("/me?fields=id,name,email", HttpMethod.GET, FacebookGetInfo);
}
else
{
Debug.Log("User cancelled login");
}
}
private void FacebookGetInfo(IResult result)
{
if (result.Error == null)
{
if (result.ResultDictionary.ContainsKey("email"))
{
string aEmail = result.ResultDictionary["email"].ToString();
return;
}
}
else
{
Debug.Log(result.Error);
}
}
Using Moq, I have a method that is being called but the Verify in the test fails stating it isn't. Confused as it seems that there is one invocation in the mock object. Stepping through debugging, the code makes it to the method in question.
the code in the test
[Fact]
public async Task WhenUsernameAndPasswordAreEmpty_ThenDisplayErrorMessage() {
mockAuthenticationService.Setup(mock => mock.SignInAsync(It.IsAny<string>(), It.IsAny<string>())).Throws(new NullReferenceException());
var loginMessageReceived = false;
mockAppService.Setup(mock => mock.IsBusy).Returns(false);
mockLoginViewModel.Object.Username = string.Empty;
mockLoginViewModel.Object.Password = string.Empty;
MessagingCenter.Subscribe<LoginViewModel>(this, "LoginSuccessful", (obj) => {
loginMessageReceived = true;
});
await mockLoginViewModel.Object.LoginCommand.ExecuteAsync();
Equals(loginMessageReceived, false);
mockAuthenticationService.Verify(auth => auth.SignInAsync(string.Empty, string.Empty), Times.Once());
mockMessageService.Verify(msg => msg.DisplayLoginError(new Exception("You must enter both a username and password to login.")), Times.Once());
}
the code that is being called
catch (NullReferenceException ex) {
_messageService.DisplayLoginError(new Exception("You must enter both a username and password to login."));
var properties = new Dictionary<string, string> {
{ "ExecuteLoginCommand", "You must enter both a username and password to login." },
{ "Username", Username },
{ "Password", Password }
};
Crashes.TrackError(ex, properties);
}
Appreciate any guidance
Ok, thanks in part to #canton7 I figured it out. Had to do some searching, but figured out how.
The Verify needed to take any type of Exception and then I can check the property there.
mockMessageService.Verify(msg =>
msg.DisplayLoginError(It.Is<Exception>(ex =>
ex.Message == "You must enter both a username and password to login."
))
, Times.Once()
);
In my app, for debugging purposes and testing purposes I need to send an email.
How do I present the mail controller, when the class that sends it does not contain a definition for PresentViewController ? The class needs to fire off the email app after the user clicks "yes" from the alert which fires.
public async Task<bool> SendEmail(Exception ex)
{
var result = await SendNotificationToRequestSendingEmail();
if (result)
{
if (MFMailComposeViewController.CanSendMail)
{
mailController = new MFMailComposeViewController();
mailController.SetToRecipients(new string[] { "test#one.com", "test#two.com" });
mailController.SetSubject("Details");
mailController.SetMessageBody($"Message: {ex.Message}" +
$"Exception: {ex.ToString()}"
, false);
mailController.Finished += (object s, MFComposeResultEventArgs args) =>
{
Console.WriteLine(args.Result.ToString());
args.Controller.DismissViewController(true, null);
};
this.PresentViewController(mailController, true, null); //this line causes the error
}
}
return true;
}
How can I fix this problem or get around it? This is called from a Xamarin Forms page.
Please take a look at this answer:
Sending e-mail from Gmail in Xamarin.Forms app
besides that you can also do it with this NuGet package:
https://www.nuget.org/packages/Xam.Plugins.Messaging/
Here is my login code, the problem happens to users redirected to "Profile" after logging in.
protected void UserLogin_Click(object sender, EventArgs e)
{
if (IsValid)
{
var manager = new IdentityModels.UserManager();
IdentityModels.User user = manager.Find(Username.Text, Password.Text);
if (user != null)
{
if (isUserActive(user.PatientId)=="isNotActive")
{
lblError.Text =
"you are no longer active. Please contact your local clinic to find out why.";
return;
}
if (isUserActive(user.PatientId) == "clinicNotActive")
{
lblError.Text =
"Your clinic is no longer active. Please contact your local clinic to find out why.";
return;
}
IdentityModels.IdentityHelper.SignIn(manager, user, RememberMe.Checked);
if (manager.IsInRole(user.Id,"Administrator") || manager.IsInRole(user.Id,"Staff") || manager.IsInRole(user.Id,"Physician"))
{
Response.Redirect("Dashboard");
}
if (Request.QueryString["Profile"] != null)
{
IdentityModels.IdentityHelper.RedirectToReturnUrl(Request.QueryString["Profile"], Response);
}
else
{
Response.Redirect("Profile");
}
}
else
{
ModelState.AddModelError("", "Invalid username or password");
lblError.Text = "Invalid username or password";
}
}
}
here is my page load code on the Profile page:
var manager = new IdentityModels.UserManager();
IdentityModels.User user = manager.FindById(HttpContext.Current.User.Identity.GetUserId());
if (user == null)
{
var ex = new Exception("patient was null, BUT TRIED SIGNING IN NOW" + UserAccess.GetUserId().ToString());
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
Response.Redirect("Login");
}
Elmah logs show the exception "patient was null, BUT TRIED SIGNING IN NOW 0".
So if my users are signing in successfully, which they must be because they are hitting the profile page, then why do some of them hit this error. Why is the user null?
I just can't figure it out, why it effects some but not all. When I republish the website all users can then login for a few minutes, sometimes a few hours, then it starts again.
Try using User.Identity rather than HttpContext.Current.User.Identity. I've seen some cases where the context (which is based on ASP.NET's session) gets out of sync with Identity's tokens.
OK guys, here is the answer.
Change the session state from InProc, in my case to SQLServer, it's been 22 hours since a login redirect, which hasn't happened before, so I think it's safe to say the problem is solved and that was the answer.
I had the following action which hung and never returned:
public Task<ActionResult> ManageProfile(ManageProfileMessageId? message)
{
ViewBag.StatusMessage =
message == ManageProfileMessageId.ChangeProfileSuccess
? "Your profile has been updated."
: message == ManageProfileMessageId.Error
? "An error has occurred."
: "";
ViewBag.ReturnUrl = Url.Action("ManageProfile");
var user = UserManager.FindByIdAsync(User.Identity.GetUserId());
var profileModel = new UserProfileViewModel
{
Email = user.Email,
City = user.City,
Country = user.Country
};
return View(profileModel);
}
but when I converted it to this:
public async Task<ActionResult> ManageProfile(ManageProfileMessageId? message)
{
ViewBag.StatusMessage =
message == ManageProfileMessageId.ChangeProfileSuccess
? "Your profile has been updated."
: message == ManageProfileMessageId.Error
? "An error has occurred."
: "";
ViewBag.ReturnUrl = Url.Action("ManageProfile");
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var profileModel = new UserProfileViewModel
{
Email = user.Email,
City = user.City,
Country = user.Country
};
return View(profileModel);
}
It returned right away. So Im not sure what is going on with this? If its as simple as the method returned without waiting on the result of FindByIdAsync, then why didn't I get a view with nothing in it.
So it appears to me that it neither waited for the return of:
UserManager.FindByIdAsync(User.Identity.GetUserId());
nor returned a null profile nor threw an exception. So I dont get whats happening here when it's hanging in the first example.
I assume your first example was using Result, thus causing a deadlock that I explain on my blog.
In summary, ASP.NET provides a "request context" which only allows one thread in at a time. When you block a thread using Result, that thread is locked into that context. Later, when FindByIdAsync attempts to resume on that context, it cannot because there's another thread already blocked in it.