I have spent the better part of the last few weeks building my Score Predict app in Xamarin.Forms. After much emphasis by Xamarin at their Evolve event in 2014 I wanted to get a sense as to how ready Forms was for primetime, especially with the recent 1.3 release.
Sadly, while there are many positives with Forms, certain features are weird or just not developed enough for me to give the platform a full vote of confidence and make me choose to use it over more mature approaches such as those involving MVVMCross. Let’s look at a couple:
Appearance
The idea for Forms, conceptually, is very ambitious: unify the definition of the UI and allow each platform to translate that definition into a native user interface. Achieving this would effectively negate the single biggest weakness of “one and done” platforms like Titanium and PhoneGap, which is allowing a single UI but creating one that follows the idioms of the target platform.
For the most part Forms nails this pretty well. Where you mainly run into issues is when applying a custom theme, but most of this can be solved with custom renders or by leveraging the abilities of the platform as in the case of Android.
However, it is clear the team is still not sure what do on at least one element in iOS: the toolbar. This concept is fairly straightforward in Android (Action Bar) and Windows Phone (Application Bar), but is less clear on iOS which has limited space in the Navigation bar. At this point the team has settled on the concept of primary and secondary navigation.
On Windows Phone and Android, Primary will make the action available outside the overflow until the space is consumed. Secondary places the item directly into the overflow (application menu on WP and the pop out on Android). On iOS though you have two options: specify both as primary and get this:
Use Secondary and you get this:
Honestly, between the two the former is the better looking, but not by much. I dont recall ever seeing an iOS app putting two links in the upper right or left. Generally, you get only two buttons with the left primarily be used for navigation.
There actually is a precedent for handling this scenario is iOS, the toolbar. This was established in iOS7 and has become an integral part of many of Apple’s own apps, such as Maps:
It seems like this would be the preferred way forward, or at least allow the usage of the left hand side of the navigation bar.
It also seems like you cannot Custom Render the TabbedPage interface. I was trying to after this to fix Android which looked like this:
Despite defining a Custom renderer for TabbedRenderer I could not get a reference to the TabWidget or the TabBar. The only way I found to correct this was to visit http://jgilfelt.github.io/android-actionbarstylegenerator/ which allowed me to create a Style that I could apply to the MainActivity responsible for launching the application.
[Activity(Label = "Score Predict", MainLauncher = true, Icon = "@drawable/app_icon", Theme="@style/Theme.Maintheme")] public class MainActivity : FormsApplicationActivity, IPageHelper {
Kind of weird, since one would think I should be able to do this via the Custom Rendering engine. Here is a link to the styles as generated by the website: https://gist.github.com/xximjasonxx/d5a8f36f9631e8ff0141
Navigation
On the topic of navigation, this is not done well in Xamarin.Forms and for Forms to be successful its going to have to be done better since Navigation is one of the core elements in a successful experience. However, one has to undertake significant effort to support even the most standard user flow in Android and Windows Phone.
Consider a simple app with a login function that fronts the main app. It is fairly standard paradigm where the user must login before being granted access to the app. Here is the story for each of the platforms:
iOS
The user should launch the app and if no valid authentication information is available the user should be placed on the Login screen with access to a create login screen. Upon successful login or registration the user should be placed into the app with no way back to the login screen without invoking a logout function.When the user launches the app and valid authentication information is available, the user is immediately placed on the main page.
Android & Windows Phone
The user should launch the app and if no valid authentication information is available, the user should be placed on the login screen. Pressing the hardware back button should cause the user to leave the app. Successful login or registration should place the user on main page. From this page, if the back button is pressed the user leaves the app.The user should have no way to reach the login screen without invoking the Logout function. If the user launches the app with valid credentials, they are immediately placed into the main page.
Forms provides the Navigation property to all ContentPage instances as a means to abstractly control navigation. Primarily you will find Push* and Pop* methods for control navigation. The issue is two things: 1) the presence of the back button on Windows Phone and Android and 2) the different way iOS presents things versus Android and Windows Phone.
The bottom line is the way navigation is presented to the user is very different on a per platform bases. For example, iOS will make heavy use of modals because developers can completely control when the modal gets dismissed. In Android and WP, developers must be more careful due to the presence of the hardware back button.
WP and Android users will expect the Back button to do different things based on the screen, therefore successful apps must take this into account. Some may point out that you could override the Back button to suppress it or change its behavior, but this is not at all recommended by any platform guidelines (further, I have not found a way to suppress it as returning false in the override does nothing).
The best way around this that I have found is to leverage Dependency Injection to enable navigation (especially at the start) to be handled differently depending the platform. To that end, I defined the IStartupPageHelper and IPageHelper interfaces. Since each platform defines its dependencies through injection I can ensure the appropriate implementations are used per platform. For iOS I defined the MainPageWithModalStartupHelper to assist with determine the startup page. Here is the source:
public class MainPageWithModalStartupPageHelper : IStartupPageHelper { #region IGetMainPage implementation public Xamarin.Forms.Page GetLoginPage() { var mainPage = GetMainPage(); mainPage.Navigation.PushModalAsync(PageFactory.GetLoginPage(), true); return mainPage; } public Xamarin.Forms.Page GetMainPage() { return PageFactory.GetHomePage(); } public Xamarin.Forms.Page GetUsernamePage(User user) { var mainPage = GetMainPage(); mainPage.Navigation.PushModalAsync(PageFactory.GetUsernamePage(user)); return mainPage; } #endregion }
As you can see, iOS views the MainPage as its root, regardless of the user state. However, if we want to display the Login or Username pages, we display them as modals.
On Windows Phone and Android we use StandardStartupPageHelper
public class StandardStartupPageHelper : IStartupPageHelper { #region IStartupPageHelper implementation public Xamarin.Forms.Page GetMainPage() { return PageFactory.GetHomePage(); } public Xamarin.Forms.Page GetLoginPage() { return PageFactory.GetLoginPage(); } public Xamarin.Forms.Page GetUsernamePage(User user) { return PageFactory.GetUsernamePage(user); } #endregion }
This is much simpler because we are simply displaying the page as the root.
Once we have our startup page the user will either attempt to login or create a user; this is done within a NavigationPage component so as to provide navigation to iOS and Android (Windows Phone doesnt have anything like this). Once we are ready to return to the MainPage we invoke methods on our IPageHelper implementation. This is where things get very interesting. On iOS it looks like this:
public class TouchPageHelper : IPageHelper { private INavigation Navigation { get { return Resolver.CurrentResolver.GetInstance(); } } #region IPageHelper implementation public void ShowLogin() { Navigation.PushModalAsync(PageFactory.GetLoginPage(), true); } public void ShowMain() { Navigation.PopModalAsync(true); } #endregion }
I should point out that my code is rolling a custom DI implementation that I wrote; my plan is to move to Autofac in the near future.
iOS is pretty straightforward. To show the Login screen we “Push” that page onto the stack as a modal. To return to Main we “Pop” the modal. In fact, this class is located in my Core project and is not platform specific.
On Android, my attempts to modify navigation stack did not end well and I stumbled across this bit of code (pre 1.3) to physically change the root page of the application:
public class MainActivity : FormsApplicationActivity, IPageHelper { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); Forms.Init(this, bundle); LoadApplication(new App(new StandardStartupPageHelper(), new ServiceInjectionModule(), new DroidInjectionModule(this))); } public void ShowLogin() { SetPage(PageFactory.GetLoginPage()); } public void ShowMain() { SetPage(PageFactory.GetHomePage()); } }
As you can see, we are actually implementing this interface on the Launcher activity for our app. This, so we can make calls to Obsoleted “SetPage” which resets the root of the application. Calling this method will change the root of the application and allow us to exit the application if the Back button is pressed since this will become the first page in the stack.
Now, you may be expecting to see a similar implementation for Windows Phone and you would be wrong. There may be a way to do something similar but my attempts to call ConvertToUiElement ended in failure. However, on Windows Phone navigation is tracked globally, so we can actually manipulate the stack. Here is the implementation of IPageHelper on Windows Phone; located in the Core project using Forms code:
public class PhonePageHelper : IPageHelper { private INavigation NavigationProperty { get { return Resolver.CurrentResolver.GetInstance(); } } /* dont use the PageFactory - no navigation controller concept in Windows Phone */ public void ShowLogin() { NavigationProperty.PushAsync(new LoginPage()); ClearStackTo(); } public void ShowMain() { NavigationProperty.PushAsync(new MainPage()); ClearStackTo(); } private void ClearStackTo() where T : Page { var stack = NavigationProperty.NavigationStack; if (stack.Count > 1) { var page = stack.First(); if (page.GetType() == typeof (NavigationPage)) page = ((NavigationPage) page).CurrentPage; var typesMatch = page.GetType() == typeof(T); while (!typesMatch) { NavigationProperty.RemovePage(page); page = stack.FirstOrDefault(); if (page == null) throw new InvalidOperationException("Could not find the requested page"); if (page is NavigationPage) page = ((NavigationPage) page).CurrentPage; typesMatch = page.GetType() == typeof(T); } } } }
Here is what is happening. Whenever ShowLogin or ShowMain is called we will “Push” that page onto the stack. This places the instance of this page at the end of our stack. We will then Clear all pages from the stack up to that type.
Notice also that I do not use my PageFactory here. The reason is the page is currently inside a NavigationPage which is located at the root, despite Windows Phone not rendering anything for this. Attempting to return another NavigationPage (which is what PageFactory is returning) will cause an error due to more than one Root element being present. The solution here is to simple new up the page without the NavigationPage.
As a side note, in the IPageHelper example you will see my extracting an INavigation dependency. That is simply the Navigation reference given to the root page, assigned in my App class constructor:
public App(IStartupPageHelper getPageHelper, params InjectionModule[] modules) { Resolver.CurrentResolver.Initialize(modules); _readUserSecurityService = Resolver.CurrentResolver.Get(); SetMainPage(getPageHelper); Resolver.CurrentResolver.AddModule(new CoreInjectionModule(MainPage.Navigation)); }
Closing
Let’s be honest. The Forms team has only made this available for about a year and the progress has been immense. It would be insane to expect such an endeavor to be perfected so quickly. As a concept, I absolutely love Forms (and the ability to use Xaml for iOS). Score Predict is so far along after 3wks I want to jump on this tool and use it for my next client. However, as a consultant I can see they’re are some very rough edges that must be smoothed out, least of which is the Navigation.
Remember, the goal of Forms was to enable the creation of apps through a unified UI definition that gets translated into a native app from Xaml or code. This approach enables the developer to have a minimal understanding of the platforms to quickly create an App in a language they understand. In reality, the developer continues to need intimate knowledge of each platform to “plug the holes” present in Forms (Appearance and Navigation in this case).
I must admit that it is possible there are better ways to do some of the things above; if so, it is not well-known since my searching turned up nothing other than just how important a solid understanding of DI is for Cross Platform Mobile Development.
I look forward to what the team delivers in future releases and hope they stay on track cause this is really something I would love to succeed cause it is truly an awesome remarkable concept.
Full source for this project is available here: https://github.com/xximjasonxx/ScorePredictForms
Reblogged this on Dinesh Ram Kali..
LikeLike
Hi Kym,
Good job, we really need this feature in out project. Can you please share the exact code which you’ve shown in this blog (not the github code, I’ve already gone through the github code). I’m more interested into how to manage MainPageWithModalStartupPageHelper and TouchPageHelper calls (when and how exactly invoke calls to these services)
LikeLike
Greetings Hrishikesh,
So, I have been going back and forth on this topic for some time now. Can you give me a better idea what you mean when you say “manage … calls”? I am a bit confused and want to make sure I answer you correctly.
LikeLike
HI xximjasonxx,
I can’t find the sulution about Navigation. Can you upload the project contain MainPageWithModalStartupPageHelper ?
Thanks in advance
Jackie
LikeLike
Greetings Jackie,
I can upload something since the Forms project has since change and I no longer rely on this strategy. I am working on ways to avoid doing what I described above. I will see what I can dig up this weekend.
Thanks,
LikeLike
Hi, I’m fairly new to Xamarin Forms (~4 months), but it seem your android interface implementation could leverage Android Activities w/ NoHistory=true to keep navigation working properly around an initialization screen and login/logout functionality. I do something similar to handle a SplashScreen in an app I have designed on the Forms framework.
LikeLike
Shane,
So the issue is that Forms operates as a container within a singular activity. For Android (and Windows Phone) where a hardware back button is present the back button is intercepted by the framework so that you do not immediately leave the application (as you since there is only ever one Activity). Now, if you elect to, as it sounds like you have, combined Forms with vanilla Xamarin.Android, there is more control to be had, but then you, at least partially, lose the benefit of Forms
LikeLike
Ah! I definitely agree with you then. I am indeed talking about combining functionality. One takes the good with the bad I suppose. Thanks for your great post, by the way! I’m always interested to see what fun, exciting things people are doing in this awesome (but nowhere near perfect) platform!
LikeLike
Verry thoughtful blog
LikeLike