Xamarin Form Tabbed Page with Mvvmcross - android

I am having a weird issue, Xamarin Forms App works fine when I setup Content page as a startup page. If I set TabbedPage as a startup and same ContentPage as a Children of a TabbedPage then it doesn't display/data-bind ContentPage. No errors. What am I missing any idea? Here is my TabbedPage view model.
using MvvmCross.Core.ViewModels;
using System.Windows.Input;
namespace Company.Mobile.ViewModels
{
public class TabbedMainViewModel
: MvxViewModel
{
}
}
XAML:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="using:Xamarin.Forms"
xmlns:local="clr-namespace:company.Mobile.Pages;assembly=company.Mobile"
x:Class="company.Mobile.Pages.TabbedMainPage"
Title="Title">
<TabbedPage.Children>
<local:HomePage/>
<local:MainPage/>
<local:ResourcesPage/>
<local:ContactPage/>
</TabbedPage.Children>
</TabbedPage>

After a lot of trial and error and help from the community, here is what worked.
Set BindingContext to the ContentPage code-behind C#, something like below:
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
var svc = Mvx.Resolve<IMobileService>();
BindingContext = new HomeViewModel(svc);
}
}
Get your data in HomeViewModel constructor something like below:
public class HomeViewModel : MvxViewModel
{
private readonly IMobileService service;
public HomeViewModel(IMobileService service)
{
this.service = service;
//Content = service.GetContent; //Get your data
}
}

I would say that you can do that easier by adding this inline property for an each your tabbed page in XAML, e.g. for the home page it should be BindingContext="{Binding HomePageViewModel}"

Related

Xamarin.Forms page not displaying xaml layout

I have a contentpage in c# along with a xaml file.
Page1.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CompanyName.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
}
}
}
Page1.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CompanyName.Pages.Page1">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
x:Name="test"
/>
<Button Text="Hello, World!" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is a fairly large project so to isolate anything that could be causing issues, I set my MainPage to Page1.
App.xaml.cs:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
using System.Linq;
using CompanyName.DataBases;
using CompanyName.Pages;
using CompanyName.Models;
namespace CompanyName
{
public partial class App : Application
{
public static String OnDatabasesLoaded { get; } = "DatabasesLoaded";
public static Boolean IsDatabasesLoaded { get; private set; }
public App()
{
InitializeComponent();
// The below line is my normal entry point
// MainPage = new NavigationPage(new MainPage()) { BarBackgroundColor = Color.FromHex("#2196f3"),BarTextColor=Color.White};
MainPage = new NavigationPage(new Page1()); // I added this to see if Page1 would even work
}
protected async override void OnStart()
{
// Create the tabels
/*
ColorDatabase colorDatabase = await ColorDatabase.Instance();
QuiltDatabase quiltDatabase = await QuiltDatabase.Instance();
TextDatabase textDatabase = await TextDatabase.Instance();
*/
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
My problem is that my xaml file is not rendered. On both a physical device and the emulator, a blank screen appears (excluding the navigation bar at the top). Both the xaml and code behind are set as Embedded Resources. I am using Visual Studio 2022 with Xamarin.Forms 5.0.0.2478. I made a new project and I was able to create a new page and the layout displayed fine.
I haven't been able to see if this error happens on ios. All my testing has been on Android.
Thanks
"Both the xaml and code behind are set as Embedded Resources."
Why? code and xaml are not resource types. VS has to "compile" those files.
Let VS correctly insert them into your project:
Copy those files someplace safe outside the project.
Delete them from project.
Rt-click on project / Add New Item / Content Page, name "Page1".
Then open your originals in a text editor, so you can copy the text, paste them into the files created by VS.

page has two tool bars

I am starting the NavigationPage in the App.cs class:
MainPage = new NavigationPage(new PageA());
In PageA(), I am are calling "PushAsync":
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void btnCourseList_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new PageB());
}
}
If I click back button on PageB(), it goes to PageA() in which it has two tool bars.
Any idea how to fix this?
The reason for it is very simple you are providing two navigation pages in the page lifecycle which is in turn showing two Navbar's
Solution :
In your App.xaml.cs add a static NavigationPage property something like this:
public static NavigationPage NavigationPage { get; set; }
Assign this a Page in your app.xaml constructor something like this :
App.NavigationPage = new NavigationPage(new yourPage) ;
Then Assign this NavigationPage as your MainPage like this:
MainPage=App.NavigationPage;
Now Whenever you plan on going to the next page call the next page like this:
App.NavigationPage.PushAsync(new YourNewPage());
Goodluck!
In case of queries revert.

