I'm trying to check which page should load my app at the beginning, first of all I check a database table if I find the login information stored I want to push the once named StartPage(), as I'm working with the database the method includes an await if there isn't any data stored I want to push the LoginPage(). I have tried following this example Xamarin.Forms Async Task On Startup . My code is :
public App()
{
int result;
InitializeComponent();
ThreadHelper.Init(SynchronizationContext.Current);
ThreadHelper.RunOnUIThread(async () => {
MainPage = new ActivityIndicatorPage();
result = await InitializeAppAsync();
if (result == 0)
{
PushLoginPage();
}
else
{
PushStartPage();
}
});
}
public void PushStartPage()
{
NavigationPage nav = new NavigationPage(new StartPage());
nav.SetValue(NavigationPage.BarBackgroundColorProperty, Color.FromHex("#D60000"));
MainPage = nav;
}
public void PushLoginPage()
{
MainPage = new Login();
}
public void PushLoginPage(string email, string password)
{
MainPage = new Login(email, password);
}
private async Task<int> InitializeAppAsync()
{
if (ViewModel == null)
ViewModel = new MainViewModel(this);
return await ViewModel.LoginViewModel.PushInitialPage();
}
But throws the following exception and as the author of the article says, is not recommended to do it.
Exception
Another option tried was overriding the OnStart() method but didn't work either.
protected override async void OnStart()
{
Task.Run(async ()=> { await InitializeAppAsync(); });
}
The PushInitialPage method:
public async Task PushInitialPage()
{
if (_app.Properties.ContainsKey("isLogged"))
{
var user = await UserDataBase.GetUserDataAsync();
var result = await Login(user.Email, user.Password);
if (result.StatusCode != 200)
{
return 0;
///PushLoginPage();
}
else
{
return 1;
//PushStartPage();
}
}
else
{
return 0;
}
}
When the OS asks your app to show a page, it must show a page. It can't say "hold on a minute or two while I talk to this remote server over an iffy network connection." It has to show a page Right Now.
So, I recommend bringing up a splash page - your company or app logo, for example. When the splash page shows, then call InitializeAppAsync, and based on the result, switch to the login or start page or nice user-friendly offline error page.
In Xamarin.Forms we have properties called 'Application.Current.Properties'. By using this we can able to save the any data type. So once user login in to the application you can set one flag and set it is true. Then after every time when user login in to the application you can check this flag and navigate your respective page.
Sample Code :
App.cs :
public App()
{
if (Current.Properties.ContainsKey("isLogged"))
{
if((bool)Application.Current.Properties["isLogged"])
{
// navigate to your required page.
}
else
{
// naviate to login page.
}
}
else
{
// naviate to login page.
}
}
At first time application open it checks the 'isLogged' property is presented or not, if not it will move to the login page. When user login into the application by using his credentials, we need to create 'isLoggin' property and set as true. Then after if user try to login it checks the condition and navigates to the respective page.
Saving Property SampleCode :
Application.Current.Properties["isLogged"] = true;
await Application.Current.SavePropertiesAsync();
write above code for after login into the application. If a user log out from the app you need to set 'isLogged' flag is false.
Related
I'm trying to implement a functionality where user can simply click on download button and can go on any page that he/she wishes while download is still running, and it should not stop.
I'm using xamarin form.
Is there any way to do this, please suggest or any reference, since i couldn't find the same.
Since the class App always exists in memory when the application is running , we can place the Task in App.
Simple Task
//App
public async void DoSomething()
{
await Task.Delay(30000);
}
// in specific page
string result = await (App.Current as App).DoSomething();
Task with return values
//App
public async Task<string> DoSomething()
{
await Task.Delay(30000);
return "123";
}
// in specific page
string result = await (App.Current as App).DoSomething();
If you don't want to put the code into App , we can create a extra class in App to handle this .
//App
private MyTask _myTask;
public MyTask myTask
{
get
{
return _myTask ?? new MyTask();
}
}
//MyTask
public class MyTask
{
public async Task<string> DoSomething()
{
await Task.Delay(30000);
return "123";
}
}
//in specific page
string result = await (App.Current as App).myTask.DoSomething();
I'm using firebase anonymous authantication for my unity project.
As i always did when project is started i'm sending request to firebase for authantication,
but on my last project (which uses firebase sdk 6.16.0) my request creates new user everytime.
Here is some code about how i'm sending my request
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
auth.SignInAnonymouslyAsync().ContinueWith((task =>
{
if (task.IsCanceled)
{
Debug.Log("task cancelled");
return;
}
if (task.IsFaulted)
{
Debug.Log("task cancelled");
return;
}
if (task.IsCompleted)
{
Firebase.Auth.FirebaseUser userr = task.Result;
firebaseUserId = userr.UserId;
Debug.Log("firebaseUserId");
Debug.Log(firebaseUserId);
//every opening returns new uniq id here.
}
}));
On firebase authantication panel i only activated anonymous login. any suggestions?
Or is there any way to downgrade unity firebase version? i've tried to import old version which i was using on my last game (sdk 6.15.2) but there is some errors on resolver.
Basically, every time you call SignInAnonymouslyAsync you'll create a new user and the last one will be basically lost (it's more or less a random hash - anonymous as it's name suggests).
I'll typically do something like:
using System;
using Firebase.Auth;
using UnityEngine;
using UnityEngine.Events;
public class Login : MonoBehaviour
{
public UnityEvent OnSignInFailed = new UnityEvent();
public UserSignedInEvent OnUserSignedIn = new UserSignedInEvent();
public async void TriggerLogin()
{
var auth = FirebaseAuth.DefaultInstance;
var user = auth.CurrentUser;
if (user == null)
{
try
{
user = await auth.SignInAnonymouslyAsync();
}
catch (Exception e)
{
Debug.LogException(e);
OnSignInFailed.Invoke();
return;
}
}
// user definitely should not be null!
if (user == null)
{
OnSignInFailed.Invoke();
Debug.LogWarning("User still null!?");
return;
}
var userName = user.UserId;
OnUserSignedIn.Invoke(userName);
Debug.Log($"Logged in as {userName}");
}
[Serializable]
public class UserSignedInEvent : UnityEvent<string>
{
}
}
Note that for this code snippet, TriggerLogin is a public method so I can chain it off of a UnityEvent in the Unity editor.
Try and Put it some kind of check to find if used is already logged in. If yes, then do a silent login, if no then use anonymous login.
Currently you are straightaway logging in user even if they logged in last time they opened the Application.
Try this link: https://github.com/firebase/quickstart-unity/issues/266#issuecomment-447981995
I am trying to develop a xamarin forms app in which user can make call
(Navigate to dialer) from taping on number showed on app.In android I accomplished this through dependency service.But in ios I am stuck.I heard about callkit.I saw the documentation of it in https://learn.microsoft.com/en-us/xamarin/ios/platform/callkit?tabs=windows. But how can I actually implement on this in my App? I added all the classes in that document to my app.But how I can make the call from xamal.cs to the ios specified code? By using Dependency service?
Edit: I know how to navigate app to dialer or phone app. Why I am using callkit is I want to get the call duartion.
I created an Instance
public interface IosCallerDialer
{
void StartCall();
}
Implementation on ios
class IosCallDial: IosCallerDialer
{
private CXCallController CallController = new CXCallController();
private void SendTransactionRequest(CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction(transaction, (error) => {
// Was there an error?
if (error == null)
{
// No, report success
Console.WriteLine("Transaction request sent successfully.");
}
else
{
// Yes, report error
Console.WriteLine("Error requesting transaction: {0}", error);
}
});
}
public void StartCall()
{
// Build call action
string contact = "8547085532";
var handle = new CXHandle(CXHandleType.Generic, contact);
var startCallAction = new CXStartCallAction(new NSUuid(), handle);
// Create transaction
var transaction = new CXTransaction(startCallAction);
// Inform system of call request
SendTransactionRequest(transaction);
}
}
My xaml.cs
async void btnCall_Clicked(object sender, System.EventArgs e)
{
DependencyService.Get<IosCallerDialer>().StartCall();
}
Apart this I added all the classes defined in the document.I want only outgoing call. Is this proper way? I cant find any tutorials regarding callkit on xamarin. Any help is appreciated.
EDIT: I understand Callkit only for voip. So is there any other workaround like starting a timer when moves to phone app and stop timer when returns to app? Is it possible? Please provide any insights.
You can try the code below to detect the state of incoming call.
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public CTCallCenter c { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
c = new CTCallCenter();
c.CallEventHandler = delegate (CTCall call)
{
if (call.CallState == call.StateIncoming)
{
//start the timer
}
else if (call.CallState == call.StateDialing)
{
}
else if (call.CallState == call.StateConnected)
{
}
else if(call.CallState == call.StateDisconnected)
{
//end the timer
//use messagecenter to send duartion
MessagingCenter.Send<Object>(new Object(), "Hi");
}
};
return base.FinishedLaunching(app, options);
}
}
And any Where in Xamarin.forms:
MessagingCenter.Subscribe<Object>(this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
Console.WriteLine("hihihi");
});
Note: I haven't test it on my side yet as I don't have enough device. You can test it and let me know if it works.
Currently I am developing a Xamarin App which is using IdentityModel.OidcClient to authenticate against my server, and it is being done using the automatic mode presented on the documentation (https://github.com/IdentityModel/IdentityModel.OidcClient2). Everything is working just fine as var result = await client.LoginAsync();
is returning the LoginResult with the AccessToken, etc.
What I am trying to figure out is how the backbutton, the recent apps button (both on android) and the close button on ChromeCustomTabsBrowser should be handled since these three actions close the Ibrowser attached to the oidcClient without returning a response and will keep me stuck awaiting for a response preventing me to process with the rest of the code validations.
private async Task SignInAsync() {
IsBusy = true;
await Task.Delay(500);
try {
LoginResult result = await IdentityService.LoginAsync(new LoginRequest());
if (result == null) {
OnError(noInternetErrorMessage);
IsBusy = false;
return;
}
if (result.IsError) {
OnError(result.Error);
} else {
string userName = result.User.Claims.Where(claim => claim.Type == userNameClaimType).Select(claim => claim.Value).SingleOrDefault();
_UserToken = IdentityService.CreateOrUpdateUserToken(userName, result);
if (_UserToken != null) {
await NavigationService.NavigateToAsync<LockScreenViewModel>();
} else {
OnError(errorMessage);
}
}
} catch (Exception e) {
OnError(e.ToString());
}
IsBusy = false;
}
In the previous block of code I can't reach if (result == null) if those buttons where clicked which in turn will prevent me from removing the ActivityIndicator in the loginView and provide the login button to the user so he can try login again.
This happens because your IdentityService.LoginAsync() task is actually still waiting in the background for the custom tabs activity callback to happen, regardless of the fact that the custom tabs browser is no longer visible. Because the user closed before completing the login roundtrip, no callback will be triggered until the user completes the roundtrip in a future attempt. Each login attempt will create a new awaiting task, so the collection of waiting tasks will grow each time the user closes the custom tabs window prematurely.
At the time the user actually finishes a login roundtrip it becomes clear that the tasks are all still waiting, because they all at once unfreeze when the long awaited callback finally occurs. This poses another issue to handle, because all but the last task will result in an 'invalid state' oidc error result.
I resolved this by canceling the previous task just before starting a new login attempt. I added a TryCancel method to ChromeCustomTabsBrowser on a custom interface IBrowserExtra. In the ChromeCustomTabsBrowser.InvokeAsync implementation, a reference is kept to the TaskCompletionSource to be returned.
The next time the user clicks the sign in button, TryCancel is first invoked before ChromeCustomTabsBrowser.LoginAsync to unlock the previous login attempt still awaiting, using the kept reference.
To make this work, IsBusy=True should be postponed until after the custom tabs callback (custom tabs browser will be on top anyway), to keep the gui interactive in case the custom tabs close button was clicked. Otherwise the user will never be able to reattempt login.
Update: added sample code as requested.
public interface IBrowserExtra
{
void TryCancel();
}
public class ChromeCustomTabsBrowser : IBrowser, IBrowserExtra, IBrowserFallback
{
private readonly Activity _context;
private readonly CustomTabsActivityManager _manager;
private TaskCompletionSource<BrowserResult> _task;
private Action<string> _callback;
public ChromeCustomTabsBrowser()
{
_context = CrossCurrentActivity.Current.Activity;
_manager = new CustomTabsActivityManager(_context);
}
public Task<BrowserResult> InvokeAsync(BrowserOptions options)
{
var builder = new CustomTabsIntent.Builder(_manager.Session)
.SetToolbarColor(Color.Argb(255, 0, 0, 0))
.SetShowTitle(false)
.EnableUrlBarHiding()
.SetStartAnimations(_context, Android.Resource.Animation.SlideInLeft, Android.Resource.Animation.SlideOutRight)
.SetExitAnimations(_context, Android.Resource.Animation.SlideInLeft, Android.Resource.Animation.SlideOutRight);
var customTabsIntent = builder.Build();
// ensures the intent is not kept in the history stack, which makes
// sure navigating away from it will close it
customTabsIntent.Intent.AddFlags(ActivityFlags.NoHistory);
_callback = null;
_callback = url =>
{
UnsubscribeFromCallback();
_task.TrySetResult(new BrowserResult()
{
Response = url
});
};
SubscribeToCallback();
// Keep track of this task to be able to refer it from TryCancel later
_task = new TaskCompletionSource<BrowserResult>();
customTabsIntent.LaunchUrl(_context, Android.Net.Uri.Parse(options.StartUrl));
return _task.Task;
}
private void SubscribeToCallback()
{
OidcCallbackActivity.Callbacks += _callback;
}
private void UnsubscribeFromCallback()
{
OidcCallbackActivity.Callbacks -= _callback;
_callback = null;
}
void IBrowserExtra.TryCancel()
{
if (_callback != null)
{
UnsubscribeFromCallback();
}
if (_task != null)
{
_task.TrySetCanceled();
_task = null;
}
}
}
public class LoginService
{
private static OidcClient s_loginClient;
private Task<LoginResult> _loginChallengeTask;
private readonly IBrowser _browser;
private readonly IAppInfo _appInfo;
public LoginService(
IBrowser secureBrowser,
IBrowserFallback fallbackBrowser,
IAppInfo appInfo)
{
_appInfo = appInfo;
_browser = ChooseBrowser(appInfo, secureBrowser, fallbackBrowser);
}
private IBrowser ChooseBrowser(IAppInfo appInfo, IBrowser secureBrowser, IBrowserFallback fallbackBrowser)
{
return appInfo.PlatformSupportsSecureBrowserSession ? secureBrowser : fallbackBrowser as IBrowser;
}
public async Task<bool> StartLoginChallenge()
{
// Cancel any pending browser invocation task
EnsureNoLoginChallengeActive();
s_loginClient = OpenIdConnect.CreateOidcClient(_browser, _appInfo);
try
{
_loginChallengeTask = s_loginClient.LoginAsync(new LoginRequest()
{
FrontChannelExtraParameters = OpenIdConnectConfiguration.LoginExtraParams
});
// This triggers the custom tabs browser login session
var oidcResult = await _loginChallengeTask;
if (_loginChallengeTask.IsCanceled)
{
// task can be cancelled if a second login attempt was completed while the first
// was cancelled prematurely even before the browser view appeared.
return false;
}
else
{
// at this point we returned from the browser login session
if (oidcResult?.IsError ?? throw new LoginException("oidcResult is null."))
{
if (oidcResult.Error == "UserCancel")
{
// Graceful exit: user canceled using the close button on the browser view.
return false;
}
else
{
throw new LoginException(oidcResult.Error);
}
}
}
// we get here if browser session just popped and navigation is back at customer page
PerformPostLoginOperations(oidcResult);
return true;
}
catch (TaskCanceledException)
{
// swallow cancel exception.
// this can occur when user canceled browser session and restarted.
// Previous session is forcefully canceled at start of ExecuteLoginChallenge cauing this exception.
LogHelper.Debug($"'Login attempt was via browser roundtrip canceled.");
return false;
}
}
private void EnsureNoLoginChallengeActive()
{
if (IsLoginSessionStarted)
{
(_browser as IBrowserExtra)?.TryCancel();
}
}
private static bool IsLoginSessionStarted => s_loginClient != null;
}
I need to implement chat on my web project. How to implement it on one page - there are many articles about it. But I need to have ability :
1. Notify other users, that somebody logged to site (on any page, not only on chat page)
2. Notify other users, that somebody logout
So, I have the following code of hub:
public void Connect()
{
try
{
var id = Context.ConnectionId;
string username = Context.User.Identity.Name;
var currentUser = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
if (currentUser == null)
{
AddNewUserToCollection();
}
else
{
// update ConnectionId for sure (connection id is changed sometimes (probably if user is logged out and login again))
if (currentUser.ConnectionId != id)
{
var companyId = _chatRepository.GetCompanyIdOfUser(username); // throws exception if companyId is null
Groups.Remove(currentUser.ConnectionId, companyId.ToString());
Groups.Add(id, companyId.ToString());
currentUser.ConnectionId = id;
//Clients.Group(companyId.ToString()).onNewUserConnected(username);
}
}
}
catch(InvalidCompanyException c_ex)
{
Clients.Client(Context.ConnectionId).onErrorMessage($"User '{c_ex.Username}' does not exist");
}
}
public void Disconnect()
{
string username = Context.User.Identity.Name;
var item = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
if (item != null)
{
connectedUsers.Remove(item);
Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
}
}
public override Task OnDisconnected(bool stopCalled)
{
var item = connectedUsers.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
if (item != null)
{
connectedUsers.Remove(item);
Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
}
return base.OnDisconnected(stopCalled);
}
and I added the following code to _layout.cshtml:
<script>
$(document).ready(function () {
var chat = $.connection.chatHub;
$.connection.hub.start().done(function () {
chat.server.connect();
});
});
</script>
to notify other users, that the current user is logged. But debugger says, that pair OnDisconnected/Connect is called every time, when user reload page (go thru pages) with different connectionId. When I remove this client code - that pair is not called. How to implement it correctly, to notify other users, that somebody is online, but without reconnect each time?
Since you have the connect() call in a razor (_layout.cshtml) page, you will effectively be disconnecting/connecting on every post back (page load).
The scenario you're trying to use is best in a SPA (or AJAX) scenario where navigation is handled asynchronously by client side JavaScript. Your current setup is refreshing the screen, reloading the JavaScript, re-running the document ready() function with each server rendered navigation.
Another alternative is to use the client's actual user Id, and pass that to the server.connect(id) method. Then use this user Id to track the user activity instead of the hub ConnectionId.