Xamarin Forms - Prism - OnNavigatedTo Calling Twice - c#

So, I've been developing an App for 2 months with prism, and just now I've realized that the method OnNavigatedTo is been calling twice when I select an Item from a MasterDetailPage.
I have no clue why it is happening, I'm sure I'm missing something but I'm about two days trying to solve it.
I'll put some code here, and if u guys need more info I can post more detailed.
Observation: When I'm in the page "A" and I choose the page "A" in the master detail item list, the OnNavigatedTo is called only once, but when I'm in Page "B" and I choose the page "A", The OnNavigatedTo is called twice.
Since now, Thank you guys and sorry about the ignorance.
MasterDetailPage MVVM:
public class PrincipalMasterDetailPageViewModel : ViewModelBase {
public ObservableCollection<PrincipalMasterPageItem> MenuItems { get; set; }
public PrincipalMasterDetailPageViewModel(INavigationService navigationService) : base(navigationService)
{
MenuItems = new ObservableCollection<PrincipalMasterPageItem>();
}
public async override void OnNavigatedTo(NavigationParameters parameters) {
base.OnNavigatedTo(parameters);
.. Here I'm calling an API, thats why I have the async
}
}
Custom Navigation Page MVVM:
public class PrincipalNavigationPageViewModel : ViewModelBase {
public PrincipalNavigationPageViewModel(INavigationService navigationService) : base(navigationService) {
}
}
The Page that I actually show when I select an item in masterdetailpage item list:
public class NewPageTestViewModel : ViewModelBase
{
public NewPageTestViewModel(INavigationService navigationService) : base(navigationService)
{
}
public override void OnNavigatedTo(NavigationParameters parameters)
{
base.OnNavigatedTo(parameters);
Debug.WriteLine("Calling twice HERE!");
}
}
The RegisterTypes of these three examples:
containerRegistry.RegisterForNavigation<PrincipalMasterDetailPage>();
containerRegistry.RegisterForNavigation<PrincipalNavigationPage>();
containerRegistry.RegisterForNavigation<NewPageTest>();
How do I call other pages from PrincipalMasterDetailPageViewModel:
NavigationService.NavigateAsync(string.Format("PrincipalNavigationPage/{0}", item.TargetPageName));
In App.cs I start like the following because I need the login page first:
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("LoginPage");
}
When the user log in, It navigate like this:
await NavigationService.NavigateAsync("/PrincipalMasterDetailPage/PrincipalNavigationPage/WhateverPageIWantTo");

I don't know if anybody is still interested in this but I ran into the same issue and figured out what is going on.
All code samples I found register a NavigationPage like this:
containerRegistry.RegisterForNavigation<NavigationPage>("Navigation");
In order to be able to do something like this on app launch:
NavigationService.NavigateAsync($"Main/Navigation/Home");
However, the problem seems to be that when this NavigationPage is instantiated without a specific ViewModel assinged to it, the 'INavigationAware' events are somehow propagated to the MasterDetailPage's ViewModel resulting in the events on that one to be called twice.
I fixed it by registering the NavigationPage for Navigation with a ViewModel like this:
containerRegistry.RegisterForNavigation<NavigationPage, NavigationPageViewModel>("Navigation");
The ViewModel itself is nothing special:
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Attributes;
namespace SocialRecipe.ViewModels
{
public class NavigationPageViewModel : ViewModelBase
{
public NavigationPageViewModel()
{
}
public override void OnNavigatedFrom(INavigationParameters parameters)
{
}
public override void OnNavigatedTo(INavigationParameters parameters)
{
}
public override void OnNavigatingTo(INavigationParameters parameters)
{
}
}
}
This way the events of the NavigationPage are routed to the NavigationPageViewModel and are no longer propagated to the MasterDetailPageā€¦

I've noticed some inconsistent results with Prism as well from a previous project. If you are only seeing it now and it worked previously I would try and backtrack to see if the version you used was different.
https://github.com/PrismLibrary/Prism/issues

Related

Event when Xamarin page is FULLY loaded?

