directly from mexico, eduardo shares some knowledge on the xamarin platform. also, he is super into artificial intelligence, so you may also see some posts about that here.

Interacting with a ListView in Xamarin Forms

Interacting with a ListView in Xamarin Forms

pexels-photo-870903.jpeg

ListView interactions

Pull to refresh, slide to delete and navigation to details

ListView interactions are an important part of the user experience on any application. As often as it is necessary for our applications to list items inside of a list, it is necessary for the user to interact with them.

In my previous post, I built a Notes application that allowed users to create new notes, assign a title and some content, and save these notes inside an SQLite database. Once saved in the database, there was a Page that read all these items and listed them inside a ListView, but there is no real interaction with the list.

In this post I will focus on 3 interactions you can add to your ListViews:

  1. Pull to refresh (reading from the table)
  2. Context actions (deleting an element)
  3. Taps (showing a details page)

All the code can be found here, you can switch branches to find the old status of the previous (SQLite) post, and the new status at the end of this post (Interactions).

Pull to refresh

Probably the easiest functionality to add, especially in a scenario like this where we already have the code that gets the information that is later displayed in the ListView. I will do one change in my MainPage.xaml.cs file (the one inside the shared library) so that the code that is currently reading from the Post table from the OnAppearing overridden method is not an independent method.

private void ReadPosts()
{
    //! added using SQLite;
    using (SQLiteConnection conn = new SQLiteConnection(App.DatabasePath))
    {
        conn.CreateTable<Note>();
        List<Note> notes = conn.Table<Note>().ToList();
        notesListView.ItemsSource = notes;
    }
}

Nothing new here, this new method contains the same code that was previously in the OnAppearing, except of course for the call to the base class' OnAppearing. A method call to this ReadPost method then replaces the previous body of the overridden method.

Enabling pull to refresh then, is as easy as setting the IsPullToRefresh property for the ListView to true. By doing this, either from XAML or the C# code behind, when the user "pulls" the ListView down, an animation with an activity indicator will be executed, but it is up to us to implement the functionality that will retrieve the information, and let the ListView know when the information has been retrieved so the animation ends.

My ListView then will now look like this:

<ListView x:Name="notesListView"
          IsPullToRefreshEnabled="True"
          ItemSelected="NotesListView_ItemSelected">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Title}"
                     Detail="{Binding Content}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Handling the event

This pull to refresh will trigger an event for which we can add an event handler that will respond to this interaction, in this case retrieving any information that is needed and displaying it in the ListView. The name of the event is Refreshing, you can set the event handler from XAML or C#. Since I will do it from C#, I will add this code inside the constructor for the MainPage:

notesListView.Refreshing += NotesListView_Refreshing;

And that event handler will simply call the same method that we are calling from the OnAppearing, the one that contains the same functionality we had before.

private void NotesListView_Refreshing(object sender, EventArgs e) { ReadPosts(); }

Ending the animation

This is of course not enough to stop the refreshing animation, we have to notify the ListView that it has already been updated. It turns out that executing the pull to refresh, the IsRefreshing property is set to true, we simply need to change this to be false.

I will do this inside the ReadPosts method, right after reading from the table and setting the source of the ListView:

notesListView.IsRefreshing = false;

Context Actions

On Android, by long pressing on an item from the ListView, context actions can appear in the toolbar (titlebar), on iOS, this context actions can appear when swiping left on any of the items, these context actions can be easily implemented with Xamarin Forms on both Android and iOS.

This can be done by simply adding these context actions to the ListView's cell, and then adding an event handler (or Command) to the Click event for that ContextAction. Notice here I am setting the action to have an IsDestructive as true so that on iOS this shows as a red button.

<TextCell Text="{Binding Title}"
          Detail="{Binding Content}">
    <TextCell.ContextActions>
        <MenuItem IsDestructive="True"
                  Text="Delete"
                  Clicked="MenuItem_Delete"/>
    </TextCell.ContextActions>
</TextCell>

Just like this, all the items inside the ListView will now display this context action, either by long-pressing the item on Android, or sliding left on iOS. Now it is as easy as implementing the functionality on the event handler.

