Navigating between Multiple pages in Xamarin.forms? - android

I have a content view and left navigation drawer which are commons on 4 pages and Content view contains 4 icons which navigates between 4 different pages. I want to keep my content view and left navigation drawer on all 4 pages.
I create a Master details page and set Master page to Left navigation Drawer and i am changing the details page each time.
I got the exception as android can navigate only page at a time while navigating between multiple pages.
Following is my Rootpage and ContentView Page
public class RootPage : MasterDetailPage
{
LeftNavigationPanel menuPage;
public RootPage(string detailSel)
{
menuPage = new LeftNavigationPanel(); //This is the left navigation class. rename later.
Master = menuPage;
if (detailSel.Equals(""))
{
var detail = new NavigationPage(new Tabpage());
Detail = detail; //homepage
detail.Icon = "leftnav.png";
App.navigation = detail.Navigation;
}
else if (detailSel.Equals("1"))
{
var detail = new NavigationPage(new Post());
Detail = detail; //homepage
detail.Icon = "leftnav.png";
App.navigation = detail.Navigation;
// System.Diagnostics.Debug.WriteLine("page1: " + Navigation.NavigationStack[Navigation.NavigationStack.Count-1]);
}
else if (detailSel.Equals("2"))
{
var detail = new NavigationPage(new TrackTabPage());
Detail = detail; //homepage
detail.Icon = "leftnav.png";
App.navigation = detail.Navigation;
// System.Diagnostics.Debug.WriteLine("page1: " + Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]);
}
}
}
ContentView
public partial class HomeContentView : ContentView
{
public HomeContentView()
{
InitializeComponent();
}
private async void read_click(Object sender, EventArgs e)
{
if (!((Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]) is Tabpage))
{
//await Navigation.PopAsync();
await Navigation.PushAsync(new RootPage(""));
// this.Navigation.PopAsync();
}
}
private async void post_click(Object sender, EventArgs e)
{
if (!((Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]) is Post))
{
System.Diagnostics.Debug.WriteLine("page: "+ Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]);
// await Navigation.PopAsync();
await Navigation.PushAsync(new RootPage("1"));
}
}
private async void track_click(Object sender, EventArgs e)
{
if (!((Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]) is TrackTabPage))
{
System.Diagnostics.Debug.WriteLine("page: " + Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]);
// await Navigation.PopAsync();
await Navigation.PushAsync(new RootPage("2"));
}
}
private void play_click(Object sender, EventArgs e) { }
}