community.
In advance, I apologize if this really has a simple solution. I have a troublesome problem, considering my skill level with Xamarin, that I need to get resolved.
The problem is it seems like Xamarin doesn't have an event/function/etc to call when a page is FULLY displayed, as in you can see everything the page is supposed to display, and then that function is called. Quick note, what does not work for me is the OnAppearing function because it fires off too early before the screen is visible.
I was attempting to decipher a solution here. In this post, a user answers with the following code.
Here is step 1):
private bool _toggleTemp;
public bool ToggleTemp
{
get => _toggleTemp;
set => SetProperty(ref _toggleTemp, value);
}
Step 2)
LoadingVm.ToggleTemp = true;
Step 3)
<Switch IsToggled="{Binding ToggleTemp}" Toggled="Switch_OnToggled" IsVisible="False" />
Step 4)
private async void Switch_OnToggled(object sender, ToggledEventArgs e)
{
/* Your code goes here... */
}
First concern. I created a ViewModel file, it's inside of a folder called "ViewModels", and the user who posted the code in the link said to create a property in the view model, for some reason I'm getting an error stating "the name set property does not exist in the current context". So is it alright I swap that code out and just put the following instead? I mean it SEEMS like the same thing, right?
public bool Temp
{
get { return _toggleTemp; }
set { _toggleTemp = value; }
}
Second concern. I have no clue what "LoadingVm" is in his code. It doesn't come up for me. Is it because I'm missing a "using" at the top? But regardless, the whole code line was this "LoadingVm.ToggleTemp = true;", so he's just calling the function to set it to be true.
In the end, I'm ASSUMING that this will no doubt help me get the code working where I can do whatever I please AFTER the page is completely loaded, correct? I don't really see too many people disagreeing with the method the guy in the link has given, but if so, feel free to give other suggestions.
Again, please forgive me if this is a simple error. I'm still getting used to Xamarin and C# is still somewhat fresh in my mind from not having used it in a while.
You can use custom renderer of ContentPage to know if the view is fully loaded and then use MessagingCenter to notify your shared project:
In iOS:
[assembly:ExportRenderer (typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.iOS
{
public class CameraPageRenderer : PageRenderer
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("ViewDidAppear");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Android:
[assembly: ExportRenderer(typeof(ContentPage), typeof(CameraPageRenderer))]
namespace App362.Droid
{
[Obsolete]
public class CameraPageRenderer : PageRenderer{
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Console.WriteLine("OnAttachedToWindow");
MessagingCenter.Send<object>(new object(), "ViewLoaded");
}
}
}
In Shared project:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Console.WriteLine("OnAppearing");
MessagingCenter.Subscribe<object>(new object(), "ViewLoaded", (sender) =>
{
// Do something whenever the "ViewLoaded" message is received
Console.WriteLine("Do something whenever the ViewLoaded message is received");
});
}
}

parameter for navigation service (INavigationService)

I create a project with infragistic which generates the view and views models folders, what I want to do now is create a binding context to the view model as it is normally done, but this view model has INavigationService parameters and I don't know how to configure it. Those parameters, if someone helps me, I would really appreciate it, I attach images so that they understand me more.
enter image description here
enter image description here
In the mainPage background code you can use following code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext= new PersonsViewModel(Navigation);
}
}
This Navigation comes from NavigableElement you can use Navigation directly in ContentPage.
NavigableElement is under the Xamarin.Forms namespace like following code.
============Prism==============
If you use Prism. you should registe it the App.xaml.cs.
public partial class App
{
/*
* The Xamarin Forms XAML Previewer in Visual Studio uses System.Activator.CreateInstance.
* This imposes a limitation in which the App class must have a default constructor.
* App(IPlatformInitializer initializer = null) cannot be handled by the Activator.
*/
public App() : this(null) { }
public App(IPlatformInitializer initializer) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/MainPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
}
}
In your MainPage.xaml.cs you do not need other binding code.
Here is a demo about it.
https://github.com/manacespereira/xamarin-prism-navigation

Execute same function on every page in Xamarin.Forms

I have a function that I need to perform to do some checks (e.g: CheckForUpdate, CheckNetworkConnection, CheckUserAuthorization, ...) on every page appearing or somehow before user request completed.
so I made a c# class and called it BasePage.cs:
public static class BasePage
{
public static async void CheckForUpdate()
{
// actual codes to check for updates are not included here
// just a sample alert
await App.Current.MainPage.DisplayAlert("Update", "There is a new version avaiable to update, would you like to download?", "Download", "Skip");
}
}
and used it in my pages like below:
LoginPage.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
await Task.Run(() => BasePage.CheckForUpdate());
}
}
I don't know if this is best practice or not (I guess not) but it's not displaying alert anyhow.
so my question is what is the best way to execute functions on every page and why the code above doesn't work.
Your code doesn't seem to run on UI thread. Just use Device.BeginInvokeOnMainThread, try like below
protected override void OnAppearing()
{
Device.BeginInvokeOnMainThread(() => {
BaseClass.CheckForUpdate();
});
}
Hope it help you.

