Search code examples
xamarinmvvmviewmodel

View Page label not refreshing with MVVM command in xamarin


I have a page in an e-commerce app for the cart. I have my orders total price as a normal label on the xamarin view page outside of the data template in the xaml code as shown below. I have a button on the viewmodel page which handles the delete command of the products from the cart. I want the total order and the points label to update when a item is deleted from the cart.

Here is my code that I'm using

XAML

`<Button ImageSource="bin.png"
                                        x:Name="btnDelete"
                                        Command="{Binding Source={x:Reference cartCollectionView}, Path=BindingContext.CartsCommand}" 
                                        CommandParameter="{Binding .}"
                                        Clicked="btnDelete_Clicked"
                                        BackgroundColor="Transparent"
                                        HorizontalOptions="EndAndExpand"/>

                            </StackLayout>
                        </StackLayout>
                    </Frame>
                </StackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
    <StackLayout Orientation="Horizontal">
        <Label Text="Order #:"
               FontSize="20"
               TextColor="Black"/>
        <Label x:Name="txtOrderNumber"
              FontSize="20" 
               TextColor="Black"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal">
        <Label Text="Total Woof Bits earned:" FontSize="Title" Margin="0,0,0,0" TextColor="Black"/>
        <Label x:Name="txtPoints" Text="{Binding strPoints}" FontSize="Title" Margin="0,0,0,0" TextColor="Black"/>
    </StackLayout>

    <StackLayout Orientation="Horizontal">
        <Label Text="Total Order:" FontSize="Title" TextColor="Black"/>
        <Label  x:Name="txtTotalOrder" Text="{Binding strTotalOrder}" FontSize="Title" TextColor="Black"/>
    </StackLayout>`

View

`public CartPage(CartViewModel viewModels)
    {
        InitializeComponent();
        BindingContext = viewModels;
        BindingContext = new CartViewModel();
        ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
        ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);

        txtOrderNumber.Text = SigninPage.strOrderNumber;
        txtPoints.Text = CartViewModel.strPoints;
        txtTotalOrder.Text = CartViewModel.strTotalOrder;
        
        
    }

    

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


        ((CartViewModel)BindingContext).LoadCartsCommand.Execute(null);
        ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);
        txtPoints.Text = CartViewModel.strPoints;
        txtTotalOrder.Text = CartViewModel.strTotalOrder;
        
    }
    
    


    private void Button_Clicked(object sender, EventArgs e)
    {
        
        ((CartViewModel)BindingContext).PayCommand.Execute(null);
        
    }

    private void btnDelete_Clicked(object sender, EventArgs e)
    {
        ((CartViewModel)BindingContext).LoadTotalsCommand.Execute(null);

        txtPoints.Text = CartViewModel.strPoints;
        txtTotalOrder.Text = CartViewModel.strTotalOrder;
        DisplayAlert("Successful", "Item removed from Cart (▼´•̥ᴥ•̥`) ", "OK");
    }`

ViewModel

`private ICommand _loadTotalsCommand;


    public ICommand LoadTotalsCommand
    {
        get
        {
            if (_loadTotalsCommand == null)
            {
                _loadTotalsCommand = new Command(async () => await LoadTotals());
                OnPropertyChanged();
            }

            return _loadTotalsCommand;
        }
    }

    public async Task LoadTotals()
    {
        string sqlstring = "MySQL string";
        MySqlConnection mysqlcon = new MySqlConnection(sqlstring);
        MySqlCommand mysqlcom;
        MySqlDataReader mdr;

        mysqlcon.Open();

        string selectquery = "SELECT ordernumber, quantity, price, sum(price * quantity) AS Total, sum(pointvalue) AS Total1 FROM cart WHERE ordernumber = '" + SigninPage.strOrderNumber + "' GROUP BY ordernumber;";
        mysqlcom = new MySqlCommand(selectquery, mysqlcon);

        mdr = mysqlcom.ExecuteReader();

        while (mdr.Read())
            if (mdr.FieldCount >= 1)
            {
                TotalOrder = mdr.GetDecimal(3);
                Points = mdr.GetDecimal(4);
            }



        mysqlcon.Close();
        strTotalOrder = "R" + Convert.ToString(TotalOrder);
        strPoints = "🐾" + Convert.ToString(Points);

    }



    private ICommand _cartCommand;

    public ICommand CartsCommand
    {
        get
        {
            if (_cartCommand == null)
            {
                _cartCommand = new Command<Cart>(async (cart) =>
                {
                    await OnCartSelected(cart);
                });
            }

            return _cartCommand;
        }
    }

    



    public async Task OnCartSelected(Cart cart)
    {


        // Navigate to the ProductDetailsPage and pass the selected product as a parameter
        string constring5 = "MySQL string";
        MySqlConnection con5 = new MySqlConnection(constring5);
        con5.Open();
        MySqlTransaction transaction5 = con5.BeginTransaction();

        using (MySqlCommand cmd5 = new MySqlCommand("DELETE FROM cart WHERE ordernumber = '" + strOrderNumber4 + "'", con5))
        {
            cmd5.Transaction = transaction5;

            cmd5.ExecuteNonQuery();
            transaction5.Commit();
            con5.Close();
        }

       
        LoadTotalsCommand.Execute(null);
        LoadCartsCommand.Execute(null);
        


    }`

Solution

  • To make a conclusion,

    1. Use a proper mvvm way. You may refer to From Data Bindings to MVVM. Bind to a property in ViewModel and raise PropertyChanged event :

        private string _strPoints;
        public string strPoints
        {
            get
            {
                return _strPoints;
            }
            set
            {
                _strPoints = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(strPoints)));
            }
        }
    

    When strPoints value changed, the UI would refresh.

    2. For TabbedPage passing arguments into constructor, you may refer to this: Passing Arguments in XAML

    <NavigationPage Title="Cart" IconImageSource="cart.png">
        <x:Arguments>
            <local:CartPage Title="Cart">
                <x:Arguments>
                    <viewModels:CartViewModel/>
                </x:Arguments>
            </local:CartPage>
        </x:Arguments>
    </NavigationPage>
    

    That generate a CartViewModel instance and pass it to the Constructor.

    So for CartPage.cs, don't have to create any new CartViewModel instance any more.

        CartViewModel ViewModel;
    
        public CartPage(CartViewModel viewModels)
        {
            InitializeComponent();           
            BindingContext = viewModels;           
            ViewModel = viewModels;
        }
    

    3. A way to change the color of RadioButton for Android is to use Custom Renderer

    Step1.Create your own custom RadioButton in shared project:

    public class MyRadioButton : RadioButton
    {
        public MyRadioButton()
        {
        }
    }
    

    Step2.Then in YourProject.Android folder create MyRadioButtonRenderer.cs

    [assembly: ExportRenderer(typeof(MyRadioButton), typeof(MyRadioButtonRenderer))]
    namespace ViewPageLabelNotRefreshing76512008.Droid
    {
        public class MyRadioButtonRenderer : RadioButtonRenderer
        {
            public MyRadioButtonRenderer(Context context) : base(context)
            {
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<RadioButton> e)
            {
                base.OnElementChanged(e);
    
                if (Control != null)
                {
                    Control.ButtonTintList = ColorStateList.ValueOf(Android.Graphics.Color.ForestGreen);
                }
            }
        }
    }
    

    Step3.Consume it in xaml,

    <local:MyRadioButton x:Name="Delivery" Content="Delivery" CheckedChanged="RadioButton_CheckedChanged"/>
    

    Hope it helps!