In this case, the functionality will be deleting the item that triggered the event from the list, but not only from the ListView of course but from the source. Remember that by establishing the ItemSource for the ListView, each element inside that source is listed in a cell. For this, the cell contains the information for the element that it is listing. This is how we can get the object (in this case note) that needs to be removed from the table.

private void MenuItem_Delete(object sender, EventArgs args)
{
    Note itemToDelete = ((sender as MenuItem).BindingContext as Note);
}

The way to remove this item form the List will be removing it form the source, and then reading the source again (which now won't have the item again). So this next functionality is exclusive to this app (SQLite) but may be a good example of how the item will eventually be removed from the view.

using (SQLiteConnection conn = new SQLiteConnection(App.DatabasePath))
{
    conn.CreateTable<Note>();
    conn.Delete(itemToDelete);
}

ReadPosts();

Notice the call to the ReadPosts method after deleting, and outside the using statement (this is important because in that method a new connection to the SQLite database is created).

This will effectively delete the item from the list, that item from which the delete context action was executed.

Tapped item and detail page

Finally, let's implement the navigation to a details page. I won't create a new page, but I will change the Page that I already have from which a new Note is being created. The logic will be this one:

  1. The selected value from the ListView will be obtained similarly to the itemToDelete in the code above.

  2. That element will be passed to the NewNotePage

  3. The values from the properties for the selected value will be set to the text of the text boxes in the UI

  4. When the NewNotePage receives an item, instead of inserting, it will be updating when pressing the save button

I will start by making this NewNotePage request a Note from its constructor (an overload of the constructor that is also calling the InitializeComponent method). That selected value will be assigned to a local variable:

Note selectedNote;
public NewNotePage (Note selectedNote)
{
    InitializeComponent ();

    this.selectedNote = selectedNote;
}

After this, I can assign the value from the properties of this variable to the text boxes:

titleEntry.Text = selectedNote.Title;
contentEditor.Text = selectedNote.Content;

And like this, the Page is ready for navigation to be implemented. Simply by getting the selected item from the MainPage, and passing it through the constructor to this new page will the data be set to the corresponding text boxes.

Back in the MainPage, I already had the event handler for when an item in the ListView is selected. From there, I can get the selected value and navigate to the Page, while passing that selected value to the second overload of the NeWNotePage's constructor:

private void NotesListView_ItemSelected(object sender, EventArgs e)
{
    Note selectedNote = notesListView.SelectedItem as Note;
    Navigation.PushAsync(new NewNotePage(selectedNote));
}

And there it is, like this, you can implement navigation to a detail page by passing the selected value. I now only need to change a bit the functionality of the save button's click event handler. With the following code, if an item was received from the constructor, the Update method will be executed instead of the Insert method:

private void SaveToolbarItem_Clicked(object sender, EventArgs e)
{
    if (selectedNote == null)
    {
        Note note = new Note()
        {
            Title = titleEntry.Text,
            Content = contentEditor.Text
        };

        //! added using SQLite;
        using (SQLiteConnection conn = new SQLiteConnection(App.DatabasePath))
        {
            conn.CreateTable<Note>();
            int itemsInserted = conn.Insert(note);

            if (itemsInserted > 0)
                DisplayAlert("Done", "Note saved", "Ok");
            else
                DisplayAlert("Error", "Note not saved", "Ok");
        }
    }
    else
    {
        selectedNote.Title = titleEntry.Text;
        selectedNote.Content = contentEditor.Text;

        using (SQLiteConnection conn = new SQLiteConnection(App.DatabasePath))
        {
            conn.CreateTable<Note>();
            int itemsUpdated = conn.Insert(selectedNote);

            if (itemsUpdated > 0)
                DisplayAlert("Done", "Note updated", "Ok");
            else
                DisplayAlert("Error", "Note not updated", "Ok");
        }
    }
}

This topic, along with many many others, is covered in greater depth in my 25-hour long "The Complete Xamarin Developer Course: iOS and Android" course, which you can practically steal from me by

Xamarin Forms Maps - iOS

Xamarin Forms Maps - iOS

Local Databases in Xamarin Forms with SQLite

Local Databases in Xamarin Forms with SQLite