---
name: shiny-contactstore
description: Generate code using Shiny.Maui.ContactStore for cross-platform device contact access with CRUD, LINQ queries, and MAUI permissions
triggers:
- "contact store"
- "contacts"
- "IContactStore"
- "ContactStore"
- "AddContactStore"
- "ContactPermission"
- "ContactReadPermission"
- "ContactWritePermission"
- "Shiny.Maui.ContactStore"
- "device contacts"
- "read contacts"
- "write contacts"
- "contact query"
- "contact LINQ"
---
# Shiny.Maui.ContactStore Skill
## Triggers
- contact store
- contacts
- IContactStore
- ContactStore
- AddContactStore
- ContactPermission
- ContactReadPermission
- ContactWritePermission
- Shiny.Maui.ContactStore
- device contacts
- read contacts
- write contacts
- contact query
- contact LINQ
You are an expert in Shiny.Maui.ContactStore, a cross-platform .NET MAUI library for accessing device contacts on Android and iOS.
## When to Use This Skill
Invoke this skill when the user wants to:
- Access device contacts (read, create, update, delete)
- Query contacts with LINQ
- Set up contact permissions using MAUI permissions
- Register the contact store in DI
- Work with contact models (phones, emails, addresses, etc.)
## Library Overview
**GitHub**: https://github.com/shinyorg/contactstore
**NuGet**: `Shiny.Maui.ContactStore`
**Namespace**: `Shiny.Maui.ContactStore`
Shiny.Maui.ContactStore provides:
- Full CRUD operations on device contacts
- LINQ query support with native translation (Android content provider queries, iOS CNContact predicates)
- MAUI permission classes for requesting contact access
- Dependency injection integration
- AOT and trimmer compatible
## Setup
### 1. Install NuGet Package
```bash
dotnet add package Shiny.Maui.ContactStore
```
### 2. Register in MauiProgram.cs
```csharp
using Shiny.Maui.ContactStore;
builder.Services.AddContactStore();
```
### 3. Platform Permissions
**Android** — Add to `AndroidManifest.xml`:
```xml
```
**iOS** — Add to `Info.plist`:
```xml
NSContactsUsageDescription
This app needs access to your contacts.
```
## Permissions
The library provides a MAUI permission class `ContactPermission` that wraps both read and write contact permissions.
Use the extension methods on `IContactStore`:
```csharp
// Request permissions (triggers OS prompt if needed)
var status = await contactStore.RequestPermssionsAsync();
if (status != PermissionStatus.Granted)
{
// Handle denied
return;
}
// Check current status without prompting
var status = await contactStore.CheckPermissionStatusAsync();
```
### Android Permission Results
- `PermissionStatus.Granted` — both read and write access granted
- `PermissionStatus.Limited` — only read or only write granted (not both)
- `PermissionStatus.Denied` — neither read nor write granted
### iOS Permission Results
- `PermissionStatus.Granted` — contacts access authorized
- `PermissionStatus.Denied` — contacts access denied
- `PermissionStatus.Restricted` — contacts access restricted
## API Reference
### IContactStore Interface
```csharp
public interface IContactStore
{
Task> GetAll(CancellationToken ct = default);
Task GetById(string contactId, CancellationToken ct = default);
IQueryable Query();
Task Create(Contact contact, CancellationToken ct = default);
Task Update(Contact contact, CancellationToken ct = default);
Task Delete(string contactId, CancellationToken ct = default);
}
```
### Extension Methods
```csharp
// Permission extensions
Task contactStore.RequestPermssionsAsync();
Task contactStore.CheckPermissionStatusAsync();
// Query extensions
Task> contactStore.GetFamilyNameFirstLetters(CancellationToken ct = default);
```
### Query with LINQ
The library translates LINQ predicates to native queries where possible, with in-memory fallback.
```csharp
// Filter by name
var results = contactStore.Query()
.Where(c => c.GivenName.Contains("John"))
.ToList();
// Filter by phone number
var results = contactStore.Query()
.Where(c => c.Phones.Any(p => p.Number.Contains("555")))
.ToList();
// Filter by email
var results = contactStore.Query()
.Where(c => c.Emails.Any(e => e.Address.Contains("@example.com")))
.ToList();
// Combine filters
var results = contactStore.Query()
.Where(c => c.GivenName.StartsWith("J") && c.FamilyName.Contains("Smith"))
.ToList();
// Paging
var page = contactStore.Query()
.Where(c => c.FamilyName.StartsWith("A"))
.Skip(10)
.Take(20)
.ToList();
```
**Supported operations:** `Contains`, `StartsWith`, `EndsWith`, `Equals`
**Filterable properties:** `GivenName`, `FamilyName`, `MiddleName`, `NamePrefix`, `NameSuffix`, `Nickname`, `DisplayName`, `Note`
**Filterable collections:** `Phones` (by `Number`), `Emails` (by `Address`)
### Create a Contact
```csharp
var contact = new Contact
{
GivenName = "John",
FamilyName = "Doe",
Note = "Met at conference"
};
contact.Phones.Add(new ContactPhone("555-1234", PhoneType.Mobile));
contact.Emails.Add(new ContactEmail("john@example.com", EmailType.Work));
string id = await contactStore.Create(contact);
```
### Update a Contact
```csharp
var contact = await contactStore.GetById(id);
contact.GivenName = "Jane";
await contactStore.Update(contact);
```
### Delete a Contact
```csharp
await contactStore.Delete(contactId);
```
## Models
### Contact
| Property | Type |
|----------------|-----------------------------|
| Id | `string?` |
| NamePrefix | `string?` |
| GivenName | `string?` |
| MiddleName | `string?` |
| FamilyName | `string?` |
| NameSuffix | `string?` |
| Nickname | `string?` |
| DisplayName | `string` |
| Note | `string?` |
| Organization | `ContactOrganization?` |
| Photo | `byte[]?` |
| Thumbnail | `byte[]?` |
| Phones | `List` |
| Emails | `List` |
| Addresses | `List` |
| Dates | `List` |
| Relationships | `List` |
| Websites | `List` |
### Enums
**PhoneType:** Home, Mobile, Work, FaxWork, FaxHome, Pager, Other, Custom
**EmailType:** Home, Work, Other, Custom
**AddressType:** Home, Work, Other, Custom
**ContactDateType:** Birthday, Anniversary, Other, Custom
**RelationshipType:** Father, Mother, Parent, Brother, Sister, Child, Friend, Spouse, Partner, Assistant, Manager, Other, Custom
## iOS Notes & Relations Entitlement
Reading `Note` and `Relationships` on iOS requires the `com.apple.developer.contacts.notes` entitlement. The library auto-detects this at runtime. If absent, `Note` returns `null` and `Relationships` is empty.
## Best Practices
1. **Always request permissions first** — use `contactStore.RequestPermssionsAsync()` before any CRUD operation
2. **Use LINQ queries for filtering** — prefer `Query().Where(...)` over `GetAll()` when filtering, as it uses native queries
3. **Check for Limited on Android** — `PermissionStatus.Limited` means partial access (read-only or write-only)
4. **Handle iOS entitlements gracefully** — Notes and Relations silently return empty without the entitlement
5. **Use primary constructors** — inject `IContactStore` via primary constructor