How to set page icon in Xamarin Forms?

I'm trying to set page icon. There is App class of PCL below.
public App()
{
CustomMainPage mainPage = new CustomMainPage();
NavigationPage rootPage = new NavigationPage(mainPage);
this.MainPage = rootPage;
}
What I tried to do?
NavigationPage.SetTitleIcon(mainPage, "icon.png");
The second approach.
NavigationPage.SetTitleIcon(this, "icon.png");
The third approach.
NavigationPage.SetTitleIcon(rootPage, "icon.png");
File icon.png is situated into Resources/drawable.
And finally I decided to implement my custom renderer for the NavigationPage in Xamarin Forms. NavigationPage is setted to MainPage property into App class of PCL.
I created DroidNavigationRenderer class into Android project.
[assembly: ExportRenderer(typeof(NavigationPage), typeof(DroidNavigationPageRenderer))]
namespace App1.Droid.DroidRenderers
{
public class DroidNavigationPageRenderer : NavigationPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
var actionBar = ((Activity)Context).ActionBar;
actionBar.SetIcon(Resource.Drawable.icon);
}
}
}
But actionBar is always returns as null.
What I do wrong and how to fix it?
In your ContentPage, you have the Icon property.
You can set it this way
Icon = Device.OnPlatform("Menu", "ic_fa_bars.png", "Assets/Icons/reload.png");
What is your CustomMainPage class? If I have TabbedPage and set one of its child I do it this way:
var navigationPage = new NavigationPage(mainPage);
if (Device.RuntimePlatform == Device.iOS)
{
navigationPage.Icon = "services.png";
}
hope it helps

Xamarin.Forms implement VideoView

How to implement a VideoView into my Xamarin.Forms shared code class?
What I tried:
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using XamNative.ViewModels;
using XamNative.Droid;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class VideoPage : ContentPage
{
public VideoPage ()
{
InitializeComponent ();
#if __ANDROID__
var linearLayout = new LinearLayout(Forms.Context);
linearLayout.LayoutParameters = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FillParent, LinearLayout.LayoutParams.WrapContent);
var _videoView = new VideoView(linearLayout.Context) { };
_videoView.SetMinimumHeight(300);
_videoView.SetMinimumWidth(300);
linearLayout.AddView(_videoView);
//MediaRecorder Code...
#endif
}
I think it has something to the with the VideoPage class inheriting from ContentPage, which is not the right inheritance.
The error is logged as "Application lost the surface".
I can implement a TextView this way, but not a VideoView
I would suggest using a Third Party Nuget as all the code is done for you.
You can either use:
Rox.Xamarin.Video Player: https://www.nuget.org/packages/Rox.Xamarin.Video/
Which is free.
Or a paid for component:
Octane.VideoPlayer: https://components.xamarin.com/view/video-player

MVVMCross (Droid) GridView won't update without focus?