You should use the Navigation from the Detail-Page, because you set the Detail as a new NavigationPage().
I usually use following code to geht the INavigation to navigate inside my app:
public static INavigation GetNavigation()
{
var mdp = Application.Current.MainPage as MasterDetailPage;
if (mdp != null)
{
// Return the navigation of the detail-page
return mdp.Detail.Navigation;
}
var np = Application.Current.MainPage as NavigationPage;
if (np != null)
{
// Page is no MasterDetail-Page, but has a navigation
return np.Navigation;
}
// No navigation found (just set the page, instead of navigation)
return null;
}
And use it like the following example:
var nav = GetNavigation();
if(nav != null)
{
await nav.PushAsync(new Tabpage());
}
else
{
// Just set the page, because there is no navigation
Application.Current.MainPage = new new Tabpage();
// Or like this
Application.Current.MainPage = new NavigationPage(new Tabpage());
}
Update
I changed my answer a little bit (thanks for the comment, I just don't realized this). Do not use new Rootpage("") to change the page. With that you create a new Navigation on top of the current Navigation.. that makes no sense.
Just Push your page onto the current navigation you get by GetNavigation().
Please read the documentation about the navigation pattern in Xamarin.Forms for further informations.

Related

Layout render error when using Tasks modifying controls

Description
Hello,
Rendering of Layouts are not updated if they contains controls (ContentView) with some rendering Task !
I created a simple ContentView which contains three Image (the FireControl in the screenshot).
In the constructor, I use a simple async method, with Task.Factory.StartNew, which will play on the visibility of the Face images with a small Task.Delay between them.
In the MainPage, I load this control in a StackLayout, using a button : "Load control".
It is impossible for me to add a second control (if I press again "Load control" )!
It's like if the rendering of the StackLayout in my MainPage was not updated ...
If I dont call my task method: no rendering problem!
If I dont touch controls in my task method: no rendering problem !
If I spam-click "Load control" and "Remove control", sometimes loaded controls appears...
Is there a way to tell my StackLayout to update its rendering after adding a control?
Is there a better way than Task.Factory.StartNew to perform frame animation in a control, so as not to block the rendering?
(In this example the desired animation has been simplified)
Steps to Reproduce
My Test solution :
Test Solution
TestControl
public class TestControl : ContentView
{
protected Image FaceNormal;
protected Image FaceLoose;
public TestControl()
{
var principalImage = new Image { Source = "fire_principal.png" }; // The body
FaceNormal = new Image { Source = "fire_facenormal.png" }; // Opened eyes
FaceLoose = new Image { Source = "fire_faceloose.png" }; // Closed eyes
Content = new Grid { Children = { principalImage, FaceNormal, FaceLoose } }; // Loaded in the Content
// /!\ Causes rendering errors in the StackLayout
Task.Factory.StartNew(() => StartFaceAnimation());
}
public async Task StartFaceAnimation()
{
Dispatcher.Dispatch(() =>
{
FaceNormal.IsVisible = true;
FaceLoose.IsVisible = false;
});
await Task.Delay(2000);
Dispatcher.Dispatch(() =>
{
FaceNormal.IsVisible = false;
FaceLoose.IsVisible = true;
});
}
}
MainPage
public class MainPage : ContentPage
{
public MainPage()
{
var verticalStackLayout = new VerticalStackLayout();
var loadTestControlButton = new Button { Text = "Load control", Margin = 2 };
loadTestControlButton.Clicked += (o, e) => verticalStackLayout.Children.Add(new TestControl());
verticalStackLayout.Children.Add(loadTestControlButton);
var removeTestControlButton = new Button { Text = "Remove control", Margin = 2 };
removeTestControlButton.Clicked += (o, e) => verticalStackLayout.Children.Remove(verticalStackLayout.Children.Last());
verticalStackLayout.Children.Add(removeTestControlButton);
Content = verticalStackLayout;
}
}
The trick is to call Arrange after IsVisible, see the below modifications:
public async Task StartFaceAnimation()
{
Dispatcher.Dispatch(() =>
{
FaceNormal.IsVisible = true;
FaceLoose.IsVisible = false;
Arrange(new Rect());
});
await Task.Delay(2000);
Dispatcher.Dispatch(() =>
{
FaceNormal.IsVisible = false;
FaceLoose.IsVisible = true;
Arrange(new Rect());
});
}
It redraws the controls!

Xamarin forms PushAsync opens a blank page

I'm currently working on a project where I received push notification on the device, and when taping the notification it should open a specific page.
I tried it on an Android 5.1, but when I tap the notification, it first opens the page and immediately after that it opens a blank page, whithout any navigation bar and I don't understand why...
Here is my code:
When I receive the the message on android I perform the following:
Intent intent = new Intent(this, typeof(MainActivity));
intent.PutExtra("key", "message");
PendingIntent pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
Notification notif = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Alert")
.SetContentText("alert message")
.SetAutoCancel(true)
.SetDefaults(NotificationDefaults.Sound | NotificationDefaults.Vibrate)
.SetContentIntent(pendingIntent)
.SetPriority((int)NotificationPriority.High)
.Build();
NotificationManager notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify(0, notif);
When taped it opens the MainActivity which does the following:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
if (app == null)
app = new App();
LoadApplication(app);
// If the user tapped a notification
if (Intent.Extras != null)
{
Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));
MessagingCenter.Send<Data>(data, "Show data");
}
}
Finally I have a DataPage (sets as the MainPage of the App, inside a NavigationPage) which does this:
public DataPage()
{
MessagingCenter.Subscribe<Data>(this, "Show data", (sender) =>
{
await Navigation.PushAsync(new DataDetail(sender));
});
}
I don't really understand why this does not work properly...
Especially since if I do
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(new DataPage(data)));
And create a second constructor in the DataPage:
DataPage(Data data)
{
await Navigation.PushAsync(new DataDetail(data));
}
It works fine. (But I don't like this, it looks very... not nice)
I can barely understand the connections between your Data, your DataDetail, your DataPage and your MainPage.
By my side, I think Data is like a data model based on your code Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));, but inside this class you code await Navigation.PushAsync(new DataDetail(data));, where does this Navigation come from?
Never mind, I guess that you have a MainPage wrapped by NavigationPage, and you want to navigate it to your DataPage when you click the notification. If so, then there is no problem with your Notification part.
To show the data in your DetailPage, you can create a constructor with parameter for example:
public partial class DataPage : ContentPage
{
public DataPage()
{
InitializeComponent();
}
public DataPage(int count)
{
InitializeComponent();
//set the data to the UI
label.Text = "This is Count: " + count;
}
}
You can create a single instance for Navigation in MainPage, so can it be used in Data class:
public static INavigation Navi;
public MainPage()
{
InitializeComponent();
Navi = this.Navigation;
}
Then my Data class is as simple as following:
public class Data
{
public Data()
{
MessagingCenter.Subscribe<Data>(this, "show data", (sender) =>
{
MainPage.Navi.PushAsync(new DataPage(this.Count));
});
}
public int Count { get; set; }
}
Finally in MainActivity:
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
if (Intent.Extras != null)
{
int count = Intent.Extras.GetInt("count", -1);
var data = new Data() { Count = count };
MessagingCenter.Send<Data>(data, "show data");
}
}
You can modify the Data class as you need. The following is the rendering image of my demo:
Update:
Based on our discussion, your message is directly sent to DataPage, then in App class in PCL, wrap this DataPage with NavigationPage: MainPage = new NavigationPage(new DataPage());.
In the MainAcitvity send message like this:
if (Intent.Extras != null)
{
int count = Intent.Extras.GetInt("count", -1);
var data = new Data() { Count = count }; //fake data, change it to your data
var navipage = App.Current.MainPage as NavigationPage;
var datapage = navipage.CurrentPage as DataPage;
MessagingCenter.Send<DataPage, Data>(datapage, "show data", data);
}
And in the DataPage:
public DataPage()
{
InitializeComponent();
MessagingCenter.Subscribe<DataPage, Data>(this, "show data", (sender, arg) =>
{
Navigation.PushAsync(new DataDetail(arg));
});
}
Of course we need to create an instructor in DataDetail with parameter:
public DataDetail(Data data)
{
InitializeComponent();
}
So I finally solved the problem.
It seems that it was caused by the fact that I made a static App variable in the MainActivity in order to create it only once.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
static App app;
protected override void OnCreate(Bundle bundle)
{
...
if(app == null)
app = new App();
...
}
}
I did that because otherwise the Current.Properties of the app instance was reset.
Now I changed that and I no longer have this problem:
I call the Current.SavePropertiesAsync() to save the Current.Properties and therefore I can remove the static instance of App in the MainActivity. It solved the blank page problem.
But this made another problem. When I tapped the notification, it works correctly the first time, but after that it opened the requested page multiple times (The second notification tapped opened 2 pages, the third opened 3, ...). I don't understand why it did that, but it seemed to be caused by the MessagingCenter. So I remove the MessagingCenter part and simply did this in the MainActivity:
if (Intent.Extras != null)
{
Data data = JsonConvert.DeserializeObject<Data>(Intent.Extras.GetString("key"));
DataPage currentPage = (DataPage)((NavigationPage)App.Current.MainPage).CurrentPage;
currentPage.OpenDataDetails(data);
}
And in the DataPage I have this function
public async void OpenDataDetails(Data data)
{
await Navigation.PushAsync(new DataDetail(data));
}
This does work as expected, so now I'm happy.
But still, I don't understand why when I had a static App instance in MainActivity it opened a blank page, nor why it opened multiple pages with the MessagingCenter...

