Skip to content

Commit ccc520f

Browse files
Update README.md
1 parent c091f48 commit ccc520f

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed

README.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,207 @@
11
# How-to-dynamically-fetch-and-display-items-from-an-API-in-.NET-MAUI-Autocomplete
22
This repository contains a sample explaining how to dynamically fetch and display items from an API in .NET MAUI Autocomplete.
3+
4+
**Step 1: Define the User Interface**
5+
6+
In the `MainPage.xaml`, configure the visual interface by placing a `Autocomplete` control inside an outlined `TextInputLayout`. This setup creates an intuitive search input field with a material design outline. We define a customized `ItemTemplate` to display the contact's name, title, city, and country in a structured format, giving users contextual information while selecting.
7+
8+
> The key customization here is attaching a `CustomFilterBehavior`, which triggers API calls based on user input—making the UI reactive and data-driven.
9+
10+
11+
```
12+
<?xml version="1.0" encoding="utf-8" ?>
13+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
14+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
15+
xmlns:filter="clr-namespace:AutocompleteSample.CustomFilter"
16+
xmlns:vm="clr-namespace:AutocompleteSample.ViewModel"
17+
xmlns:editors="clr-namespace:Syncfusion.Maui.Inputs;assembly=Syncfusion.Maui.Inputs"
18+
xmlns:inputlayout="clr-namespace:Syncfusion.Maui.Core;assembly=Syncfusion.Maui.Core"
19+
x:Class="AutocompleteSample.MainPage">
20+
21+
<ContentPage.BindingContext>
22+
<vm:CustomerViewModel/>
23+
</ContentPage.BindingContext>
24+
25+
<VerticalStackLayout Padding="30,0" Spacing="25">
26+
<inputlayout:SfTextInputLayout ContainerType="Outlined"
27+
ShowHint="False"
28+
ReserveSpaceForAssistiveLabels="False"
29+
Padding="0,-2,0,0"
30+
OutlineCornerRadius="8"
31+
IsHintAlwaysFloated="True"
32+
WidthRequest="360"
33+
HeightRequest="60">
34+
<editors:SfAutocomplete NoResultsFoundText=""
35+
DropDownBackground="WhiteSmoke"
36+
DropDownStroke="Black"
37+
DropDownItemHeight="54"
38+
PlaceholderColor="#283618"
39+
ClearButtonIconColor="#283618"
40+
TextSearchMode="Contains"
41+
Placeholder="Search by Customer Name/Designation/Country"
42+
DisplayMemberPath="ContactName"
43+
ItemsSource="{Binding OrderedItems}">
44+
<editors:SfAutocomplete.FilterBehavior>
45+
<filter:AutocompleteCustomFilter/>
46+
</editors:SfAutocomplete.FilterBehavior>
47+
<editors:SfAutocomplete.ItemTemplate>
48+
<DataTemplate>
49+
<Grid Padding="10" RowDefinitions="Auto,Auto">
50+
<HorizontalStackLayout Grid.Row="0" Spacing="2">
51+
<Label Text="{Binding ContactName}"
52+
FontAttributes="Bold"
53+
VerticalOptions="Center"
54+
FontSize="16"
55+
TextColor="Black"/>
56+
<Label Text=" - " VerticalOptions="Center" FontSize="12"/>
57+
<Label VerticalOptions="Center"
58+
Text="{Binding ContactTitle}"
59+
FontAttributes="Bold"
60+
FontSize="12"
61+
TextColor="Black"/>
62+
<Label Text="&#xe78c;"
63+
VerticalOptions="Center"
64+
FontSize="14"
65+
Padding="4,0"
66+
TextColor="#50D072"
67+
FontFamily="MauiMaterialAssets"/>
68+
</HorizontalStackLayout>
69+
<HorizontalStackLayout Grid.Row="1" Spacing="2">
70+
<Label Text="&#xe71c;"
71+
VerticalOptions="Center"
72+
FontSize="14"
73+
Padding="4,0"
74+
TextColor="Green"
75+
FontFamily="MauiMaterialAssets"/>
76+
<Label Text="{Binding City}"
77+
FontSize="12"
78+
VerticalOptions="Center"/>
79+
<Label Text=" , " VerticalOptions="Center" FontSize="12"/>
80+
<Label VerticalOptions="Center"
81+
Text="{Binding Country}"
82+
FontSize="12"
83+
TextColor="Black"/>
84+
</HorizontalStackLayout>
85+
</Grid>
86+
</DataTemplate>
87+
</editors:SfAutocomplete.ItemTemplate>
88+
</editors:SfAutocomplete>
89+
</inputlayout:SfTextInputLayout>
90+
</VerticalStackLayout>
91+
</ContentPage>
92+
```
93+
94+
**Step 2: Set Up Model and ViewModel**
95+
96+
This step establishes the data structure and binding logic:
97+
98+
* The `Customer` class models the shape of the data retrieved from the API. It includes all the properties returned from the Northwind service (like CustomerID, ContactName, City, Country, etc.).
99+
* The `ODataResponse<T>` generic class represents the structure of the `JSON` returned by the API.
100+
* The `CustomerViewModel` holds the CustomerDetails collection and implements INotifyPropertyChanged to support dynamic data binding.
101+
102+
> This MVVM setup ensures clean separation between UI and data logic while enabling the Autocomplete control to reflect data changes in real-time.
103+
104+
105+
```
106+
// Model class
107+
public class Customer
108+
{
109+
public string CustomerID { get; set; }
110+
public string CompanyName { get; set; }
111+
public string ContactName { get; set; }
112+
public string ContactTitle { get; set; }
113+
public string Address { get; set; }
114+
public string City { get; set; }
115+
public string Region { get; set; }
116+
public string PostalCode { get; set; }
117+
public string Country { get; set; }
118+
public string Phone { get; set; }
119+
public string Fax { get; set; }
120+
}
121+
122+
// OData response structure
123+
public class ODataResponse<T>
124+
{
125+
public List<T> value { get; set; }
126+
}
127+
128+
// ViewModel
129+
public class CustomerViewModel : INotifyPropertyChanged
130+
{
131+
public ObservableCollection<Customer> CustomerDetails { get; set; }
132+
133+
public CustomerViewModel()
134+
{
135+
CustomerDetails = new ObservableCollection<Customer>();
136+
}
137+
138+
public event PropertyChangedEventHandler? PropertyChanged;
139+
protected virtual void OnPropertyChanged(string propertyName)
140+
{
141+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
142+
}
143+
}
144+
```
145+
146+
**Step 3: Implement Custom Filter Behavior**
147+
148+
Here’s where the dynamic fetching logic comes in:
149+
150+
* The `AutocompleteCustomFilter` class implements `IAutocompleteFilterBehavior`, letting you define a custom matching strategy.
151+
* When the user types text, the overridden `GetMatchingItemsAsync` method is invoked. It uses `HttpClient` to asynchronously call the Northwind OData API, applying a startswith filter on the ContactName, ContactTitle, and Country fields.
152+
* The API response is parsed into a list of Customer objects, and only the top 5 matches are returned to avoid overloading the UI.
153+
* Assign the `AutocompleteCustomFilter` to the `FilterBehavior` property of the Autocomplete control in the XAML layout.
154+
155+
> The use of `CancellationTokenSource` ensures only the latest request is processed, avoiding race conditions or unnecessary API calls when users type quickly.
156+
157+
```
158+
public class AutocompleteCustomFilter : IAutocompleteFilterBehavior
159+
{
160+
private CancellationTokenSource? _cts;
161+
162+
public async Task<object?> GetMatchingItemsAsync(SfAutocomplete autocomplete, AutocompleteFilterInfo filterInfo)
163+
{
164+
// Cancel any ongoing request
165+
_cts?.Cancel();
166+
_cts?.Dispose();
167+
_cts = new CancellationTokenSource();
168+
var cancellationToken = _cts.Token;
169+
170+
if (autocomplete != null)
171+
{
172+
var text = filterInfo.Text ?? string.Empty;
173+
using (HttpClient client = new HttpClient())
174+
{
175+
string filter = $"$filter=startswith(ContactName, '{text}') or startswith(ContactTitle, '{text}') or startswith(Country, '{text}')";
176+
string requestUrl = $"https://services.odata.org/V4/Northwind/Northwind.svc/Customers?{filter}&$format=json";
177+
try
178+
{
179+
HttpResponseMessage response = await client.GetAsync(requestUrl, cancellationToken);
180+
response.EnsureSuccessStatusCode();
181+
182+
string jsonResponse = await response.Content.ReadAsStringAsync(cancellationToken);
183+
var odataResponse = JsonSerializer.Deserialize<ODataResponse<Customer>>(jsonResponse);
184+
185+
List<Customer> customers = odataResponse?.value?.Take(5).ToList() ?? new List<Customer>();
186+
return customers;
187+
}
188+
catch (OperationCanceledException)
189+
{
190+
// Request was cancelled, return empty list
191+
return new List<Customer>();
192+
}
193+
catch (Exception ex)
194+
{
195+
// Log or handle exception
196+
return new List<Customer>();
197+
}
198+
}
199+
}
200+
return new List<Customer>();
201+
}
202+
}
203+
```
204+
Output:
205+
![image.png](https://support.syncfusion.com/kb/agent/attachment/article/20441/inline?token=eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQyMTUzIiwib3JnaWQiOiIzIiwiaXNzIjoic3VwcG9ydC5zeW5jZnVzaW9uLmNvbSJ9.b_YAZtjQfJiZIWCUdp8V-5GNBzAZb2wLe3D7n4C14J4)
206+
207+
![Android](https://support.syncfusion.com/kb/agent/attachment/article/20441/inline?token=eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQyMTU2Iiwib3JnaWQiOiIzIiwiaXNzIjoic3VwcG9ydC5zeW5jZnVzaW9uLmNvbSJ9.Wb6-bJsYhy_Dj6Vv_Sa384QZXqRhqN1Pj7lJ6cL9v4g)

0 commit comments

Comments
 (0)