iTranslated by AI
Two Approaches to Navigate to a Detail Page from a ListView in Xamarin.Forms
Apologies for the slightly over-the-top "elegant" title, the long title, and for the self-promotion.
I would like to introduce how you can easily handle ListView selections, navigate to a new page, and pass parameters using a combination of KAMISHIBAI for Xamarin.Forms and Xamarin.Forms.BehaviorsPack.

This is a common scenario, and I will show you how to write it extremely simply.
For the complete source code of the sample, please refer to StylishListViewSample here.
First, define the ViewModel for the initial screen as follows:
public class FruitsListPageViewModel : ViewModelBase
{
public IReadOnlyList<Fruit> Fruits { get; }
= FruitsRepository.Fruits;
public NavigationRequestCommand<Fruit> RequestDetail { get; }
= new NavigationRequestCommand<Fruit>();
}
NavigationRequestCommand is an implementation class of INavigationRequest that also implements ICommand.
It handles both the reception of command execution and the issuance of navigation requests simultaneously.
You can use this when there is little to do between the user operation and the navigation. (It is also possible to register an Action in the constructor to execute simple processing before navigation.)
Next, write the XAML like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
<ContentPage.BindingContext>
<viewModels:FruitsListPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Behaviors>
<mvvm:PushAsync Request="{Binding RequestDetail}"
x:TypeArguments="views:FruitDetailPage" />
</ContentPage.Behaviors>
<behaviorsPack:SelectedItemBehavior
Command="{Binding RequestDetail}"/>
<DataTemplate>
<TextCell Text="{Binding Name}" TextColor="{Binding Color}"/>
</DataTemplate>
</ContentPage>
First, take a look at the SelectedItemBehavior registered in the ListView's Behaviors.
<behaviorsPack:SelectedItemBehavior Command="{Binding RequestDetail}"/>
SelectedItemBehavior is quite helpful (at least to me) because it executes the Command while passing the selected item when a row is tapped (it also handles issues like re-selection).
When a row is selected in the ListView, it calls the Execute method of RequestDetail, passing the Fruit object bound to the selected row as an argument.
Next, look at the Page's Behaviors.
<mvvm:PushAsync Request="{Binding RequestDetail}"
x:TypeArguments="views:FruitDetailPage" />
This declares that upon receiving a request from RequestDetail, the app should navigate to FruitDetailPage using PushAsync.
With this, when an item is selected in the ListView, the selected Fruit is passed to the next page, and navigation occurs.
Finally, implement the receiving side as follows:
public class FruitDetailPageViewModel
: ViewModelBase, IPageInitializeAware<Fruit>
{
public void OnInitialize(Fruit fruit) => Fruit = fruit;
You can receive it in a type-safe manner as a parameter in the OnInitialize method. In this code, I set the value to the Fruit property after receiving it.
Isn't that nice?
...Hmm? Not nice? You want to pass the ID and re-fetch the value on the destination page?
OK, let's introduce that pattern.
Modify the initial screen's ViewModel as follows:
// public NavigationRequestCommand<Fruit> RequestDetail { get; }
// = new NavigationRequestCommand<Fruit>();
public NavigationRequestCommand<int> RequestDetail { get; }
= new NavigationRequestCommand<int>();
I just changed the type parameter from Fruit to int.
Next, modify the SelectedItemBehavior in XAML to pass the Fruit's ID to the Command as follows:
before
<behaviorsPack:SelectedItemBehavior
Command="{Binding RequestDetail}"/>
after
<!--
<behaviorsPack:SelectedItemBehavior
Command="{Binding RequestDetail}" PropertyPath="Id"/>
Finally, modify the receiving side to re-fetch the value based on the received ID.
//public class FruitDetailPageViewModel
// : ViewModelBase, IPageInitializeAware<Fruit>
public class FruitDetailPageViewModel
: ViewModelBase, IPageInitializeAware<int>
{
// public void OnInitialize(Fruit fruit) => Fruit = fruit;
public void OnInitialize(int id) =>
Fruit = FruitsRepository.FindById(id);
That's it.
Man, Xamarin.Forms.BehaviorsPack and Kamishibai are so convenient!
Bonus
By the way, sharp-eyed readers might have already realized this, but you can actually implement this navigation request without going through the ViewModel, can't you?
Whether that is a good practice or not is debatable, but it is certainly possible.
So, I will introduce that method as well just in case.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
<ContentPage.BindingContext>
<viewModels:FruitsListPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Behaviors>
<mvvm:PushAsync x:TypeArguments="views:FruitDetailPage">
<mvvm:PushAsync.Request>
<mvvm:NavigationRequestCommand x:TypeArguments="x:Int32"
x:Name="NavigationRequestCommand"/>
</mvvm:PushAsync.Request>
</mvvm:PushAsync>
</ContentPage.Behaviors>
<ListView>
<ListView.Behaviors>
<behaviorsPack:SelectedItemBehavior
Command="{Binding Source={x:Reference NavigationRequestCommand}}"
PropertyPath="Id"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" TextColor="{Binding Color}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Define the Request to be set in the PushAsync behavior by giving it a name in XAML:
<mvvm:PushAsync x:TypeArguments="views:FruitDetailPage">
<mvvm:PushAsync.Request>
<mvvm:NavigationRequestCommand x:TypeArguments="x:Int32"
x:Name="NavigationRequestCommand"/>
</mvvm:PushAsync.Request>
</mvvm:PushAsync>
Then, use x:Reference in the SelectedItemBehavior to call the NavigationRequestCommand in XAML directly.
<behaviorsPack:SelectedItemBehavior
Command="{Binding Source={x:Reference NavigationRequestCommand}}"
PropertyPath="Id"/>
Since the ViewModel ends up doing effectively nothing, one might feel that going through the ViewModel is unnecessary. Furthermore, if you aren't splitting logic into a ViewModel or don't need code reusability, you might even think, "Just write it directly in the code-behind!" Yet, bypassing the ViewModel entirely... hmm.
What would you do?
That's all for today! See you next time.
Discussion