How to make an async button click update a TextView dynamically with Xamarin Android

In Xamarin Native Android app I am calling methodOne() and getting some details. After this I want to updated the details to text view and then calls the methodTwo(). After executing MethodTwo I have to clear the text details from text view. I tried with
RunOnUiThread(() => tColorDetail.SetText("text", TextView.BufferType.Normal));
The details are displaying after finishing all the methods it is not showing immediately after executing MethodOne().
What is missing here?
.....................................................
public class MainActivity : Activity
{
int count = 1;
ticket Ticket;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button bClick= FindViewById<Button>(Resource.Id.bSub);
try {
bClick.Click += delegate {
Job Job;
Action act = new Action();
EditText Address = (EditText)FindViewById(Resource.Id.Address);
act.Address = Address.Text.ToString();
if (!ValidateIPv4(act.Address))
{
ShowMessage("Invalid IP");
}
else
{//TextView Mode = (TextView)FindViewById(Resource.Id.Mode);
string httpUrl = act.iPAddress ;
Ticket = act.ExecuteTicket(httpUrl, "Ticket");
//RunOnUiThread(() => tColorMode.SetText("text", TextView.BufferType.Normal));
// this.RunOnUiThread(() => setTicket(true));
setStatus(" Ticket : Success", true);
setTicket(true);
//tColorMode.SetText("text",TextView.BufferType.Normal);
//tColorMode.Invalidate();
Job = act.executeJob(httpUrl, Ticket);
setStatus(" Job : Success", true);
act.File(httpUrl, Ticket, Job);
setStatus("File Retrieval : Success", true);
setTicket(false);
setStatus("File Retrieval : Success", false);
}
};
}
catch (Exception ex)
{
ShowMessage(ex.Message.ToString());
}
}
public bool ValidateIPv4(string ipString)
{
if (String.IsNullOrWhiteSpace(ipString))
{
return false;
}
string[] splitValues = ipString.Split('.');
if (splitValues.Length != 4)
{
return false;
}
return true;
}
public void setTicket(bool b)
{
//TextView tMode = (TextView)FindViewById(Resource.Id.tMode);
// tMode.SetText("text",TextView.BufferType.Normal);
RunOnUiThread(() => {
TextView tMode = (TextView)FindViewById(Resource.Id.Mode);
tColorMode.SetText("text",TextView.BufferType.Normal);// b==true?Ticket.getProcessing():"";
((TextView)this.FindViewById(Resource.Id.tRes)).Text = b == true ? Ticket.getHeight().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tFormat)).Text = b == true ? Ticket.getFormat().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tOrode)).Text = b == true ? Ticket.getType().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tRot)).Text = b == true ? Ticket.getValue().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tSource)).Text = b == true ? Ticket.getSource().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tSize)).Text = b == true ? Ticket.getAutoDetect().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tExpo)).Text = b == true ? Ticket.getAuto().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tSharp)).Text = b == true ? Ticket.getSharp().ToString() : "";
((TextView)this.FindViewById(Resource.Id.tComp)).Text = b == true ? Ticket.getComp().ToString() : "";
//tColorMode.Invalidate();
}); }
private void ShowMessage(string msg)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
AlertDialog dialog = builder.Create();
dialog.SetMessage(msg);
dialog.SetButton("OK", (s, ev) =>
{
dialog.Cancel();
});
dialog.Show();
}
private void setStatus(string status,bool b)
{
((TextView)this.FindViewById(Resource.Id.tStatus)).Text = b == true ? status : " ";
}
}
Without access to the full code it is tricky to give you an answer but
Ticket = act.ExecuteTicket(httpUrl, "Ticket");
this line has the potential to take a while to complete if it performs network operations and probably runs in a background thread not to freeze the UI.
Presumably when you get to
// this.RunOnUiThread(() => setTicket(true));setStatus(" Ticket : Success", true);
setTicket(true);
the act.ExecuteTicket call might not have finished
also if there are async operations taking place in
Job = act.executeJob(httpUrl, Ticket);
it could take a while to complete
All taken into consideration, you could probably be getting to
setTicket(false);
setStatus("File Retrieval : Success", false);
before the first calls finish
It could be worth checking
ANSWER UPDATE after discussion in comments:
Since your problem is that your asynchronous calls are taking a long time and you are experiencing "race conditions" because of it (your UI thread is proceeding with the updates on the TextViews before it should), you can solve the situation by changing your button click delegate to an async delegate, and forcing an await on the long operations' result before proceeding.
bClick.Click += async delegate {
//...
Job = await act.executeJob(httpUrl, Ticket);
//...
}