I'm very new to MVVMCross & Xamarin, so it's very possible I'm missing something simple, but I have an Mvx.MvxGridView layout bound to a simple list of objects.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Mvx.MvxGridView
android:numColumns="5"
android:verticalSpacing="15dp"
android:horizontalSpacing="15dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="ItemsSource Bikes"
local:MvxItemTemplate="#layout/bikeassignmentview_bikeelement" />
</LinearLayout>
The view is pretty simple:
namespace Keiser.MPM.Screen.Droid.Views
{
using Android.App;
[Activity(Theme = "#style/Theme.FullScreen")]
public class BikeAssignmentView : BaseView
{
protected override void OnCreate(Android.OS.Bundle bundle)
{
base.OnCreate(bundle);
this.SetContentView(Resource.Layout.BikeAssignmentView_Page);
}
}
}
Same with the view model:
namespace Keiser.MPM.Screen.Core.ViewModels
{
using System.Collections.Generic;
using System.Windows.Input;
using Cirrious.CrossCore;
using Cirrious.MvvmCross.ViewModels;
using Keiser.MPM.Screen.Core.Models.Bikes;
public class BikeAssignmentViewModel : BaseViewModel
{
private IBikeManagerService _bikeManagerService;
private List<Bike> _bikes;
public List<Bike> Bikes { get { return _bikes; } }
public BikeAssignmentViewModel(IBikeManagerService bikeManagerService)
{
_bikeManagerService = bikeManagerService;
_bikes = _bikeManagerService.Bikes;
}
}
}
The service where the Bikes list is actually originating is nested all the way down in a service class:
namespace Keiser.MPM.Screen.Core.Models.Bikes
{
using System;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Core;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Plugins.Messenger;
using Core.Models.Settings;
public class BikeManagerService : MvxNotifyPropertyChanged, IBikeManagerService
{
public object BikesLocker = new object();
private List<Bike> _bikes = new List<Bike>();
public List<Bike> Bikes
{
get { return _bikes; }
set { _bikes = value; RaisePropertyChanged(() => Bikes); }
}
// --- Other boring code...
}
}
Here's the issue. The grid view won't populate dynamically at all if the list is empty when the view is loaded. If I enter the page with the list populated, it will load correctly, and the grids will be added for new objects added to the list, but it will not remove disposed grids from the list until I click on the screen a bit. The objects continue to update correctly until the object is disposed. Then the fields of the object stop working, but they don't disappear. Also, if the list ever goes back to empty, the view won't ever update again.
Am I missing something? Should I be invalidating the view or something? Any help or resources would be greatly appreciated!
[======================= Solution =======================]
The final solution was to convert the list to an observable collection:
Interface:
namespace Keiser.MPM.Screen.Core.Models.Helpers
{
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
public interface IObservableCollection<T>
: IList<T>
, INotifyPropertyChanged
, INotifyCollectionChanged
{
}
}
Class:
namespace Keiser.MPM.Screen.Core.Models.Helpers
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class SimpleObservableCollection<T>
: ObservableCollection<T>
, IObservableCollection<T>
{
public SimpleObservableCollection(List<T> source) : base(source) { }
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
}
}
}
All changes made to the Collection had to be done on the main UI thread, which began to degrade performance (I'm guessing from the continual context switching?). I ended up scrapping the Observable list and implementing an IEnumerable class which fires a message on changes and subscribing to the message in the view:
namespace Keiser.MPM.Screen.Droid.Views
{
using Android.App;
using Android.Widget;
using Cirrious.MvvmCross.Plugins.Messenger;
[Activity(Theme = "#style/Theme.FullScreen")]
public class BikeAssignmentView : BaseView
{
protected MvxSubscriptionToken _BikeListToken;
protected override void OnCreate(Android.OS.Bundle bundle)
{
base.OnCreate(bundle);
this.SetContentView(Resource.Layout.BikeAssignmentView_Page);
var gridView = FindViewById<GridView>(Resource.Id.gridview);
_BikeListToken = Cirrious.CrossCore.Mvx.Resolve<IMvxMessenger>().SubscribeOnMainThread<Core.Models.Bikes.BikesChangedMessage>(message =>
{
((BaseAdapter)gridView.Adapter).NotifyDataSetChanged();
});
}
}
}
Normal Mvvm and Data-Binding works using INotifyPropertyChanged
This means that when your Grid in the UI binds its ItemsSource to Bikes on the BikeAssignmentViewModel then it hooks into the PropertyChanged event on BikeAssignmentViewModel
Since you are firing RaisePropertyChanged from your Service and not from your ViewModel then the Grid never sees this change notification.
To work around this:
you could find a way to raise the RaisePropertyChange call from the ViewModel rather than the Service
you could find a way to bind the Grid to the Service rather as well as to the ViewModel (e.g. bind ItemsSource Service.Books)
If you're new to Data-Binding then it may also be worth reading up more about Data-Binding and for lists also learning about ObservableCollection and INotifyCollectionChanged

Categories

Resources