'How to make a checkbox stay checked when i navigate through pages? Xamarin

I have this listview and in the listview I have an ItemTemplate with a DataTemplate and a ViewCell in which I have a checkbox named "box1". I want to make it stay checked when i switch pages, but i can't acess it via name because it is in a DataTemplate and in a ViewCell. I have tried to name all the controls down to the checkbox and get access to it like that, but it does not seem to work...

This is my xaml:

<ListView SeparatorVisibility="None"
                  BackgroundColor="Transparent"
                  VerticalOptions="Center"
                  x:Name="listView"
                  HasUnevenRows="True"
                  >
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="model:Meal"
                              x:Name="mydt"
                              >
                    <ViewCell
                        x:Name="myvc"
                        >

                        <Grid BackgroundColor="Transparent"
                              x:Name="mygrid"
                              >
                            <Frame BackgroundColor="Transparent"
                                   CornerRadius="20"
                                   x:Name="myframe"
                                   >
                                <StackLayout Orientation="Horizontal"
                                             >
                                    <Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
                                    <StackLayout Orientation="Vertical" WidthRequest="300">
                                    <Label VerticalOptions="Start"
                                       FontSize="20"
                                       Text="{Binding Name}"
                                       FontAttributes="Bold"/>
                                    <Label VerticalOptions="Start"
                                       FontSize="15"
                                       Text="{Binding Ingredients}"/>
                                        <StackLayout Orientation="Horizontal">
                                            <Label VerticalOptions="Start"
                                               FontSize="15"
                                               Text="{Binding Calories}"
                                               TextColor="OrangeRed"/>
                                            <Label Text="kcal" 
                                                   FontSize="15"
                                                   TextColor="OrangeRed"/>
                                        </StackLayout>

                                    </StackLayout>
                                    <CheckBox 
                                       
                                        x:Name="box1"
                                          IsChecked="{Binding Checked}"
                                              Color="Green"
                                              Margin="60, 0, 0, 0"
                                              CheckedChanged="box1_CheckedChanged"
                                              BindingContext="{Binding ., Mode=TwoWay}"
                                              />
                                  

                                </StackLayout>
                            </Frame>
                        </Grid>
                            
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

This is my event handler from the Content Page in cs:

    private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
    {           
        var meal = listView.SelectedItem as Meal;
        if (listView.SelectedItem != null)
        {
            if (e.Value == true)
            {
                long cal = long.Parse(meal.Calories);
                calories_consumed = calories_consumed + cal;
                ch = true;
            }
            else
            {
                long cal = long.Parse(meal.Calories);
                calories_consumed = calories_consumed - cal;
                ch = false;
            }
        }
        label_cal.Text = calories_consumed.ToString();
    } 