ionic keyboard go button to next button

Reference:Change go button to next button in android
I am developing an application with sign up page using Ionic framework.
Is there any option to replace go button with next button? I want to move cursor from one field to another using next button in the keyboard.
you can use following reference to achieve your requirement in ionic.Below code is for cordova
(function($) {
$.fn.enterAsTab = function(options) {
var settings = $.extend({
'allowSubmit': false
}, options);
this.find('input, select, textarea, button').live("keypress", {localSettings: settings}, function(event) {
if (settings.allowSubmit) {
var type = $(this).attr("type");
if (type == "submit") {
return true;
}
}
if (event.keyCode == 13) {
var inputs = $(this).parents("form").eq(0).find(":input:visible:not(disabled):not([readonly])");
var idx = inputs.index(this);
if (idx == inputs.length - 1) {
idx = -1;
} else {
inputs[idx + 1].focus(); // handles submit buttons
}
try {
inputs[idx + 1].select();
}
catch (err) {
// handle objects not offering select
}
return false;
}
});
return this;
};
})(jQuery);
For adding next button , you can refer following link:
How to add Next button in Ionic soft keyboard plugin

Tabs appear above ActionBar when calling setDisplayShowHomeEnabled(false) in 4.0.3

I use ActionBar tab navigation mode and a ViewPager. This works fine in newer versions of Android, but on Android 4.0.3, tabs is in the top of the ActionBar instead of below.
If I use ActionBar.SetDisplayShowHomeEnabled(true), the problem appears to be fixed. However, up navigation then will not work.
My code looks as follows
ActionBar.SetTitle(Resource.String.DocumentDetails);
ActionBar.SetDisplayHomeAsUpEnabled(true);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
SetContentView(Resource.Layout.Main);
ActionBar.Tab tab = ActionBar.NewTab();
tab.SetText("tab1");
tab.TabSelected += (sender, args) => {
// Do something when tab is selected
};
ActionBar.AddTab(tab);
tab = ActionBar.NewTab();
tab.SetText("tab2");
tab.TabSelected += (sender, args) => {
// Do something when tab is selected
};
ActionBar.AddTab(tab);
Is this on an actual device? I have tested your code in an Emulator and can't reproduce. However, it does seem that several other people reported this to Google: https://code.google.com/p/android/issues/detail?id=36191
So a couple of ideas to fix this. Instead of using SetDisplayHomeAsUpEnabled try using the DisplayOptions property instead:
ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowHome |
ActionBarDisplayOptions.HomeAsUp |
ActionBarDisplayOptions.ShowTitle;
The important one is the ActionBarDisplayOptions.ShowHome in this case, which forces the appearance of a home icon and ActionBarDisplayOptions.HomeAsUp does what SetDisplayHomeAsUpEnabled(true) does. The last one I suppose you can guess.
If that does not help. There was one answer in that issue thread, which suggested to use an extension method to set the tab items as embedded into the ActionBar. However, that will look as if you are running on a tablet. The code is as follows:
using Java.Lang;
using Java.Lang.Reflect;
namespace SomeNamespace
{
public class ActionBarUtils
{
public static void SetHasEmbeddedTabs(Object inActionBar, bool hasEmbeddedTabs)
{
var actionBarClass = inActionBar.Class;
if ("android.support.v7.app.ActionBarImplJBMR2" == actionBarClass.Name)
{
actionBarClass = actionBarClass.Superclass.Superclass;
}
else if ("android.support.v7.app.ActionBarImplJB" == actionBarClass.Name)
{
actionBarClass = actionBarClass.Superclass;
}
try
{
var actionBarField = actionBarClass.GetDeclaredField("mActionBar");
actionBarField.Accessible = true;
inActionBar = actionBarField.Get(inActionBar);
actionBarClass = inActionBar.Class;
}
catch (IllegalAccessException) { }
catch (IllegalArgumentException) { }
catch (NoSuchFieldException) { }
try
{
var method = actionBarClass.GetDeclaredMethod("setHasEmbeddedTabs", new[] { Boolean.Type });
method.Accessible = true;
method.Invoke(inActionBar, new Boolean(hasEmbeddedTabs));
}
catch (NoSuchMethodException) { }
catch (InvocationTargetException) { }
catch (IllegalAccessException) { }
catch (IllegalArgumentException) { }
}
}
}
Then can be used like so in your Activity:
ActionBarUtils.SetHasEmbeddedTabs(ActionBar, true);

Categories

Resources