How to navigate from ContentPage to ContentPage? - c#

I am trying to navigate from one ContentPage to another.
In my WelcomePage, I have a button which should direct me to the other page with following content
_loginButton.Clicked += (s, e) =>
{
if (OnLoginEnter != null) OnLoginEnter();
};
Now, in my PageManager class, where I try to manage all pages, I have something like this
public class PageManager : Page
{
#region Fields
public static WelcomePage WelcomePage = new WelcomePage();
public static LoginPage LoginPage = new LoginPage();
#endregion
public Page GetStarted()
{
return WelcomePage.Generate();
}
}
And finally, in MainActivity.cs (Android project), I am trying to manage all this with the following:
//I have to use a method for that for some reason. Can't call .Generate() directly.
SetPage(PageManager.GetStarted());
PageManager.WelcomePage.OnLoginEnter += () =>
{
SetPage(PageManager.LoginPage.Generate());
};
I find this to be very confusing and unproductive.
All I want is a way to navigate from one ContentPage to another.

I share your frustration - however, I understand Xamarin are working on improving the page navigation.
For now, you have a few options.
If you are happy to have a flow, so no need to replace the whole page but be able to go back, use a NavigationPage or the PageManager echyzero mentioned.
If you are going to have an options page, use a MasterDetailPage and replace the detail.
Alternatively, create an interface which has a method called SetRootPage and implement it for both Android and iOS. Pass the instance of the interface in to your App.Run(IPageLoader) on startup and you can then call the SetRootPage from the App class to replace the Root. I did report a bug with this a while ago, which may have been fixed now. In the meantime, my workaround was to use the CarouselPage, with only a single page on the Carousel, which I replace when needed - it actually works quite well, if a bit hacky.

Answer taken from Xamarin.Forms documation here
listView.ItemSelected += async (sender, e) => {
var todoItem = (TodoItem)e.SelectedItem;
var todoPage = new TodoItemPage(todoItem); // so the new page shows correct data
await Navigation.PushAsync(todoPage);
};
So for you, it would probably look something like this:
PageManager.WelcomePage.OnLoginEnter += () =>
{
await Navigation.PushAsync(PageManager.LoginPage.Generate());
};
For iOS, you will need to do this through a NavigationPage as shown below (example is inside the AppDelegate.cs file)
window.RootViewController = new NavigationPage (new MyLoginForm ()).CreateViewController ();
After that you can call await Navigation.PushAsync('any page you want') inside any ContentPage in your application.

Related

Can you change a tabbedpage's child navigation stack from app.xaml.cs?

I'm trying to set up automatic logging in when a user reopens my app so that it will check for existing login credentials saved in app.current.properties and when finding some, navigate the user to the proper tabbedpage, and change the current page from the default login screen to the account main page.
I've tried something that through research seemed like it should work, specifically accessing the child page in question (login) and pushing a new page onto it via its navigation element. This hasn't worked, and neither have some other much more janky solutions I've tried implementing.
In App.xaml.cs
protected override void OnStart()
{
AuthService.LoadUserCredentials();
if (AuthService.authenticated)
{
var page = new MainTabs();
page.CurrentPage = page.Children[2];
ContactService.RefreshData();
var page2 = new PinCodePage(false);
page.CurrentPage.Navigation.PushAsync(page2).ConfigureAwait(false);
MainPage = page;
}
}
Should note that my app is setup in a way that the mainpage of the whole application is a tabbedpage, which has 3 pages, the third one being the Login page. When a user normally logs in successfully, that page has a new page added over it, Account, and the Login page is popped out, effectively removing it.
The expected result is that when a user opens the app after closing it/shutting it down after having been logged in, it checks for user stored info, and finding it, replaces the Login page with the Account page. If it finds nothing, it ignores the replacement of the login page and leaves it there.
you could try this (here is a sample simple,you could use your page and data replace):
protected override void OnStart()
{
if (App.Current.Properties.ContainsKey("isLogin"))
{
bool isLogin = (bool)App.Current.Properties["isLogin"];
if (isLogin)
{
TabbedPage p = MainPage as TabbedPage;
var navigationPage = new NavigationPage(new AccoutPage());
navigationPage.IconImageSource = "tab_accout.png";
navigationPage.Title = "Accout";
p.Children.Add(navigationPage);
p.Children.RemoveAt(2);
p.CurrentPage = navigationPage;
}
}
}
update:
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
App.Current.Properties["isLogin"] = true;
App.Current.SavePropertiesAsync();
TabbedPage p = App.Current.MainPage as TabbedPage;
var navigationPage = new NavigationPage(new AccoutPage());
navigationPage.IconImageSource = "tab_accout.png";
navigationPage.Title = "Accout";
p.Children.Add(navigationPage);
p.Children.RemoveAt(2);
p.CurrentPage = navigationPage;
}
}

How to navigate from one page to other page in the page constructor in Xamarin forms

i am using xamarin forms and i have a scenario where i have 3 pages(content pages) .
In page1 on the click of a button i need to goto page2. and in the the page2 check a flag to decide whether to stay in page2 or redirect to page3. i am trying to do this logic in page 2 constructor and my NavigationStack is coming up as empty. Please suggest.
Page2 constructor:
public Page2()
{
InitializeComponent();
If(check==true)
{
Application.Current.MainPage = new NavigationPage(new Page3());
}
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.InitializeData();
}
and in intialize data write the if condition
You have to use the PushAsync method to not lose the previous stack.. What u're doing is just keeping the stack with minimal size (1 page). Try this code, but you have to await it, so you 're better not doing it the constructor
await Application.Current.MainPage.NavigationPage.PushAsync(new NavigationPage(new Page3()));

Why Init() method is not called when I navigate to the ViewModel second time?