Android Things with Xamarin Issue with IGpioCallback

I'm just starting with Android Things with Xamarin, and I've already successfully turned on a LED, but I'm having trouble to detect a push button input.
I think the problem is the "RegisterGpioCallback" in the code below, but I'm not sure and really don't know how to fix it. Can somebody help me?? This is the code I'm using:
public class BlinkActivity : Activity
{
private IGpio gpio;
private IGpio button;
private IGpioCallback mButtonCallback;
protected override void OnCreate(Bundle savedInstanceState)
{
this.mButtonCallback = mButtonCallback;
PeripheralManager peripheralManager = PeripheralManager.Instance;
gpio = peripheralManager.OpenGpio("BCM17");
gpio.SetDirection(Gpio.DirectionOutInitiallyLow);
gpio.Value = false;
button = peripheralManager.OpenGpio("BCM4");
button.SetDirection(Gpio.DirectionIn);
button.SetEdgeTriggerType(Gpio.EdgeNone);
button.RegisterGpioCallback(new Handler(), mButtonCallback);
base.OnCreate(savedInstanceState);
Task.Run(() =>
{
if (mButtonCallback.OnGpioEdge(button) == true)
{
gpio.Value = !gpio.Value;
}
});
}
}
You need to actually implement the IGpioCallback interface so the com.google.android.things.pio library can make a "call back" into your application when the value of the GPIO changes.
Assign the RegisterGpioCallback to the actual object instance that has implemented the interface, in the following example, that will be on the Activity.
public class BlinkActivity : Activity, IGpioCallback
{
~~~~
button.RegisterGpioCallback(new Handler(), this);
~~~~
// remove the Task.Run block
public OnGpioEdge(Gpio gpio)
{
Log.Debug("SO", gpio.Value.ToString());
}
~~~~
}
I had some issues following this in Maui. I'd created an IGPIO interface in the shared code, and then a platform-specific GPIO class inside the Android platform code. The code would run, but then crash when it got to the Registration of the callback. The error said I had to pass a Java.Lang.Object or Java.Lang.Throwable as argument 2 to com.google.android.things.pio.impl.GpioImpl.registerGpioCallback(android.os.Handler, com.google.android.things.pio.GpioCallback).
I tried using each of these as the base class for my GPIO class, but then the app wouldn't build. When I'd autogenerated the IGpioCallback interface implementation in the class it had created a dispose method and a Handle property along with the OnGpioEdge callback method. Removing these allowed the app to work properly. so my class definition ended up looking something like this for the registration and event:
public class GPIO : Java.Lang.Throwable, IGPIO, IGpioCallback
{
public event EventHandler OnButtonEdge;
IGpio ButtonPin;
public void registerPinForEdgeDetection(string pinName)
{
using (var peripheralManager = PeripheralManager.Instance)
{
ButtonPin = peripheralManager?.OpenGpio(pinName);
ButtonPin.SetDirection(Gpio.DirectionIn);
ButtonPin.SetEdgeTriggerType(Gpio.EdgeBoth);
ButtonPin.RegisterGpioCallback(new Android.OS.Handler(), this);
}
}
public bool OnGpioEdge(IGpio gpio)
{
OnButtonEdge?.Invoke(ButtonPin, EventArgs.Empty);
return true;
}
}

Zxing.net.mobile, cannot get an event when scanner is closed xamarin.forms

I'm using the Zxing.net.mobile library in my xamarin.forms application, but I've found a big limitation in the using of it.
When the barcode scanner is active and I press the back button, the UI returns to the previous page but there's no way to execute code, because there's no way to trigger an event or passing a callback function to the scanner and the OnAppearing() method doesn't fire. Does anyone know a solution that might work? Thank you!
Edit: example code
public partial class MyPage : ContentPage
public MyPage()
{
InitializeComponent();
}
public async void MyMethod()
{
var scanner = new MobileBarcodeScanner();
scanner.ScanContinuously(MyScanningAction);
}
private async void MyScanningAction(ZXing.Result scanningResult)
{
... do something ....
}

Categories