Xamarin Forms MVVM架构下ViewModel与API调用及ListView绑定问题排查
Fixing JSON Parsing, Data Binding, and ListView Population in Xamarin Forms MVVM
Hey there, let's break down the issues in your current implementation and fix them one by one to get your product list working correctly.
Key Issues in Your Current Code
- Model Mismatch: Your
CarouselModelandCarouselDataclasses don't match the structure of the JSON response you shared. The JSON'sdataobject containsenableNextandproducts, not a list ofCarouselDataitems. - Deadlock Risk from Sync Async Call: Using
.Resultin the ViewModel constructor can cause deadlocks in Xamarin apps. Always use async/await properly. - Uninitialized Collection: Your
Itemslist isn't initialized, so callingItems.Add()will throw aNullReferenceException. - No ViewModel Binding: Your page doesn't set the
BindingContextto the ViewModel, so the ListView can't access the data. - Hardcoded ListView Template: The
TextCelluses static text instead of binding to your product properties.
Corrected Implementation
Step 1: Update Model Classes to Match JSON Structure
First, let's fix the models to align with the JSON response you shared:
using Newtonsoft.Json; namespace CameliaMaison.Models { public class ApiResponse { [JsonProperty("status")] public long Status { get; set; } [JsonProperty("message")] public string Message { get; set; } [JsonProperty("data")] public ProductData Data { get; set; } } public class ProductData { [JsonProperty("enableNext")] public bool EnableNext { get; set; } [JsonProperty("products")] public List<Product> Products { get; set; } } public class Product { [JsonProperty("image")] public string Image { get; set; } [JsonProperty("id")] public long Id { get; set; } [JsonProperty("code")] public string Code { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("description")] public string Description { get; set; } [JsonProperty("tags")] public string Tags { get; set; } [JsonProperty("price")] public string Price { get; set; } [JsonProperty("quantity")] public long Quantity { get; set; } [JsonProperty("images")] public List<string> Images { get; set; } } }
Step 2: Fix ViewModel with Proper Async Handling and ObservableCollection
Use ObservableCollection instead of List so the UI automatically updates when data changes. Also, avoid blocking calls in the constructor:
using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows.Input; using Xamarin.Forms; using CameliaMaison.Models; using Newtonsoft.Json; namespace CameliaMaison.ViewModels { public class ProductsListViewModel : BaseViewModel { private ObservableCollection<Product> _products; public ObservableCollection<Product> Products { get => _products; set { _products = value; OnPropertyChanged(); } } public ICommand LoadProductsCommand { get; } public ProductsListViewModel() { _products = new ObservableCollection<Product>(); LoadProductsCommand = new Command(async () => await LoadProductsAsync()); } private async Task LoadProductsAsync() { try { var apiResponse = await MyHTTP.GetProductsAsync(); if (apiResponse?.Data?.Products != null) { Products.Clear(); foreach (var product in apiResponse.Data.Products) { Products.Add(product); } } } catch (Exception ex) { // Add error handling here, e.g., show an alert to the user await Application.Current.MainPage.DisplayAlert("Error", $"Failed to load products: {ex.Message}", "OK"); } } } public static class MyHTTP { public static async Task<ApiResponse> GetProductsAsync() { using (var httpClient = new HttpClient()) { var response = await httpClient.GetAsync("http://bresa.lazyhost.in/api/banners"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<ApiResponse>(content); } } } // Base ViewModel to handle property change notifications public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
Step 3: Update Page to Bind ViewModel and ListView
Update the XAML code to bind the ViewModel and display product data:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CameliaMaison.ViewModels" x:Class="CameliaMaison.Views.ProductsListPage" Title="Product List"> <ContentPage.BindingContext> <vm:ProductsListViewModel /> </ContentPage.BindingContext> <ListView ItemsSource="{Binding Products}" IsPullToRefreshEnabled="True" RefreshCommand="{Binding LoadProductsCommand}" Padding="5"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" Padding="10" Spacing="8"> <Image Source="{Binding Image}" WidthRequest="80" HeightRequest="80" Aspect="AspectFill" /> <StackLayout VerticalOptions="Center"> <Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" /> <Label Text="{Binding Price}" FontSize="Small" TextColor="#2ECC71" /> <Label Text="{Binding Description}" FontSize="Small" LineBreakMode="TailTruncation" TextColor="#666" /> </StackLayout> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
Update the code-behind to trigger data loading when the page appears:
using Xamarin.Forms; using CameliaMaison.ViewModels; namespace CameliaMaison.Views { public partial class ProductsListPage : ContentPage { private ProductsListViewModel _viewModel; public ProductsListPage() { InitializeComponent(); _viewModel = (ProductsListViewModel)BindingContext; } protected override async void OnAppearing() { base.OnAppearing(); await _viewModel.LoadProductsAsync(); } } }
Additional Notes
- Error Handling: We added a try/catch block in
LoadProductsAsyncto handle network issues gracefully—you can expand this to suit your app's needs. - HttpClient Disposal: Using
usingwithHttpClientensures resources are properly cleaned up. - ObservableCollection: This collection type automatically notifies the UI of changes, which is critical for MVVM data binding.
- Endpoint Check: Double-check that your API endpoint (
http://bresa.lazyhost.in/api/banners) actually returns the product JSON you shared. If it's meant for banners, you'll need to switch to the correct product endpoint.
内容的提问来源于stack exchange,提问作者govind kumar
相关产品推荐
相关产品推荐