This is my updated Meal class using INotifyPropertyChanged: public class Meal : INotifyPropertyChanged { [PrimaryKey, AutoIncrement] public int Id { get; set; }

    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    private string ingredients;
    public string Ingredients
    {
        get { return ingredients; }
        set
        {
            ingredients = value;
            OnPropertyChanged(nameof(Ingredients));
        }
    }

    private string calories;
    public string Calories
    {
        get { return calories; }
        set
        {
            calories = value;
            OnPropertyChanged(nameof(Calories));
        }
    }

    private bool isChecked;
    public bool IsChecked
    {
        get
        {
            return isChecked;
        }
        set
        {
            isChecked = value;
            OnPropertyChanged(nameof(IsChecked));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }


}

And this is my code behind for the ContentPage:

protected override async void OnAppearing()
    {
        base.OnAppearing();
        
        listView.ItemsSource = new ObservableCollection<Meal>(await App.Database.GetMealAsync());
    }
    public static bool ch;

Event handler for the checkbox in the code behind:

private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
    {
     
        
        var meal = listView.SelectedItem as Meal;
        if (listView.SelectedItem != null)
        {
            if (e.Value == true)
            {
                long cal = long.Parse(meal.Calories);
                calories_consumed = calories_consumed + cal;
                ch = true;
            }
            else
            {
                long cal = long.Parse(meal.Calories);
                calories_consumed = calories_consumed - cal;
                ch = false;
            }
        }
        meal.IsChecked = ch;
        label_cal.Text = calories_consumed.ToString();
    }


Solution 1:[1]

To be short you can just save the value in Preferences or make a variable in a model that is static and bind that values OnAppearing

Solution 2:[2]

There are mainly two ways that you could store the checkbox's state.

1.You could use Json to Serialize and deserialize the model that has a IsChecked property that binding with the checkbox.For more details, you could refer to this thread.

Code in checkbox_CheckedChanged event:

    private void checkbox_CheckedChanged(object sender, CheckedChangedEventArgs e)
        {
            var checkbox = (CheckBox)sender;
            var selectMeal = checkbox.BindingContext as Meal;
            selectMeal.Checked = e.Value;
            //save the data and checkbox state,you could save the data as a json string
            string json = JsonConvert.SerializeObject(blistView);
            Preferences.Set("listmeals", json);
        }

2.You could store the state of the checkbox using sqlite-net-pcl.Please refer to below MS official docs for more details. And then retrieve the check state of the checkbox via OnAppearing Method.

protected override void OnAppearing()
{
  // retrieve the check state of the checkbox in your sqlite database.
   base.OnAppearing();
   TodoItemDatabase database = await TodoItemDatabase.Instance;
   listView.ItemsSource = await database.GetItemsAsync();
}

MS official docs:https://docs.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases

Solution 3:[3]

The way you are connecting your class your view/xaml is incorrect. If you want to do it without MVVM you can go about it by creating a model that implements INotifyPropertyChanged and an ObservableCollection as listView itemssource.

Model class Meal could look like this:

public class Meal : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    private string ingredients;
    public string Ingredients
    {
        get { return ingredients; }
        set
        {
            ingredients = value;
            OnPropertyChanged(nameof(Ingredients));
        }
    }

    private string calories;
    public string Calories
    {
        get { return calories; }
        set
        {
            calories = value;
            OnPropertyChanged(nameof(Calories));
        }
    }

    private bool isChecked;
    public bool IsChecked
    {
        get { return isChecked; }
        set
        {
            isChecked = value;
            OnPropertyChanged(nameof(IsChecked));
        }
    }

    #region INotify property changed
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

The MainPage.xaml like this:

<StackLayout>
    <ListView SeparatorVisibility="None"
              BackgroundColor="Transparent"
              VerticalOptions="Center"
              x:Name="listView"
              HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="model:Meal" x:Name="mydt" >
                <ViewCell x:Name="myvc" >
                    <Grid BackgroundColor="Transparent"
                          x:Name="mygrid" >
                        <Frame BackgroundColor="Transparent"
                               CornerRadius="20"
                               x:Name="myframe">
                            <StackLayout Orientation="Horizontal">
                                <Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
                                <StackLayout Orientation="Vertical" WidthRequest="300">
                                    <Label VerticalOptions="Start"
                                   FontSize="20"
                                   Text="{Binding Name}"
                                   FontAttributes="Bold"/>
                                    <Label VerticalOptions="Start"
                                   FontSize="15"
                                   Text="{Binding Ingredients}"/>
                                    <StackLayout Orientation="Horizontal">
                                        <Label VerticalOptions="Start"
                                           FontSize="15"
                                           Text="{Binding Calories}"
                                           TextColor="OrangeRed"/>
                                        <Label Text="kcal" FontSize="15" TextColor="OrangeRed"/>
                                    </StackLayout>
                                </StackLayout>
                                <CheckBox x:Name="box1" IsChecked="{Binding IsChecked}" Color="Green" Margin="60, 0, 0, 0" />
                            </StackLayout>
                        </Frame>
                    </Grid>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <Button Clicked="Button_Clicked" Text="Check status items"/>
</StackLayout>

And your MainPage.xaml.cs code behind like this:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        listView.ItemsSource = new ObservableCollection<Meal>(new List<Meal>
        {
            new Meal { Name = "Meal 01", Ingredients = "Ingredients 01", Calories = "250" },
            new Meal { Name = "Meal 02", Ingredients = "Ingredients 02", Calories = "350" },
            new Meal { Name = "Meal 03", Ingredients = "Ingredients 03", Calories = "450" }
        });
    }

    /// <summary>
    /// check items ischecked status
    /// </summary>
    private void Button_Clicked(object sender, EventArgs e)
    {
        foreach (var item in listView.ItemsSource)
        {
            if (item is Meal meal && meal.IsChecked)
                System.Diagnostics.Debug.WriteLine($"{meal.Name} is checked");
        }
    }
}

Look at the other answer for saving the model and/or list. When saved the list can be retrieved in the OnAppearing and set instead of recreated as shown in this example. I've added a button on the bottom that prints out which Meals are selected in the output window. See screenshot:

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Creator Space
Solution 2 Alexandar May - MSFT
Solution 3 Michael