Exercise 3: Work with mutable lists (XAM280)
This exercise will continue building the Fun Flags application. You'll add a new Toolbar to the main page of the application and use it to delete flags from the ListView.
To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.
Open the starter solution
This exercise is a continuation of the previous exercise. You can use your existing solution or begin from the prior Exercise 2 > Completed solution in your copy of the cloned or downloaded course materials.
Add the ToolBarItem elements to the page
Start by adding your ToolBarItem
to the page to set your app in "Edit" mode. You'll track the current state and adjust the button text between "Edit" and "Cancel" and two icons indicating edit and cancel.
- Open
AllFlags.xaml
. -
Add a new
ToolbarItem
to theToolbarItems
collection for the page:- Give it a meaningful name such as "editButton" so you can access it in code behind
- Set the
Text
to "Edit" - Set the
Icon
to "ic_edit.png". This is a pre-supplied image that is already in all the platform-specific projects - Set the
IsDestructive
property to "true"
- Wire up an event handler to the
Clicked
event on your new toolbar item. Name it OnEdit and create the handler in the code behind file. It's defined as a standardEventHandler
method.
XML markup
<ContentPage.ToolbarItems><ToolbarItem x:Name="editButton" Text="Edit" Clicked="OnEdit" Icon="ic_edit.png" IsDestructive="true" />
</ContentPage.ToolbarItems>
C# code
public partial class AllFlags : ContentPage
{
private void OnEdit(object sender, EventArgs e)
{
}
}
Change the toolbar button based on editing state
You want to change the button icon and text when you enter and exit "edit" mode. To to this, you could alter the text and icon properties on your existing button, but then you'll have to have duplicate copies of the text and images. Another approach is to use two separate buttons and swap them in and out based on the current editing state. Unfortunately, there's no way to hide a ToolbarItem
today so you can't put both of them into the toolbar collection. Instead, you'll store the cancel button in your page resources and then swap them out in your code behind as you enter and exit edit mode.
- Add a new
ResourceDictionary
into the page; remember this needs to be assigned to theContentPage.Resources
property. -
Add a
ToolbarItem
object to the resource dictionary:- Give it a key - "cancelEditButton"
- Set the
Text
to "Cancel" - Set the
Icon
to "ic_cancel.png" - this is another pre-supplied image that is already in your platform head projects - Set the
Clicked
event to the same handler (OnEdit
)
- Switch to the code behind file and create a new boolean field named isEditing.
-
Add a
ToolbarItem
field named cancelEditButton to the class. In the constructor, after the call toInitializeComponent
, set it to your button fromResources
. You can get the value like this:cancelEditButton = (ToolbarItem)Resources[nameof(cancelEditButton)];
-
In the
OnEdit
handler, set the isEditing field based on whether the sender argument is the editButton or cancelEditButton. The field should be true if the Edit button was pressed, and false if it was the Cancel button. - Remove the sender
ToolbarItem
from theToolbarItems
collection and add the correct button based on the state of the isEditing field you just assigned. This should swap the button state. - Try running the application and clicking on your new button; it should switch between the two buttons.
XML markup
<ContentPage.Resources>
<ResourceDictionary>
<ToolbarItem x:Key="cancelEditButton" Text="Cancel"
Clicked="OnEdit" Icon="ic_cancel.png" />
</ResourceDictionary>
</ContentPage.Resources>
C# code
public partial class AllFlags : ContentPage
{
bool isEditing;
ToolbarItem cancelEditButton;
public AllFlags()
{
...
InitializeComponent();
cancelEditButton = (ToolbarItem)Resources[nameof(cancelEditButton)];
}
private void OnEdit(object sender, EventArgs e)
{
var tbItem = sender as ToolbarItem;
isEditing = (tbItem == editButton);
ToolbarItems.Remove(tbItem);
ToolbarItems.Add(isEditing ? cancelEditButton : editButton);
}
}
Add support for editing mode
The editing behaviour starts in "normal" mode (the existing behavior), and when you click the new Edit button you'll switch to the "edit" mode. In this mode, the ListView will handle the selection by prompting the user to delete the row. If the user affirms the selection, then the row will be removed from the Flags collection.
-
Start by disabling the current tap behavior (navigate to details) when you are in edit mode. Find the
OnItemTapped
method and ignore the tap if you are in edit mode (using theisEditing
boolean).private async void OnItemTapped(object sender, ItemTappedEventArgs e) { if (!isEditing) { await this.Navigation.PushAsync(new FlagDetailsPage()); } }
-
Next, add an event handler for the
ListView.ItemSelected
event; you can do this in code behind or XAML.You could also add this logic into the existing
OnItemTapped
method, there would be no difference in this case. You are using theItemSelected
event just to demonstrate its use. -
In the event handler, check the
isEditing
field; if it'strue
, prompt the user withDisplayAlert
to delete the selectedFlag
. You will want to display a "Yes" and "No" selection. - If the user responds "Yes", remove the
Flag
from theFunFlactsViewModel.Flags
collection. - As a final step, when in edit mode, call the
OnEdit
method to turn off edit mode after an item is deleted. You will need to pass cancelEditButton as the sender. -
Run the application and try your change. Does it delete the row? Why or Why not?
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) { if (isEditing) { var flag = (Flag)e.SelectedItem; if (flag != null && await this.DisplayAlert("Confirm", $"Are you sure you want to delete {flag.Country}?", "Yes", "No")) { DependencyService.Get<FunFlactsViewModel>() .Flags.Remove(flag); } // Reset the edit button OnEdit(cancelEditButton, EventArgs.Empty); } }
Use an observable collection
Examine the assignment of the FlagRepository.Flags
collection in the FlagData project. Have a look in the constructor. It's currently set to a List<Flag>
collection which does not report changes.
Flags = new List<Flag>(flags.OrderBy(f => f.Country));
Notice how the actual property, both here and in our ViewModel, is defined as an IList<T>
- this is a good practice to get into because it means you can change the implementation of the underlying list without changing the contract exposed by the class (e.g. the property type).
- Instantiate a
System.Collections.ObjectModel.ObservableCollection<Flag>
instead of theList
and then run the app; it should now delete the row and disappear from the UI.
Optional Challenge: fix the selection issue
Tap on an item to navigate to the details page. Then go back and enter Edit mode. Notice that now you cannot tap the currently selected item to delete it! See if you can add the code to fix this minor issue. The completed solution has the fix included.
Hint: there are two ways you can solve this. You can override theOnAppearing
to get rid of the selection when you return from the details screen and also de-select the item when you exit edit mode and decide not to delete an item. Alternatively, you can move the logic into the ItemTapped
handler which is always called. This is the approach taken by the lab.
Exercise summary
In this exercise, you added support to delete an item from the ListView
through a Toolbar button.
You can view the completed solution in the Exercise 3 > Completed folder of your copy of the cloned or downloaded course materials.