Showing posts with label xamarin. Show all posts
Showing posts with label xamarin. Show all posts

Sunday, October 14, 2018

How to implement TabLayout.IOnTabSelectedListener.OnTabUnselected with TabbedPage.ToolbarPlacement=“Bottom” - Xamarin Forms?

Leave a Comment

I just recently used android:TabbedPage.ToolbarPlacement="Bottom". I used to have the following code:

void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab) {    var playPage = Element.CurrentPage as NavigationPage;    if (!(playPage.RootPage is PhrasesFrame))       return;     var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);    var playTab = tabLayout.GetTabAt(4);    tab.SetText("Play");    tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);    App.pauseCard = true; } 

Anyone knows how can I implement this with ToolbarPlacement="Bottom" ? I have implemented both BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener but can't find any reference for UnselectedTab if there is any.

Edit:

Previous custom renderer using the default tab position and implementing TabLayout:

namespace Japanese.Droid {     public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener     {         ViewPager viewPager;         TabLayout tabLayout;         bool setup;          public MyTabbedPageRenderer(Context context): base(context){ }          protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)         {             // More codes here         }          void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)         {             UpdateTab(tab);         }          void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)         {             UpdateTab(tab);         }          void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)         {             var playPage = Element.CurrentPage as NavigationPage;             if (!(playPage.RootPage is PhrasesFrame))                 return;              var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);             var playTab = tabLayout.GetTabAt(4);             tab.SetText("Play");             tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);             App.pauseCard = true;         }          void UpdateTab(TabLayout.Tab tab)          {             // To have the logic only on he tab on position 1             if (tab == null || tab.Position != 4)             {                 return;             }              if (tab.Text == "Play")             {                 tab.SetText("Pause");                 tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);                 App.pauseCard = false;             }             else             {                 tab.SetText("Play");                 tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);                 App.pauseCard = true;             }         }     } } 

Current custom renderer using the ToolbarPlacement="Bottom":

namespace Japanese.Droid {     public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener     {         public BottomTabPageRenderer(Context context) : base(context) { }          protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)         {             base.OnElementChanged(e);              // More codes here         }          bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)         {             base.OnNavigationItemSelected(item);              UpdateTab(item)         }          void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)         {             UpdateTab(item);         }          void UpdateTab(IMenuItem item)         {             var playTabId = 4;              var title = item.TitleFormatted.ToString();             if (item == null || item.ItemId != playTabId)             {                 return;             }              if (item.ItemId == playTabId)             {                 if (title == "Play")                 {                     item.SetTitle("Pause");                     item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);                     App.pauseCard = false;                 }                 else                 {                     item.SetTitle("Play");                     item.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);                     App.pauseCard = true;                 }             }          }      } } 

So now my problem is I don't have any idea how will I implement the TabLayout.IOnTabSelectedListener.OnTabUnselected in the new custom renderer.

2 Answers

Answers 1

There is no official stuff for OnTabReselected event for TabbedPage's bottom navigation or BottomNavigationView because It doesn't use TabLayout.Tab for a start. Many overridden methods of TabbedPageRenderer not being called like SetTabIcon. If you are using IOnTabSelectedListener interface(As your first part of code) you have three methods to use.

void OnTabReselected(Tab tab); void OnTabSelected(Tab tab);           void OnTabUnselected(Tab tab); 

But when it comes to BottomNavigationView interface you have only two methods

void OnNavigationItemReselected bool OnNavigationItemSelected 

So we don't have built in OnTabUnselected method. Here you need to write custom code to make unseleted event.

I have tried this code without using custom renderer using 4 tabs pages & the xaml of tabbed written in MailPage.xaml file. First declare List<string> in App.xaml.cs file to store Title of all tabs

public static List<string> Titles {get;set;} 

Add tabs pages title in above list from MainPage.xaml.cs file's OnAppearing method

protected override void OnAppearing() {     for (int i = 0; i < this.Children.Count; i++)     {         App.Titles.Add(this.Children[i].Title);     } } 

Now go to your MyTabbedPage class in which is available in shared project.

public class MyTabbedPage : Xamarin.Forms.TabbedPage {     string selectedTab = string.Empty;     string unSelectedTab = string.Empty;     bool isValid;      public MyTabbedPage()     {         On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);          this.CurrentPageChanged += delegate         {             unSelectedTab = selectedTab;             selectedTab = CurrentPage.Title;             if (App.Titles != null)                 isValid = true;             else                 App.Titles = new List<string>();             if (isValid)             {                 MoveTitles(selectedTab);                //Pass 0 index for tab selected & 1 for tab unselected                 var unSelecteTabTitle = App.Titles[1];                //TabEvents(1); here you know which tab unseleted call any method             }         };     }      //This method is for to moving selected title on top of App.Titles list & unseleted tab title automatic shifts at index 1     void MoveTitles(string selected)     {         var holdTitles = App.Titles;         if (holdTitles.Count > 0)         {             int indexSel = holdTitles.FindIndex(x => x.StartsWith(selected));             holdTitles.RemoveAt(indexSel);             holdTitles.Insert(0, selected);         }         App.Titles = holdTitles;     }      } 

Or you can make swith case like this

void TabEvents(int index) {   switch (index)   {     case 0:         //Tab selected         break;     case 1:          //Tab unselected         break;   } } 

Few things I should mention that MainPage.xaml.cs file inheriting MyTabbedPage

public partial class MainPage : MyTabbedPage 

Structure of MainPage.xaml file

<?xml version="1.0" encoding="utf-8" ?> <local:MyTabbedPage         <TabbedPage.Children>          <NavigationPage Title="Browse">              </NavigationPage>         </TabbedPage.Children> </local:MyTabbedPage> 

Answer seems long but hope it help you.

Answers 2

As per G.Hakim's suggestion, I was able to do what I wanted to do by capturing the tab item I wanted to work on and do the necessary actions in BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected.

namespace Japanese.Droid {     public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener     {         // same as above          bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)         {             base.OnNavigationItemSelected(item);              if(item.ItemId == 4 && item.TitleFormatted.ToString() == "Play")              {                 item.SetTitle("Pause");                 item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);                 App.pauseCard = false;                 playTab = item;             }               if(item.ItemId !=4 && playTab.TitleFormatted.ToString() == "Pause")              {                 playTab.SetTitle("Play");                 playTab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);                 App.pauseCard = true;             }             return true;         }          // same as above      } } 
Read More

Friday, October 12, 2018

Are there any possible performance issues if I run an Async method that runs every minute and is called from App.Xaml.cs OnStart()?

Leave a Comment

I have this code and my intention is that when the application is open then it will run a method called PointChecker.CheckPoints(); every minute.

This method runs an update against my database synchronously with db2.Execute("UPDATE ..... etc");

From what I understand by reading this:

https://xamarinhelp.com/xamarin-forms-async-task-startup/

There are a few different ways that I could implement this.

What I would like to know is if there are any performance issues with running the code like this that could be reduced if I was to look into running it in a different way. In particular have there been any recent changes to the way Xamarin.Forms works (my app runs on iOS and Android through Forms), that I should consider and that might lead to a better way of doing this task.

    public App() {         InitializeComponent();         DB.PopulateTables();         MainPage = new Japanese.MainPage();     }      protected override async void OnStart() {         await Task.Run(() => {             StartTimer();         });     }      public void StartTimer() {        if (!stopWatch.IsRunning)           stopWatch.Start();           Device.StartTimer(new TimeSpan(0, 0, 1), () => {              if (stopWatch.IsRunning && stopWatch.Elapsed.Minutes >= 1) {                 PointChecker.CheckPoints();                 stopWatch.Restart();              }              return true;           });     }     protected override void OnSleep() {         stopWatch.Reset(); base.OnSleep();     }     protected override void OnResume() {         base.OnResume(); stopWatch.Start();     } 

3 Answers

Answers 1

Recent changes to Xamarin should not affect this. This method is the most efficient way. The only thing I would consider is why your method needs to be async. You can call:

 protected override void OnStart() 

In another thread a solution which was non-UI blocking was discussed:

using Xamarin.Forms; using System; using System.Linq; using System.Diagnostics;  namespace YourNamespace {     public partial class App : Application     {         private static Stopwatch stopWatch = new Stopwatch();         private const int defaultTimespan = 1;          protected override void OnStart()         {             // On start runs when your application launches from a closed state,               if (!StopWatch.IsRunning)             {                 StopWatch.Start();             }              Device.StartTimer(new TimeSpan(0, 0, 1), () =>             {                 // Logic for logging out if the device is inactive for a period of time.                  if (StopWatch.IsRunning && StopWatch.Elapsed.Minutes >= defaultTimespan)                 {                     //prepare to perform your data pull here as we have hit the 1 minute mark                             // Perform your long running operations here.                          InvokeOnMainThread(()=>{                             // If you need to do anything with your UI, you need to wrap it in this.                         });                      stopwatch.Restart();                 }                  // Always return true as to keep our device timer running.                 return true;             });         }          protected override void OnSleep()         {             // Ensure our stopwatch is reset so the elapsed time is 0.             StopWatch.Reset();         }          protected override void OnResume()         {             // App enters the foreground so start our stopwatch again.             StopWatch.Start();         }     } } 

Best of all, this method is

Platform agnostic

Answers 2

Reference Async/Await - Best Practices in Asynchronous Programming

By using async void you remove the ability for your code to catch and handle any exceptions that may be thrown by the invocation of the timer. So avoid using async void on anything except event handlers.

OnStart however is not an event handler. Just a regular method that according to documentation...

Application developers override this method to perform actions when the application starts.

As a work around you can create your own custom event and handler that will allow for an async void to be performed on your event handler. You will subscribe to the event when OnStart is invoked by the application and then raise the custom event to be handled asynchronously.

StartTimer is not a heavy method so does not really warrant being invoked asynchronously. The PointChecker.CheckPoints() however is stated as being called against your database synchronously.

This method runs an update against my database synchronously with db2.Execute("UPDATE ..... etc");

This could however cause blocking which could affect performance every time it is invoked. That should be the one wrapped in an async call or refactored to be native async method.

//...  protected override void OnStart() {     PointsChecking += OnPointsChecking; //subscribe to event     StartTimer(); //start the timer as normal. }  private event EventArgs PointsChecking = delegate { };  private async void OnPointsChecking(object sender, EventArgs args) {     //asynchronously check points without blocking main thread     await Task.Run(() => {         PointChecker.CheckPoints();         stopWatch.Restart();     }); }  public void StartTimer() {     if (!stopWatch.IsRunning)         stopWatch.Start();     Device.StartTimer(new TimeSpan(0, 0, 1), () => {         if (stopWatch.IsRunning && stopWatch.Elapsed.Minutes >= 1) {             PointsChecking(null, EventArgs.Empty); //invoked event         }         return true;     }); }  //... 

The timer would be responsible for raising the event to handle the repeated operation. By making the event handler async you avoid blocking main thread and have the added advantage of being able to catch and handler any exceptions thrown by the invoked database operation, if so desired.

Answers 3

I have the same scenario to sync data on given time interval, so I have used android native service Sync Adapter. What it does basically instead of managing the call on the given interval it will hand over to your Android OS. I don't have any idea on iOS how you can achieve this.

If you want to implement sync adapter use this Sample Code

I think this is the best solution for Android.

Read More

Thursday, October 11, 2018

Using UIDocumentPickerViewController in Xamarin forms as a dependency service

Leave a Comment

I'm using Xamarin forms and writing a dependency service for the following objectives :

  1. Open iOS files app. (UIDocumentPickerViewController )

  2. Select any kind of a document.

  3. Copy that document into my application Documents directory. (For app access)

  4. Show that document into my application by storing its path into my SQLite DB.

What I am trying to do here is call the Files app from my application on an Entry click and the click event seems to be working well my dependency service calls perfectly but now when I try to use the UIDocumentPickerViewController I am unable to get View controller context in my dependency service to call the PresentViewController method. Now I know about the xamarin forms context but I don't know if it will work here and I don't even know if it would be a smart idea to use it as it has already been marked as obsolete and since I am not from the iOS background, I don't know what would be the right solution for it.

My code is as follows :

public class DocumentPickerRenderer : IDocumentPicker {     public object PickFile()     {         var docPicker = new UIDocumentPickerViewController(new string[] { UTType.Data, UTType.Content }, UIDocumentPickerMode.Import);         docPicker.WasCancelled += (sender, wasCancelledArgs) =>         {         };         docPicker.DidPickDocumentAtUrls += (object sender, UIDocumentPickedAtUrlsEventArgs e) =>         {             Console.WriteLine("url = {0}", e.Urls[0].AbsoluteString);             //bool success = await MoveFileToApp(didPickDocArgs.Url);             var success = true;             string filename = e.Urls[0].LastPathComponent;             string msg = success ? string.Format("Successfully imported file '{0}'", filename) : string.Format("Failed to import file '{0}'", filename);             var alertController = UIAlertController.Create("import", msg, UIAlertControllerStyle.Alert);             var okButton = UIAlertAction.Create("OK", UIAlertActionStyle.Default, (obj) =>             {                 alertController.DismissViewController(true, null);             });             alertController.AddAction(okButton);             PresentViewController(alertController, true, null);         };         PresentViewController(docPicker, true, null);     } } 

My questions:

  1. Is my methodology correct for picking files?

  2. what will be the object that I will be getting as a callback from a file selection and how will I get the callback?

  3. Is there any other way or something available for xamarin forms, some guide or something that allows me to pick documents from my native file systems and gives a brief on how to handle it in both ios and android?

3 Answers

Answers 1

Hello Guys, You can use following code for picking any type of documents to mention in code using iOS Devices-


use follwing interface:

public interface IMedia {  Task<string> OpenDocument();  }    public Task<string> OpenDocument()     {          var task = new TaskCompletionSource<string>();         try         {             OpenDoc(GetController(), (obj) =>             {                 if (obj == null)                 {                     task.SetResult(null);                     return;                 }                 var aa = obj.AbsoluteUrl;                 task.SetResult(aa.Path);             });         }         catch (Exception ex)         {             task.SetException(ex);         }         return task.Task;     }      static Action<NSUrl> _callbackDoc;      public static void OpenDoc(UIViewController parent, Action<NSUrl> callback)     {         _callbackDoc = callback;         var version = UIDevice.CurrentDevice.SystemVersion;         int verNum = 0;         Int32.TryParse(version.Substring(0, 2), out verNum);          var allowedUTIs = new string[]         {         UTType.UTF8PlainText,         UTType.PlainText,         UTType.RTF,         UTType.PNG,         UTType.Text,         UTType.PDF,         UTType.Image,         UTType.Spreadsheet,         "com.microsoft.word.doc",         "org.openxmlformats.wordprocessingml.document",         "com.microsoft.powerpoint.ppt",         "org.openxmlformats.spreadsheetml.sheet",         "org.openxmlformats.presentationml.presentation",         "com.microsoft.excel.xls",          };          // Display the picker         var pickerMenu = new UIDocumentMenuViewController(allowedUTIs, UIDocumentPickerMode.Import);         pickerMenu.DidPickDocumentPicker += (sender, args) =>         {             if (verNum < 11)             {                 args.DocumentPicker.DidPickDocument += (sndr, pArgs) =>                 {                     UIApplication.SharedApplication.OpenUrl(pArgs.Url);                     pArgs.Url.StopAccessingSecurityScopedResource();                      var cb = _callbackDoc;                     _callbackDoc = null;                     pickerMenu.DismissModalViewController(true);                     cb(pArgs.Url.AbsoluteUrl);                 };             }             else             {                 args.DocumentPicker.DidPickDocumentAtUrls += (sndr, pArgs) =>                 {                     UIApplication.SharedApplication.OpenUrl(pArgs.Urls[0]);                     pArgs.Urls[0].StopAccessingSecurityScopedResource();                      var cb = _callbackDoc;                     _callbackDoc = null;                     pickerMenu.DismissModalViewController(true);                     cb(pArgs.Urls[0].AbsoluteUrl);                 };             }             // Display the document picker             parent.PresentViewController(args.DocumentPicker, true, null);         };          pickerMenu.ModalPresentationStyle = UIModalPresentationStyle.Popover;         parent.PresentViewController(pickerMenu, true, null);         UIPopoverPresentationController presentationPopover = pickerMenu.PopoverPresentationController;         if (presentationPopover != null)         {             presentationPopover.SourceView = parent.View;             presentationPopover.PermittedArrowDirections = UIPopoverArrowDirection.Down;         }     } 

Now you need to call using following code:

var filePath = await DependencyService.Get<IMedia>().OpenDocument(); 

For pick document in Android, you can use following code

  public class IntentHelper  {    public const int DocPicker = 101;   static Action<string> _callback;   public static async void ActivityResult(int requestCode, Result resultCode, Intent data) {  if (requestCode == RequestCodes.DocPicker)     {         if (data.Data == null)         {             _callback(null);         }         else         {             var destFilePath = FilePath.GetPath(CurrentActivity, data.Data);             _callback(destFilePath);         }     } }   public static Activity CurrentActivity {     get     {         return (Xamarin.Forms.Forms.Context as MainActivity);     } }   public static void OpenDocPicker(Action<string> callback) {     _callback = callback;     var intent = new Intent(Intent.ActionOpenDocument);     intent.AddCategory(Intent.CategoryOpenable);     intent.SetType("*/*");     CurrentActivity.StartActivityForResult(intent, RequestCodes.DocPicker); } } 

Answers 2

For pick document in Android, you can use following code:

public class IntentHelper {     public const int DocPicker = 101;     static Action<string> _callback;      public static async void ActivityResult(int requestCode, Result resultCode, Intent data)     {          if (requestCode == RequestCodes.DocPicker)         {             if (data.Data == null)             {                 _callback(null);             }             else             {                 var destFilePath = FilePath.GetPath(CurrentActivity, data.Data);                 _callback(destFilePath);             }         }     }      public static Activity CurrentActivity     {         get         {             return (Xamarin.Forms.Forms.Context as MainActivity);         }     }       public static void OpenDocPicker(Action<string> callback)     {         _callback = callback;         var intent = new Intent(Intent.ActionOpenDocument);         intent.AddCategory(Intent.CategoryOpenable);         intent.SetType("*/*");         CurrentActivity.StartActivityForResult(intent, RequestCodes.DocPicker);     } } 

Use below code to access the helper class: public class Media:

IMedia {     public Task<string> OpenDocument() {          var task = new TaskCompletionSource<string>();          try {                         IntentHelper.OpenDocPicker((path) => { task.SetResult(path); });          } catch (Exception ex) {               task.SetResult(null);          }          return task.Task;      }  } 

Answers 3

Since I was looking for UIDocumentPickerViewController and not UIDocumentMenuViewController the other answer was not what I was looking for :

So this is how I ended up doing it:

Calling the document picker:

   var docPicker = new UIDocumentPickerViewController(new string[]             { UTType.Data, UTType.Content }, UIDocumentPickerMode.Import);             docPicker.WasCancelled += DocPicker_WasCancelled;             docPicker.DidPickDocumentAtUrls += DocPicker_DidPickDocumentAtUrls;             docPicker.DidPickDocument += DocPicker_DidPickDocument;             var _currentViewController = GetCurrentUIController();             if (_currentViewController != null)                 _currentViewController.PresentViewController(docPicker, true, null); 

Where GetCurrentUIController is the function to get the current UI controller something like this :

 public UIViewController GetCurrentUIController()     {         UIViewController viewController;         var window = UIApplication.SharedApplication.KeyWindow;         if (window == null)         {             return null;         }          if (window.RootViewController.PresentedViewController == null)         {             window = UIApplication.SharedApplication.Windows                      .First(i => i.RootViewController != null &&                                  i.RootViewController.GetType().FullName                                  .Contains(typeof(Xamarin.Forms.Platform.iOS.Platform).FullName));         }          viewController = window.RootViewController;          while (viewController.PresentedViewController != null)         {             viewController = viewController.PresentedViewController;         }          return viewController;     } 

For below iOS 11 i added the DidPickDocument event:

 private void DocPicker_DidPickDocument(object sender, UIDocumentPickedEventArgs e)     {         try         {             NSUrl filePath = e.Url.AbsoluteUrl;             //This is the url for your document and you can use it as you please.         }         catch (Exception ex)         {          }       } 

For above iOS 11 you use the DidPickDocumentUrls since multipick is supported there :

  private void DocPicker_DidPickDocumentAtUrls(object sender, UIDocumentPickedAtUrlsEventArgs e)     {         try         {           List<NSUrl> filePath = e.Urls.ToList().Select(y => y.AbsoluteUrl).ToList();         //returns the list of images selected         }         catch (Exception ex)         {             AppLogger.LogException(ex);         }     } 
Read More

Thursday, October 4, 2018

MasterDetailPage: Accepting something other than a Page

Leave a Comment

I am following the tutorial here on Master-Detail Page

One question I am having:

Part of the solution calls for an item class as follows:

public class MasterPageItem {     public string Title { get; set; }     public string IconSource { get; set; }     public Type TargetType { get; set; } } 

And in the Xaml file, you call it as this:

   <ListView.ItemsSource>         <x:Array Type="{x:Type local:MasterPageItem}">             <local:MasterPageItem Title="Radio" IconSource="hamburger.png" TargetType="{x:Type local:RadioPage}" />              <local:MasterPageItem Title="Item 1" TargetType="{x:Type temp:ContactsPage}" />             <local:MasterPageItem Title="Item 2" TargetType="{x:Type temp:TodoListPage}" />             <local:MasterPageItem Title="Item 3" TargetType="{x:Type temp:ReminderPage}" />             <local:MasterPageItem Title="File 4"  TargetType="{}Foodaddy" />         </x:Array>     </ListView.ItemsSource> 

What I want to do is for "File 4", rather than creating a view, I would rather send in a string, (or better an Enum), so I can call different functions. Example: Open a webpage, log out, etc. How do I modify TargetType?

1 Answers

Answers 1

If you notice in this piece of code you will see that the OnItemSelected method is handling the event and expecting for a MasterPageItem:

void OnItemSelected (object sender, SelectedItemChangedEventArgs e) {     var item = e.SelectedItem as MasterPageItem;     if (item != null) {         Detail = new NavigationPage ((Page)Activator.CreateInstance (item.TargetType));         masterPage.ListView.SelectedItem = null;         IsPresented = false;     } } 

It is doing the navigation directly through the event handler (it's not the MVVM way to do that).

I guess @Jason's advice fits for you. Here's a way to achieve that:

You can create your own model to replace the MasterPageItem and handle its properties in a MasterDetailMenuViewModel, for example, and modify the handling to something more accurate for your need.

See the example below:

Create an amazing model to your menu items

public class AmazingMenuItem {     public string ItemName { get; set; }     public string ItemIconSource { get; set; }     public EnumAction ActionType { get; set; }     public object ActionParameter { get; set; } }  public enum EnumAction {     OpenPage = 0,     ShowMessage = 1,     PopToRoot = 2     /* ... and so on  */ } 

Handle it in the view model*

public class MenuViewModel : SomeINotifyPropertyChangedImplementation {     public Command ItemSelectedCommand { get; }     /*         Other properties     */      public MenuViewModel()     {         ItemSelectedCommand = new Command<AmazingMenuItem>(ExecuteItemSelected);     }      private void ExecuteItemSelected(AmazingMenuItem item)     {         if(item != null)         {             switch(item.ActionType)             {                 case EnumAction.OpenPage:                     var detail = (App.Current.MainPage as MasterDetailPage).Detail;                      if(detail is NavigationPage)                         detail.PushAsync((Page)Activator.CreateInstance(item.ActionParameter.GetType()));                     else                         (App.Current.MainPage as MasterDetailPage).Detail = new NavigationPage((Page)Activator.CreateInstance(item.ActionParameter.GetType()));                      break;                 case EnumAction.ShowMessage:                     App.Current.MainPage.DisplayAlert(item.ActionParameter.ToString(), "Ok", "Cancel");                      break;                 case EnumAction.PopToRoot:                     (App.Current.MainPage as MasterDetailPage)?.Detail.PopToRootAsync();                      break;              }         }     } } 

* - Supposing you have made the bindings correctly

It's not a complete implementation, but I hope you've figured out a general idea of it.

Let me know if I can help you to get thru this (and sorry for any English mistake, it's not a native here).

Read More

Thursday, September 27, 2018

Does a controls OnElementChanged() get called when I leave a page?

Leave a Comment

I am using this iOS SegmentedControlRenderer on a page.

But when I go to the page this way: Navigation.PushAsync(new CFSPage()) and then click on the back arrow to go to the previous page, the OnElementChanged event in my customer iOS renderer is fired. The result with my renderer is that the following line gives a null reference error:

segmentedControl.TintColor = e.NewElement?.TintColor.ToUIColor();

Can someone please explain what's the purpose of the ? here and also should this line be after or should it be inside the if (e.NewElement != null) check. Am I correct in saying that the following lines should NOT be executed if there is no NewElement?

segmentedControl.TintColor = e.NewElement?.TintColor.ToUIColor(); SetNativeControl(segmentedControl); SetSelectedSegment(); 

This renderer as is doesn't have an

protected override void Dispose(bool disposing) 

Is that something that's missing?

Renderer

public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, UISegmentedControl> {     protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e)     {         base.OnElementChanged(e);         UISegmentedControl segmentedControl = null;         if (Control == null)         {             segmentedControl = new UISegmentedControl();             for (var i = 0; i < e.NewElement.Children.Count; i++)             {              segmentedControl.InsertSegment(Element.Children[i].Text, i, false);             }             SetNativeControl(segmentedControl);             SetSelectedSegment();         }         if (e.OldElement != null)         {             if (segmentedControl != null)                 segmentedControl.ValueChanged -= NativeValueChanged;         }         if (e.NewElement != null)         {             segmentedControl.ValueChanged += NativeValueChanged;         }         segmentedControl.TintColor = e.NewElement?.TintColor.ToUIColor();         SetNativeControl(segmentedControl);         SetSelectedSegment();     }      protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)     {         base.OnElementPropertyChanged(sender, e);          if (e.PropertyName == nameof(SegmentedControl.SelectedSegment))             SetSelectedSegment();         if (e.PropertyName == SegmentedControl.TintColorProperty.PropertyName)             SetSegmentTintColor();     }      void NativeValueChanged(object sender, EventArgs e)     {         if (Element is SegmentedControl formsElement)         {             formsElement.SelectedSegment = (int)Control.SelectedSegment;         };     }      void SetSegmentTintColor()     {         if (Element is SegmentedControl formsElement)             Control.TintColor = formsElement.TintColor.ToUIColor();     }      void SetSelectedSegment()     {         if (Element is SegmentedControl formsElement)         {             if (formsElement.SelectedSegment >= 0 && formsElement.SelectedSegment < Control.NumberOfSegments)                 Control.SelectedSegment = formsElement.SelectedSegment;         }     } } 

2 Answers

Answers 1

The following is an example of how OnElementChanged in most renderers is structured:

protected override void OnElementChanged(ElementChangedEventArgs<Type> e) {     base.OnElementChanged(e);      if (e.OldElement != null)     {         // Unsubscribe from event handlers and cleanup any resources     }      if (e.NewElement != null)     {         if (Control == null)         {             // Instantiate the native control and assign it to the Control property with             // the SetNativeControl method         }          // Configure the control and subscribe to event handlers     } } 

Please note that Control might be null when if (e.OldElement != null) is true and should not normally be recreated in this situation.

See also Xamarin.Forms iOS ButtonRenderer.

Answers 2

The ?. can be found at C# 6.0 docs:

You need to check every access of variables to ensure you are not dereferencing null. The null conditional operator makes those checks much easier and fluid.

Simply replace the member access . with ?.

In short:

segmentedControl.TintColor = e.NewElement?.TintColor.ToUIColor(); 

is the same as:

  if (e.NewElement != null)   {       segmentedControl.TintColor = e.NewElement.TintColor.ToUIColor();   } 

So yes, you are correct, it should not execute if there is NewElement.

Read More

Sunday, September 23, 2018

Getting carousel data back into object

Leave a Comment

I am creating a dynamic carousel view in my Xamarin app, and so far it works just fine, but...

My carousel contains students and each carousel page is a link to a student info page. I want to be able to set an object with the current selected student for me to grab on all the students subpages (hope this makes sense :-/).

My script is as follow.

StudentModel:

public class StudentData {     public string id { get; set; }     public string name { get; set; }     public string course { get; set; }     public string schoolclass { get; set; }     public string profileImage { get; set; } } 

CarouselPart:

ObservableCollection<StudentData> collection = new ObservableCollection<StudentData>();  collection.Add(new StudentData { name = "Soren Hanson", schoolclass = "4. grade", course = "Math" }); collection.Add(new StudentData { name = "Michael Trane", schoolclass = "7. grade", course = "English" }); collection.Add(new StudentData { name = "Tammy Jump", schoolclass = "1. grade", course = "English" });  DataTemplate template = new DataTemplate(() => {     var imageBtn = new Button();     imageBtn.Image = "Images/default.png";      imageBtn.Clicked += delegate {          // ADDING THE CURRENT STUDENT TO MY CURRSTUDENT OBJECT //         //App.currStudent = collection.......          var menteeOptions = new MenteeOptions();         imageBtn.Navigation.PushAsync(menteeOptions);      } }  carousel.ItemTemplate = template; carousel.PositionSelected += pageChanged; carousel.ItemsSource = collection; 

Hoping for help with this and thanks in advance :-)

1 Answers

Answers 1

Well you question was quite confusing at first but I will answer it from what I understood you want to keep the selected student object with you on your subpages:

So you can either use SQLite for it which can be found here

Or you can just maintain it as a static system object on your app.xaml.cs and use it

Something like this

App.Xaml.cs

 public static object YourDataHolder {get; set;} 

In your clicked event:

imageBtn.Clicked += delegate {      // ADDING THE CURRENT STUDENT TO MY CURRSTUDENT OBJECT //     App.YourDataHolder = _yourCollection;      var menteeOptions = new MenteeOptions();     imageBtn.Navigation.PushAsync(menteeOptions);  } 

The use it something like this :

fooObject=App.YourDataHolder as FooType; 
Read More

Monday, September 10, 2018

How can I make it so a HeightRequest inside a template is ignored if not specified in the XAML that uses the template?

Leave a Comment

This template takes as a binding parameter HeaderHeight. Is there a way that I can make HeaderHeight ignored by the StackLayout if it's not specified in the XAML that calls the template?

<?xml version="1.0" encoding="UTF-8"?> <ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Japanese;assembly=Japanese" x:Class="Japanese.Templates.HeaderTemplate" x:Name="this">     <ContentView.Triggers>         <Trigger TargetType="local:Templates.HeaderTemplate" Property="HeaderType" Value="Custom">             <Setter Property="Content">                 <Setter.Value>                     <StackLayout HorizontalOptions="FillAndExpand" Orientation="Vertical" Spacing="0" Margin="0">                         <StackLayout HeightRequest="{Binding HeaderHeight, Source={x:Reference this}}" Orientation="Vertical" Spacing="0" Margin="0" >                             <Label Text="ABC" HorizontalOptions="Start" VerticalOptions="EndAndExpand" />                         </StackLayout>                     </StackLayout>                 </Setter.Value>             </Setter>         </Trigger>     </ContentView.Triggers> </ContentView> 

1 Answers

Answers 1

You can set the default value of HeaderHeight to -1

The UI should go back to normal

HeaderHeight = -1; 

Once it's a BindableProperty, it should look like this:

public static readonly BindableProperty HeaderHeightProperty = BindableProperty.Create(nameof(HeaderHeight), typeof(double), typeof(HeaderTemplate), -1); public double HeaderHeight {     get => (double)GetValue(HeaderHeightProperty);     set => SetValue(HeaderHeightProperty, value); } 
Read More

Sunday, September 9, 2018

How to update progressbar from customwebview renderer in xamarin forms for android?

Leave a Comment

I'm currently developing an app in Xamarin Forms with an Android background so I wanted to create an Android app first and an iOS app later.

I'm new to Xamarin Forms and I'm struggling on how to update a ProgressBar from a WebView using a custom renderer for the WebView.

In Android, you can do something like this, with the ProgressBar and WebView that are in the main_layout.xml

public class MainActivity extends Activity {      private ProgressBar progressBar;     private WebView webView;          @Override     protected void onCreate(Bundle savedInstanceState) {              super.onCreate(savedInstanceState);         setContentView(R.layout.main_layout);          progressBar = (ProgressBar) findViewById(R.id.progress);         webView = (AdvancedWebView) findViewById(R.id.webView);         // webview initialisation         webView.setWebChromeClient(new WebChromeClient(){             @Override             public void onProgressChanged(WebView view, int newProgress) {                 // update progressbar progress                 progressBar.setProgress(newProgress);            }        });         webView.setWebViewClient(new WebViewClient(this) {             @Override             public void onPageFinished(WebView view, String url) {                 // hide progressbar when it's done                 progressBar.setVisibility(View.GONE);             }         });     } } 

In Xamarin Forms I have this layout in MainPage.xaml in the shared project

<?xml version="1.0" encoding="utf-8" ?> <ContentPage x:Class="MainPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" NavigationPage.HasNavigationBar="False">  <Grid>     <Grid.RowDefinitions>         <RowDefinition Height="*" />         <RowDefinition Height="*" />         <RowDefinition Height="*" />         <RowDefinition Height="*" />     </Grid.RowDefinitions>      <ProgressBar         x:Name="progress"         Grid.Row="3"         HorizontalOptions="FillAndExpand"         Progress="0"         VerticalOptions="Center" />      <WebView         x:Name="webview"         Grid.Row="0"         Grid.RowSpan="4"         Grid.Column="0"         HorizontalOptions="FillAndExpand"         IsVisible="False"         Source="https://google.com"         VerticalOptions="FillAndExpand" />  </Grid> 

public partial class App : Application {     public App ()     {         InitializeComponent();          MainPage = new MainPage();      } } 

And this custom WebView render for android in the android project

[assembly: ExportRenderer(typeof(WebView), typeof(CustomWebviewRenderer))] namespace MyApp.Droid {     public class CustomWebviewRenderer: WebViewRenderer     {         private readonly Context context;          public CustomWebviewRenderer(Context context) : base(context)         {             this.context = context;         }          protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)         {             base.OnElementChanged(e);             var formsWebView = e.NewElement as Xamarin.Forms.WebView;              if (formsWebView != null)             {                 var webView = Control as Android.Webkit.WebView;                 webView.SetWebViewClient(new CustomWebViewClient());                  webView.SetWebChromeClient(new CustomWebChromeClient());                      webView.Settings.LoadWithOverviewMode = true;                 webView.Settings.UseWideViewPort = true;                 SetNativeControl(webView);             }         }          private class ScoritoWebChromeClient : WebChromeClient         {             public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)             {                 // how to update progressbar progress?                 base.OnProgressChanged(view, newProgress);                            }         }          private class CustomWebViewClient : WebViewClient         {             public override void OnPageFinished(Android.Webkit.WebView view, string url)             {                 // how to hide progressbar?                 base.OnPageFinished(view, url);             }              public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)             {                 base.OnPageStarted(view, url, favicon);             }              public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)             {                 return base.ShouldOverrideUrlLoading(view, request);             }         }     } } 

How can I update the ProgressBar that I have in my MainPage.xaml from the OnPageFinished in the CustomWebviewRenderer class to show the webpage has finished loading? Should I use the MainActivity.cs in the Android project?

Can someone point me in the right direction on how to solve this?

1 Answers

Answers 1

You don't need custom renderers for what you are doing first and foremost.

Why don't you use the Navigating & Navigated Binding's in your Xamarin forms Webview?

<WebView x:Name="Webview"                          Navigating="OnNavigating"            Navigated="OnNavigated"            VerticalOptions="FillAndExpand"/>   

And then do this in your xaml.cs:

 protected async override void OnAppearing()       {           base.OnAppearing();            await progress.ProgressTo(0.9, 900, Easing.SpringIn);       }     private void OnNavigated(object sender, Xamarin.Forms.WebNavigatedEventArgs e)     {       progress.IsVisible = false;       }      private void OnNavigating(object sender, Xamarin.Forms.WebNavigatingEventArgs e)     {        progress.IsVisible = true;     } 

And this should be enough

But if you still insist to use your renderer what you can do is update the progress bar using the element property and get a callback or change a property something like below:

[assembly: ExportRenderer(typeof(WebView), typeof(CustomWebviewRenderer))]  namespace MyApp.Droid { public class CustomWebviewRenderer: WebViewRenderer {     private readonly Context context;    private MainPage MainPage     {         get         {             var mainPage = Element as MainPage;             return mainPage == null                 ? null                 : mainPage;         }     }     public CustomWebviewRenderer(Context context) : base(context)     {         this.context = context;     }      protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)     {         base.OnElementChanged(e);         var formsWebView = e.NewElement as Xamarin.Forms.WebView;          if (formsWebView != null)         {             var webView = Control as Android.Webkit.WebView;             webView.SetWebViewClient(new CustomWebViewClient());              webView.SetWebChromeClient(new CustomWebChromeClient());                  webView.Settings.LoadWithOverviewMode = true;             webView.Settings.UseWideViewPort = true;             SetNativeControl(webView);         }     }      private class ScoritoWebChromeClient : WebChromeClient     {         public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)         {             // how to update progressbar progress?             base.OnProgressChanged(view, newProgress);                        }     }      private class CustomWebViewClient : WebViewClient     {         public override void OnPageFinished(Android.Webkit.WebView view, string url)         {             // how to hide progressbar?             MainPage.yourCallbackFunc();             base.OnPageFinished(view, url);         }          public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)         {             base.OnPageStarted(view, url, favicon);         }          public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)         {             return base.ShouldOverrideUrlLoading(view, request);         }     }   } } 

And get the callback in that xamarin forms page:

  public partial class MainPage: ContentPage  {      public MainPage()      {          InitializeComponent();       }      public void yourCallbackFunc()      {       .       . // Do some functionality       .       }  } 

In case it doesn't work or you have queries revert!

Read More

Saturday, September 8, 2018

How can I make my Xamarin.Forms for iOS and Android application show a splash screen as soon as possible after a user has opened the application?

Leave a Comment

I'm looking for hints, an example or anything that could guide me with how to make a start up splash screen for Xamarin application Xamarin.Forms application.

I assume I need to make different changes to the iOS and the Android projects and have researched what I can find already.

However much of the information seems dated or no longer works.

Would appreciate any suggestions and advice on how to do this for iOS and Android.

2 Answers

Answers 1

Xamarin.Forms is a solution that, in itself, takes a while to load. Since all cross-platform things have to wait for Xamarin, there is no cross-platform good implementation of a splash screen.

It looks like you'll have to implement it separately for iOS and Android. This will enable the splash screen to show up much faster and before the standard blue Xamarin screen.

EDIT: Alan asked for a complete solution, so here it is:

iOS: Make changes to the LaunchScreen.storyboard file in the Resources section of the .iOS project (that's the splash screen, by the way). I'd suggest opening it in Xcode instead because the storyboard editor of Xcode is frankly just better than Xamarin.

Android: You're going to have to have an image here. From official Xamarin support-

The quickest way to render and display the splash screen is to create a custom theme and apply it to an Activity that exhibits the splash screen. When the Activity is rendered, it loads the theme and applies the drawable resource (referenced by the theme) to the background of the activity. This approach avoids the need for creating a layout file. The splash screen is implemented as an Activity that displays the branded drawable, performs any initializations, and starts up any tasks. Once the app has bootstrapped, the splash screen Activity starts the main Activity and removes itself from the application back stack.

Here's the specific code involved for that. Below is for a drawable. Place it in the Resources/Drawable folder of the Android project.

<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">   <item>     <color android:color="@color/splash_background"/>   </item>   <item>     <bitmap         android:src="@drawable/splash"         android:tileMode="disabled"         android:gravity="center"/>   </item> </layer-list> 

This is for a theme (add it to values/styles.xml, or add values/styles.xml if the document doesn't exist.).

<resources>   <style name="MyTheme.Base" parent="Theme.AppCompat.Light">   </style>    <style name="MyTheme" parent="MyTheme.Base">   </style>    <style name="MyTheme.Splash" parent ="Theme.AppCompat.Light.NoActionBar">     <item name="android:windowBackground">@drawable/splash_screen</item>     <item name="android:windowNoTitle">true</item>     <item name="android:windowFullscreen">true</item>   </style> </resources> 

Add this code to your Android project as MyTheme.cs:

[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] public class SplashActivity : AppCompatActivity {     static readonly string TAG = "X:" + typeof(SplashActivity).Name;      public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)     {         base.OnCreate(savedInstanceState, persistentState);         Log.Debug(TAG, "SplashActivity.OnCreate");     }      // Launches the startup task     protected override void OnResume()     {         base.OnResume();         Task startupWork = new Task(() => { SimulateStartup(); });         startupWork.Start();     }      // Simulates background work that happens behind the splash screen     async void SimulateStartup ()     {         Log.Debug(TAG, "Performing some startup work that takes a bit of time.");         await Task.Delay (8000); // Simulate a bit of startup work.         Log.Debug(TAG, "Startup work is finished - starting MainActivity.");         StartActivity(new Intent(Application.Context, typeof (MainActivity)));     } } 

Then, after you've done all that for Android, remove the MainLauncher attribute from MainActivity, since your launcher is now MyTheme.

This is by no means easy, but it's the only way there is right now.

Answers 2

Make sure you do not have SimulateStartup sample code. It used to be boiler plate code a while ago:

class SplashActivity:

It forces an await when the app starts up incase you want to make any service calls on the splash screen

MainActivity.cs

// Launches the startup task protected override void OnResume() {     base.OnResume();     Task startupWork = new Task(() => { SimulateStartup(); });     startupWork.Start(); }  // Simulates background work that happens behind the splash screen async void SimulateStartup () {     Log.Debug(TAG, "Performing some startup work that takes a bit of time.");     await Task.Delay (8000); // Simulate a bit of startup work.     Log.Debug(TAG, "Startup work is finished - starting MainActivity.");     StartActivity(new Intent(Application.Context, typeof (MainActivity))); } 

Other alternative to improve splash speed:

  • use small or native components to create your splash screen
  • make sure nothing is on OnResume and not calling any services
Read More

Friday, September 7, 2018

Xamarin iOS images overlapping inside Grid

Leave a Comment

Heyo,

So in Xamarin I have a <Grid> with an <Image> and a couple <Label>s inside it, all wrapped inside a <ViewCell>. This looks totally fine in Xamarin.Android, however in Xamarin.iOS the images overlap the labels. I'm not sure what the difference could be - why does it look good in Xamarin.Android but in iOS its all wonky?

Below is my XAML and a couple mockups to show what I mean.

<ListView.ItemTemplate>     <DataTemplate>         <ViewCell>             <ViewCell.View>             <Grid>                 <Grid.ColumnDefinitions>                 </Grid.ColumnDefinitions>                 <Grid.RowDefinitions>                 <RowDefinition Height="Auto" />                 <RowDefinition Height="Auto" />                 </Grid.RowDefinitions>                  <Image Grid.Column="0" Grid.Row="0" Aspect="AspectFill" Source="{Binding ImageOverlayEN}" />                 <Label Grid.Column="0" Grid.Row="1" VerticalTextAlignment="Start" LineBreakMode="WordWrap" Text="{Binding DynamicOfferText}" FontSize="18">                     <Label.FontFamily>                         <OnPlatform x:TypeArguments="x:String">                         <OnPlatform.iOS>RobotoCondensed-Regular</OnPlatform.iOS>                         <OnPlatform.Android>RobotoCondensed-Regular.ttf#RobotoCondensed-Regular</OnPlatform.Android>                         <OnPlatform.WinPhone>RobotoCondensed-Regular.ttf#RobotoCondensed</OnPlatform.WinPhone>                     </OnPlatform>                     </Label.FontFamily>                 </Label>                 <Label Grid.Column="0" Grid.Row="2" VerticalTextAlignment="Start" LineBreakMode="WordWrap" Text="{Binding DynamicOfferDetail}" FontSize="16">                     <Label.FontFamily>                         <OnPlatform x:TypeArguments="x:String">                         <OnPlatform.iOS>RobotoCondensed-Regular</OnPlatform.iOS>                         <OnPlatform.Android>RobotoCondensed-Regular.ttf#RobotoCondensed-Regular</OnPlatform.Android>                         <OnPlatform.WinPhone>RobotoCondensed-Regular.ttf#RobotoCondensed</OnPlatform.WinPhone>                     </OnPlatform>                     </Label.FontFamily>                 </Label>                 </Grid>             </ViewCell.View>         </ViewCell>     </DataTemplate> </ListView.ItemTemplate> 

I tried setting the HeightRequest but that didn't seem to make a difference:

#if __IOS__     if (viewModel.Items.Count > 0)     {         MyListView.HeightRequest = 300 * viewModel.Items.Count;     } #endif 

Here is a visual representation of what is happening:

enter image description here

1 Answers

Answers 1

Figured it out - had to do with my RowDefinitions being all set to Auto

This got it looking just right:

<RowDefinition Height="1*" /> <RowDefinition Height="0.18*" /> <RowDefinition Height="0.77*" /> 

I think what was happening is the ListView renders first, the Auto row definition heights set the height of each row before the image is done loading, then the image finishes loading and overlaps the rest of the list view's item (including the labels). This might just be a quirk of iOS, where as Android calculates the row heights after the images are done loading (explaining why it looked fine in Android)

Read More

Wednesday, September 5, 2018

NullReferenceException in System.Threading.Tasks.RangeWorker.FindNewWork in Android Xamarin app

Leave a Comment

In my Android native app created with Xamarin, I get the following crash report in HockeyApp:

Xamarin caused by: android.runtime.JavaProxyThrowable: System.NullReferenceException: Object reference not set to an instance of an object   at System.Threading.Tasks.RangeWorker.FindNewWork (System.Int64& nFromInclusiveLocal, System.Int64& nToExclusiveLocal) [0x00000] in <8f1acca5a43d45c5b8d35add5a11806a>:0    at System.Threading.Tasks.RangeWorker.FindNewWork32 (System.Int32& nFromInclusiveLocal32, System.Int32& nToExclusiveLocal32) [0x00000] in <8f1acca5a43d45c5b8d35add5a11806a>:0    at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () <0xec919968 + 0x00033> in <8f1acca5a43d45c5b8d35add5a11806a>:0 

I cannot figure out what is causing this exception since the stack trace only contains .net code. I have looked at the framework's source code to see if I could make sense of it but to no avail.

I have also tried many different searches in my favorites search engines without finding any posts/articles about similar issues.

From the data associated with those crashes, it seems to be an issue happening exclusively on Samsung devices (S8, S8+ and Note8). I cannot be 100% sure that it doesn't affect other devices but I only have crash reports for those.

Any idea what could cause those crashes? Am I doing something wrong with threading, maybe with cancellation tokens? Are there conditions I am not handling correctly?

Any help to further troubleshoot this issue would be very welcomed.

Thank you

EDIT:

I understand that there is not much to go by but being that this is the only stacktrace I get in HockeyApp and I cannot repro, I have no idea what code causes this.

What I am looking for is more a clue about what could cause Mono threading code to have a null reference exception when I am not managing the threads myself. Or maybe this stacktrace is just a red herring and I need to be looking in other places?

3 Answers

Answers 1

What caught my attention was the FindNewWork32 call -- link for the .NET implementation. Your app may be compiled for 32-bit architecture, and those Samsung devices you mentioned have a 64-bit processor.

This Microsoft paper shows how to target an app to one or more Android-supported CPU architectures. You may need to target multiple platforms:

To target multiple CPU architectures, you can select more than one ABI (at the expense of larger APK file size). You can use the Generate one package (.apk) per selected ABI option (described in Set Packaging Properties) to create a separate APK for each supported architecture.

You do not have to select arm64-v8a or x86_64 to target 64-bit devices; 64-bit support is not required to run your app on 64-bit hardware. For example, 64-bit ARM devices (such as the Nexus 9) can run apps configured for armeabi-v7a. The primary advantage of enabling 64-bit support is to make it possible for your app to address more memory.

Answers 2

I know this answer is not specific for your issue, but when you are dealing with an exception message, which is so brief, it's always a good idea to check system-wide logging. In case of Android, you might get more information using dmesg command (which is giving you the content of /var/log/messages file).

Good luck

Answers 3

You should call .IsCompleted or .Wait() soon after callback. If you do it before the Context objects might go away and if your code is accessing those Context objects will be arbitrarily null (removed). If you call Task.Wait() it would block the thread and then throw an AggregateException once the worker throws it. Try using try catch block to handle exception.

Issue JavaProxyThrowable not always happening due to calling service there might be your activity null some where. You should check null before using activity context.

Read More

Tuesday, September 4, 2018

How can I ensure a StackLayout is at least a minimum height?

Leave a Comment

I tried this in my code.

<StackLayout     HorizontalOptions="FillAndExpand"     Spacing="0"     MinimumHeightRequest="50">    <StackLayout Margin="20, 6, 20, 8"        HorizontalOptions="FillAndExpand"        VerticalOptions="FillAndExpand"        Spacing="0">       <Label Text="ABC"           HorizontalOptions="Start"           VerticalOptions="StartAndExpand" />    </StackLayout> </StackLayout> 

But with small text such as ABC then the height is not equal to 50. Is there some way I can ensure the height is a minimum of 50?

3 Answers

Answers 1

You would try something like that:

<Grid RowSpacing="0">     <Grid.RowDefinitions>         <RowDefinition Height="Auto"/>         <RowDefinition Height="*"/>     </Grid.RowDefinitions>      <StackLayout Grid.Row="0" HeightRequest="50"/> </Grid> 

I'm doing that in my application, and it works fine. You set the RowDefinition to Auto, then, it is going to fit exactly your HeightRequest.

Answers 2

Relaying what ShawnCastrianni.5092 on the following forum

After reading the remarks in the API docs, I see this:

"MinimumHeightRequest is used to override the results of a call to VisualElement.GetSizeRequest (double, double) by setting the minimum height property. This causes overflow handling to shrink this element to its minimum height before elements who do not have a minimum size set."

I guess that matches what you said Jason. Therefore, I think everybody will be thrown off by the name MinimumHeightRequest. I would prefer it say OverflowHeightRequest to avoid any confusion.

The name itself is misleading. You'll have to create a custom solution to fix your issue

Answers 3

You can try to set HeightRequest=50 and measure height of your label text. Use the ITextMeter from this source, and if text height is more than 50, then set HeightRequest to text height.

Read More

Thursday, August 23, 2018

Keep wifi active in foreground service after phone goes to sleep

Leave a Comment

I want to receive packets from wifi when my phone is locked. The problem is that when I lock my screen, my foreground service stops receiving packets. I'm using Foreground Service like this:

public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {     var notification = new Notification.Builder(this)         .SetContentTitle(Resources.GetString(Resource.String.app_name))         .SetContentText(Resources.GetString(Resource.String.notification_text))         .SetSmallIcon(Resource.Drawable.ic_stat_name)         .SetContentIntent(BuildIntentToShowMainActivity())         .SetOngoing(true)         .AddAction(BuildRestartTimerAction())         .AddAction(BuildStopServiceAction())         .Build();       // Enlist this instance of the service as a foreground service     StartForeground(Constants.SERVICE_RUNNING_NOTIFICATION_ID, notification);      /*DO THIS EVEN WHEN SCREEN IS LOCKED*/      var powerManager = (PowerManager)GetSystemService(PowerService);     _wakeLock = powerManager.NewWakeLock(WakeLockFlags.Partial, "WakeLockTag");     _wakeLock.Acquire();      var wifiManager = (WifiManager)GetSystemService(WifiService);     _wifiLock = wifiManager.CreateWifiLock(WifiMode.FullHighPerf, "xamarin_wifi_lock");     _wifiLock.Acquire();      if (!powerManager.IsIgnoringBatteryOptimizations("com.xamarin.xample.foregroundservicedemo") ||         !_wakeLock.IsHeld || !_wifiLock.IsHeld)         throw new InvalidOperationException("OPTIMIZATIONS NOT ACTIVE");      string msg = timestamper.GetFormattedTimestamp();     Log.Debug(TAG, msg);     Intent intent = new Intent(Constants.NOTIFICATION_BROADCAST_ACTION);     intent.SetAction(Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings);     intent.PutExtra(Constants.BROADCAST_MESSAGE_KEY, msg);     LocalBroadcastManager.GetInstance(this).SendBroadcast(intent);     Task.Run(() =>     {         using (var client = new UdpClient(12345))         {             while (true)             {                 var result = client.ReceiveAsync().Result;                 Console.WriteLine($"RECEIVED: {result.Buffer.Length}");             }         }     });      return StartCommandResult.Sticky; } 

I'm doing the following things to make sure it is not killed:

  1. Starting Foreground Service
  2. Using StartCommandResult.Sticky
  3. Using Wake Lock
  4. Using Wifi Lock
  5. Setting WifiSleepPolicy to Never (I have it setup in my phone settings)
  6. Setting ActionIgnoreBatteryOptimizationSettings in intent
  7. Whitelisting my app through adb command prompt while debugging

What else am I missing? I am using Samsung A5 with Android 6.0 - API 23.

I looked into logs from adb command prompt and I checked that my service is in fact running as Foreground Service and all locks are held.

1 Answers

Answers 1

What you're doing is just great, all is good. However!!

Let us take a look at devleport.android.com's post:

Google Play policies prohibit apps from requesting direct exemption from Power Management features in Android 6.0+ (Doze and App Standby) unless the core function of the app is adversely affected.

You stated:

I am using Samsung A5 with Android 6.0 - API 23.

Meaning, you won't be able to keep your foreground service when the phone goes to sleep because the core function of the app is not adversely affected.

This the reason why you notice you stopped receiving packets when the phone is asleep.

please explore the entire link I attached and also us the Power Manager guide.

edit: I now noticed that furthermore:

If a user leaves a device unplugged and stationary for a period of time, with the screen off, the device enters Doze mode. In Doze mode, the system attempts to conserve battery by restricting apps' access to network and CPU-intensive services. It also prevents apps from accessing the network and defers their jobs, syncs, and standard alarms.

Doze restrictions:

  • Network access is suspended.
  • The system ignores wake locks.
  • Standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window.
  • If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() or setExactAndAllowWhileIdle().
  • Alarms set with setAlarmClock() continue to fire normally — the system exits Doze shortly before those alarms fire.
  • The system does not perform Wi-Fi scans.
  • The system does not allow sync adapters to run.
  • The system does not allow JobScheduler to run.

This will conclude my answer to all of your queries.

EDIT 2:

I investigated a little further, there is an entire post regarding this issue in Xamarin.Android.

there is also a solution to wake the phone every 10 minutes to bypass it here.

Read More

Monday, August 13, 2018

Selected item not working in Picker

Leave a Comment

In our edit page, we have an issue to populate the value into Selected Item in picker and it won't select for some reason from the LoadCourses() or LoadRoundCategories() either.

Any ideas?

Here's the code:

ViewModel

public class EditGolfRoundViewModel : INotifyPropertyChanged {     ApiServices _apiServices = new ApiServices();      private string _message;     private ObservableCollection<GolfCourse> _courses;     private ObservableCollection<GolfRoundCategory> _roundCategories;     private object_selectedGolfCourse;     private GolfRoundCategory _selectedGolfRoundCategory;     private GolfRound _golfRound;      public EditGolfRoundViewModel()     {         _selectedGolfCourse = new GolfCourse();         _selectedGolfRoundCategory = new GolfRoundCategory();         LoadCourses();         LoadRoundCategories();     }      public GolfRound GolfRound     {         get { return _golfRound; }         set         {             _golfRound = value;             OnPropertyChanged();         }     }       public string Message     {         get { return _message; }          set         {             _message = value;             OnPropertyChanged();         }     }      public ObservableCollection<GolfCourse> GolfCourses     {         get { return _courses; }         set         {             if (_courses != value)             {                 _courses = value;                 OnPropertyChanged();             }          }     }      public ObservableCollection<GolfRoundCategory> GolfRoundCategories     {         get { return _roundCategories; }         set         {             _roundCategories = value;             OnPropertyChanged();         }     }      public object SelectedGolfCourse     {         get { return _selectedGolfCourse; }         set         {             _selectedGolfCourse = value;             var golfCourse = _selectedGolfCourse as GolfCourse;             Guid tempGolfCourseID = golfCourse.GolfCourseID;             OnPropertyChanged("SelectedGolfCourse");         }     }       public GolfRoundCategory SelectedGolfRoundCategory     {         get { return _selectedGolfRoundCategory; }         set         {             _selectedGolfRoundCategory = value;             OnPropertyChanged();         }     }        public ICommand EditCommand      {          get         {             return new Command(async() =>             {                 GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;                 GolfRound.GolfCourse = SelectedGolfCourse;                 GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;                 GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;                 GolfRound.LastModifiedUTC = System.DateTime.Now;                  await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);             });         }      }      public ICommand DeleteCommand     {         get         {             return new Command(async () =>             {                 await _apiServices.DeleteGolfRoundAsync(GolfRound.GolfRoundID, Settings.AccessToken);             });         }      }      public event PropertyChangedEventHandler PropertyChanged;      [NotifyPropertyChangedInvocator]     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)     {         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));     }      private async void LoadCourses()     {         GolfCourses = new ObservableCollection<GolfCourse>(await _apiServices.GetGolfCoursesAsync(Settings.AccessToken));     }      private async void LoadRoundCategories()     {         GolfRoundCategories = new ObservableCollection<GolfRoundCategory>(await _apiServices.GetGolfRoundCategoriesAsync(Settings.AccessToken));     }  } 

View - XAML

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"          xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"          xmlns:viewModels="clr-namespace:AthlosifyMobile.ViewModels.Golf"          x:Class="AthlosifyMobile.Views.EditGolfRoundPage">  <StackLayout Orientation="Vertical" VerticalOptions="Center" Spacing="30" Padding="30">     <Entry Text="{Binding GolfRound.Name}" Placeholder="name" FontSize="Default" />     <Entry Text="{Binding GolfRound.Notes}" Placeholder="notes" FontSize="Default" />     <Entry Text="{Binding GolfRound.DailyHandicap}" Placeholder="daily handicap" FontSize="Default" />     <Label Text="Date" />     <DatePicker Date="{Binding GolfRound.TeeOffUTC}"                  Format="D"                 Margin="30, 0, 0, 30"  />     <Picker x:Name="pCourse" Title="Course" ItemsSource="{Binding GolfCourses}"              SelectedItem="{Binding SelectedGolfCourse, Mode=TwoWay}"              ItemDisplayBinding="{Binding Name}"></Picker>     <Entry Text="{Binding GolfRound.GolfCourse.Name}" Placeholder="selected golf course" FontSize="Default" />     <Picker x:Name="pCategory" Title="Category" ItemsSource="{Binding GolfRoundCategories}"              SelectedItem="{Binding SelectedGolfRoundCategory, Mode=TwoWay}"             ItemDisplayBinding="{Binding Name}"></Picker>     <Entry Text="{Binding SelectedGolfRoundCategory.Name}" Placeholder="selected round category" FontSize="Default" />     <Button Command="{Binding EditCommand}" Text="Edit Round" />     <Button Command="{Binding DeleteCommand}" Text="Delete Round" />      <Label Text="{Binding Message}" ></Label> </StackLayout> 

View - code behind

public partial class EditGolfRoundPage : ContentPage {     public EditGolfRoundPage (GolfRound round)     {         var editGolfRoundViewModel = new EditGolfRoundViewModel();         editGolfRoundViewModel.GolfRound = round;          BindingContext = editGolfRoundViewModel;          InitializeComponent ();          //var editGolfRoundViewModel = new EditGolfRoundViewModel();         //editGolfRoundViewModel.GolfRound = round;         //editGolfRoundViewModel.SelectedGolfCourse = round.GolfCourse;          //BindingContext = editGolfRoundViewModel;      } } 

5 Answers

Answers 1

Implement IEquatable for class of property used in SelectedItem:

public class GolfCourse : IEquatable<GolfCourse> {     ...     public bool Equals(GolfCourse other)     {         if (other == null) return false;         return (this.Name.Equals(other.Name));     } } 

Usage, assuming ItemsSource contains an object with value of Name as shown below:

SelectedGolfCourse = new GolfCourse { Name = "Course 2" }; 

Answers 2

you are binding view model before Initialise page so that is wrong thing we can not bind data without Initialise page fro that you need to change code of xaml.cs like below public EditGolfRoundPage (GolfRound round) { InitializeComponent (); BindingContext = editGolfRoundViewModel; BindingContext.GolfRound = round; }

that will work for you Happy Coding :)

Answers 3

Xaml

    <Picker x:Name="ProductPicker" WidthRequest="220"    HeightRequest="35" Title="Select" ItemsSource="{Binding ProductList}" SelectedItem="{Binding ProductSelected}" ItemDisplayBinding="{Binding ProductName}">   </Picker> 

ViewModel

 public List<ProductModel> ProductList { get; set; } 

Populating Data in Datasource in Viewmodel

    ProductList = Products.Result.ToList(); 

Getting Selected Data

   private object _ProductSelected;       public object ProductSelected     {         get { return _ProductSelected; }         set         {             _ProductSelected = value;              ProductSelected_SelectedIndex.Execute(value);              OnPropertyChanged("ProductSelected"); //in case you are using MVVM Light         }     }   private Command ProductSelected_SelectedIndex     {         get         {             return new Command((e) =>             {             }}}     private object _CitySelectedFromList;     public object CitySelectedFromList     {         get { return _CitySelectedFromList; }         set         {             _CitySelectedFromList = value;             var cityid = _CitySelectedFromList as CityMasterModel;             tempcityids = Convert.ToInt32(cityid.Id);          }     } 

Answers 4

Can you try once replacing your Viewmodel. I have changed the type from Object to actual type. Set the Default item while loading the items from endpoint.

public class EditGolfRoundViewModel : INotifyPropertyChanged { ApiServices _apiServices = new ApiServices();  private string _message; private ObservableCollection<GolfCourse> _courses; private ObservableCollection<GolfRoundCategory> _roundCategories; private GolfCourse _selectedGolfCourse; private GolfRoundCategory _selectedGolfRoundCategory; private GolfRound _golfRound;  public EditGolfRoundViewModel() {     LoadCourses();     LoadRoundCategories(); }  public GolfRound GolfRound {     get { return _golfRound; }     set     {         _golfRound = value;         OnPropertyChanged();     } }   public string Message {     get { return _message; }      set     {         _message = value;         OnPropertyChanged();     } }  public ObservableCollection<GolfCourse> GolfCourses {     get { return _courses; }     set     {         if (_courses != value)         {             _courses = value;             OnPropertyChanged();         }      } }  public ObservableCollection<GolfRoundCategory> GolfRoundCategories {     get { return _roundCategories; }     set     {         _roundCategories = value;         OnPropertyChanged();     } }  public GolfCourse SelectedGolfCourse {     get { return _selectedGolfCourse; }     set     {         _selectedGolfCourse = value;         OnPropertyChanged("SelectedGolfCourse");     } }   public GolfRoundCategory SelectedGolfRoundCategory {     get { return _selectedGolfRoundCategory; }     set     {         _selectedGolfRoundCategory = value;         OnPropertyChanged();     } }    public ICommand EditCommand {     get     {         return new Command(async () =>         {             GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;             GolfRound.GolfCourse = SelectedGolfCourse;             GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;             GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;             GolfRound.LastModifiedUTC = System.DateTime.Now;              await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);         });     }  }  public ICommand DeleteCommand {     get     {         return new Command(async () =>         {             await _apiServices.DeleteGolfRoundAsync(GolfRound.GolfRoundID, Settings.AccessToken);         });     }  }  public event PropertyChangedEventHandler PropertyChanged;  [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }  private async void LoadCourses() {     GolfCourses = new ObservableCollection<GolfCourse>(await _apiServices.GetGolfCoursesAsync(Settings.AccessToken));     if (GolfCourses != null && GolfCourses.Count() > 0)         SelectedGolfCourse = GolfCourses[0]; }  private async void LoadRoundCategories() {     GolfRoundCategories = new ObservableCollection<GolfRoundCategory>(await _apiServices.GetGolfRoundCategoriesAsync(Settings.AccessToken));     if (GolfRoundCategories != null && GolfRoundCategories.Count() > 0)         SelectedGolfRoundCategory = GolfRoundCategories[0];  }  } 

Answers 5

You can try:

  • make sure your prop support INotifyPropertyChanged
  • make sure Selected Item variable has initial value
  • try debug your selected item variable, make sure has value

1. Initials

Take a look your code:

public EditGolfRoundViewModel() {     _selectedGolfCourse = new GolfCourse();     _selectedGolfRoundCategory = new GolfRoundCategory();     LoadCourses();     LoadRoundCategories(); } 

If you try to initial value Selected Item, don't do this:

_selectedGolfCourse = new GolfCourse(); _selectedGolfRoundCategory = new GolfRoundCategory(); 

let it null, is fine. You can do like this:

SelectedGolfRoundCategory = new GolfRoundCategory(); //or SelectedGolfRoundCategory = dataFromAPI; 

2. Assign

Take a look your code:

public ICommand EditCommand  {      get     {         return new Command(async() =>         {             GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;             GolfRound.GolfCourse = SelectedGolfCourse;             GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;             GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;             GolfRound.LastModifiedUTC = System.DateTime.Now;              await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);         });     }  } 

You trying selected item variable insert into to object GolfRound, like this part:

  GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;   GolfRound.GolfRoundCategory = SelectedGolfRoundCategory; 

Make sure you have INotifyPropertyChanged implement this model GolfRound for prop GolfRoundCategoryID and GolfRoundCategory. If not, it would not work. I have experience for this.

Hope this helpful.

Read More

Google play console does not reduce app download size

Leave a Comment

I'm developing an Android app using Xamarin latest version and Visual Studio latest version aiming Android SDK 8.0. The app is in Beta track (no release yet). I've been publishing updates of my app for 2 months using Google Play console, but something weird happened on my last update, because download size of my app growed from 14.16 MB to 29.80 MB and I haven't done anything different in my last update.

My app apk has always been 28 - 29 MB on disk (.apk size) and download size 14 MB aprox. In my last update I haven't done anything particulary big, just changed code logic and added 4 new resources (pngs total size: 43KB), no new dlls o references, no new componentes. If I use Android Studio apk analyzer on my last apk update, it says my download size should be 13,4 MB, but it seems that Google Play console does not want to reduce download size any more.

Android Studio shows download size should be 13,4 MB, but download size is actually 29 MB:

Pls your help. Thanks.

2 Answers

Answers 1

I updated Visual Studio from version 15.7.1 to 15.7.3

Visual Studio 15.7.1 to 15.7.3 = Xamarin.Android 8.3.0.19 to 8.3.3.2, consult releases.xamarin.com for details.

So the Mono runtime shared library got updated, thus the basically the whole apk is being downloaded again.

Answers 2

Have you tried Linking your app on release ?

Xamarin.Android applications use a linker to reduce the size of the application. The linker employes static analysis of your application to determine which assemblies are actually used, which types are actually used, and which members are actually used.

Used correctly it can cut your app size in half.

You can get more information here

enter image description here

Read More

Friday, August 10, 2018

Xamarin Forms Android App Keeps hiding action bar

Leave a Comment

Recently my Xamarin forms Android app has started hiding the top action bar on the main screen. After navigating to a child page it comes back. But this is very bad since the main page's navigation bar also has the menu button which can now never be tapped.

How do I prevent the behaviour that makes this title/action bar slide away and disappear?

0 Answers

Read More

Wednesday, August 1, 2018

How can I make a Xamarin Template customizable by passing in a type parameter?

Leave a Comment

Here is the method that I have come up with so far. However it seems like not such a clean solution.

Does anyone have any suggestions how I could come up with a better solution for doing this?

<?xml version="1.0" encoding="utf-8"?> <StackLayout xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"               xmlns:local="clr-namespace:Japanese;assembly=Japanese"               x:Class="Japanese.Templates.HeaderTemplate"               x:Name="this" HorizontalOptions="FillAndExpand" Orientation="Vertical" Spacing="0" Margin="0">     <StackLayout IsVisible="{Binding HeaderType, Converter={StaticResource HeaderType1BoolConverter}, Source={x:Reference this}  }" >         <!-- code -->     </StackLayout>     <StackLayout IsVisible="{Binding HeaderType, Converter={StaticResource HeaderType2BoolConverter}, Source={x:Reference this}  }" >         <!-- code -->     </StackLayout> </StackLayout> 

In my bank end CS:

using System; using System.Collections.Generic; using Xamarin.Forms;  namespace Japanese.Templates {     public partial class HeaderTemplate : StackLayout     {         public HeaderTemplate()         {             InitializeComponent();         }     public static readonly BindableProperty HeaderTypeProperty =         BindableProperty.Create(             nameof(HeaderType),             typeof(string),             typeof(DataViewCellTemplate),             default(string));      public string HeaderType     {         get { return (string)GetValue(HeaderTypeProperty); }         set { SetValue(HeaderTypeProperty, value); }     }      } } 

Converter Code:

public class HeaderType1BoolConverter : IValueConverter {     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)     {         return object.Equals(value, "1");     }      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)     {         throw new NotImplementedException();     } }  public class HeaderType2BoolConverter : IValueConverter {     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)     {         return object.Equals(value, "2");     }      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)     {         throw new NotImplementedException();     } } 

In the calling code:

<template:HeaderTemplate Header="Application" HeaderType="1" /> 

2 Answers

Answers 1

Using triggers is an option.

For e.g.: You can add property-triggers to check the value on HeaderType and accordingly update the Content (or layout) in your custom control HeaderView.

Please note in this case we are extending from ContentView not StackLayout on the assumption that only one layout/control is visible at a time.

XAML

<ContentView      xmlns="http://xamarin.com/schemas/2014/forms"      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"      xmlns:local="clr-namespace:SampleApp"      x:Class="SampleApp.HeaderView">      <ContentView.Triggers>          <!-- if header type is 1, use header1 layout -->         <Trigger TargetType="local:HeaderView" Property="HeaderType" Value="1">             <Setter Property="Content">                 <Setter.Value>                     <Label Text="Header1" />                 </Setter.Value>             </Setter>         </Trigger>          <!-- if header type is 2, use header2 layout -->         <Trigger TargetType="local:HeaderView" Property="HeaderType" Value="2">             <Setter Property="Content">                 <Setter.Value>                     <StackLayout>                         <Label Text="Header2" />                         <BoxView HeightRequest="1" BackgroundColor="Gray" />                     </StackLayout>                 </Setter.Value>             </Setter>         </Trigger>          <!-- you can add more layouts here if you need -->      </ContentView.Triggers>      <!-- add default content that can be displayed in case of no match -->     <StackLayout>         <Label Text="DefaultHeader" />         <BoxView HeightRequest="1" BackgroundColor="Gray" />     </StackLayout>      </ContentView> 

Code-behind

public partial class HeaderView : ContentView {     public HeaderView()     {         InitializeComponent();      }      public static readonly BindableProperty HeaderTypeProperty =         BindableProperty.Create(             nameof(HeaderType), typeof(string), typeof(HeaderView),             defaultValue: default(string));      public string HeaderType     {         get { return (string)GetValue(HeaderTypeProperty); }         set { SetValue(HeaderTypeProperty, value); }     } } 

Usage

<local:HeaderView HeaderType="1" /> 

Answers 2

I believe that what you are looking is a ControlTemplate here is an implementation.

App.xaml

<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleTheme.App">     <Application.Resources>         <ResourceDictionary>             <ControlTemplate x:Key="Header1Template">                 <StackLayout>                     <!-- code -->                 </StackLayout>             </ControlTemplate>             <ControlTemplate x:Key="Header2Template">                 <StackLayout>                     <!-- code -->                 </StackLayout>             </ControlTemplate>         </ResourceDictionary>     </Application.Resources> </Application> 

Usage (This will make header1 your default, you can pick any of the both)

<ContentView x:Name="contentView" ControlTemplate="{StaticResource Header1Template}"> 

Now there are many ways to change the header, either by defining as the default code above or with an action which can be a button click like the example given on the link or with a propertyChange event, if you want to depend on a property to choose the header like:

public int HeaderNumber { get; set; }  protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) {     base.OnPropertyChanged(propertyName);      if (propertyName == "HeaderNumber")     {         contentView.ControlTemplate = (HeaderNumber == 1) ? Header1Template : Header2Template;     } } 

Note that i never implemented a ControlTemplate but i think this solves your problem.

Read More