I develop Win 8.1 application using MvvmCross 3.5.1. The user sequentially goes through the some views and returns to the first view from the last view. Everything works perfect during first iteration of the workflow. But when the user starts the workflow again - Init() methods in viewmodels are not called.
For example, interaction between FirstViewModel and SecondViewModel looks like below.
FirstViewModel:
ShowViewModel<SecondViewModel>(
new
{
code = ItemCode,
descr = ItemDescription
});
SecondViewModel:
public void Init(string code, string descr)
{
...
}
So simple but works only one time :(
What reasons may entail such behavior?
As workaround I tried to load viewmodel "manually":
var d = new Dictionary<string, string>
{
{"code", ItemCode},
{"descr", ItemDescription}
};
var b = new MvxBundle(d);
var r = new MvxViewModelRequest<SecondViewModel>(b, null, null);
var m = Mvx.Resolve<IMvxViewModelLoader>().LoadViewModel(r, null);
It solved the problem with Init() methods calling. But I don't know how to show the viewmodel using the m variable. Anyone knows?
Apologies for my poor english and thanks in advance!
Init() is only being called once, because Windows 8.1 apps cache pages. Hence, the ViewModel for that page is not ever destroyed and hence the Init() method is not called again.
You can make your own BasePage which overrides this behavior by overriding OnNavigatedTo:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.New)
ViewModel = null;
base.OnNavigatedTo(e);
}

Call an Activity method from Fragment

I'm using ToDoActiviy.cs for user login, this class got this method:
[Java.Interop.Export()]
public async void LoginUser(View view)
{
if(await authenticate())..
This method is called from .axml file from Button widget android:onClick="LoginUser" I changed this for android:onClick="LoginUserClick" This last method create a dialog fragment for show different logins accounts.
Now from the Dialog Fragment(Is situated on another class) I want to hand the event for the button click on the dialog fragment and call this method from ToDoActivity.cs.
On dialog fragment class I hand the click event like this:
private void ButtonSignInFacebook_Click(object sender, EventArgs args){
//Here code for call to LoginUser method from 'ToDoActivity.cs'
ToDoActiviy.cs act = new ToDoActivity();
act.LoginUser();
}
I need to pass a View but I tried a lot of things and any works..
Someone can help me?
Thanks in advance ;)
I would like to make a slight modification to #guido-gabriel 's answer.
In C# syntax, it will be
((ToDoActivity)Activity).yourPublicMethod();
Getter/Setter Methods in Java are mapped to Getter Setter properties in Xamarin.Android
Finally I fix it ! I had to change the parameters of the method and create it without parameters.. and now Is working. Both solutions are good:
((ToDoActivity)Activity).LoginUserFacebook();
//ToDoActivity act = new ToDoActivity();
//act.LoginUserFacebook();
Adapt and use the snipped below in your fragment
var casted = Activity as MyActivityName;
if (casted != null) {
casted.IWantToCallThisMethodFromMyFragment();
}
You have to call the method from the activity. Have you tried?
((YourActivityClassName)getActivity()).yourPublicMethod();
I.E.
((ToDoActivity)getActivity()).yourPublicMethod();
This is not really a good practice to do. Why?
Doing this couples the Fragment tightly to this particular Activity type, meaning it will not be possible to reuse the Fragment elsewhere in the code.
Instead I suggest you rely on the Activity subscribing to an event or implementing some kind of callback method in order to do the desired action after login.
It could also seem like your Activity might be containing a lot of logic that could be split out into a shared library of some kind. Making it possible to reuse that code on another platform, for instance iOS in the future.
So since your are in charge of newing up the Fragment, I would do something like this instead:
public class LoginFragment : Fragment
{
Action _onLoggedIn;
public static void NewInstance(Action onLoggedIn)
{
var fragment = new LoginFragment();
fragment._onLoggedIn = onLoggedIn;
return fragment;
}
private void Login()
{
// login user
// after loggedin
_onLoggedIn?.Invoke();
}
}
Then in your Activity:
private void LoginUser()
{
// whatever
}
var loginFragment = LoginFragment.NewInstance(LoginUser);
// fragment transaction here...

Caliburn.Micro Go Back called in On Activate not working in WinRT

This question is specific to Windows Phone 8.1 (WinRT); it may also be applicable Windows 8.1. I am using Caliburn.Micro 2.0.1
In my ViewModel's OnActivate I check whether an item is a database, if it isn't, I want to navigate back to the previous page.
The simplist solution will be just to call GoBack in the OnActivate method (this works in Windows Phone 8.0):
INavigationService _navigationService;
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
_navigationService.GoBack()
}
}
To navigate to the view model I call:
_navigationService.NavigateToViewModel<MyViewModel>(_param);
But it does not work, it ignores the GoBack call and stays on the page which I do not want to view.
When stepping through the code you can see that the GoBack code is called inside the NavigateToViewModel method; I expect this is the reason why it does not work (something to do with a queuing issue maybe?).
I have a very "hacky" solution that involves a timer (that works), but I really despise it since it is prone to threading issues and has the possibility of being called during the NavigateToViewModel call (if it takes long to finish), which will then again not work:
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
DispatcherTimer navigateBackTimer = new DispatcherTimer();
navigateBackTimer.Interval = TimeSpan.FromMilliseconds(300);
navigateBackTimer.Tick += GoBackAfterNavigation;
navigateBackTimer.Start();
}
}
public void GoBackAfterNavigation(object sender, object e)
{
_navigationService.GoBack();
(sender as DispatcherTimer).Stop();
}
Is there a better way to navigate back? Why doesn't the GoBack work in OnActivate? Is there a way to get it to work in OnActivate?
You can use
Execute.OnUIThreadAsync(() => /* navigationCode */);
instead of a timer to queue the action immediately after the processing of the current stack has finished.

Categories

Resources