Skip to main content

Overview

The MemberExpand system allows you to selectively fetch only the data you need when retrieving member information. This reduces payload size, improves response times, and minimizes network bandwidth usage.
By default, member fetches return only core fields. Use field expansion to include additional information like contact details, employment, marital status, and discipleship data.

Why Field Expansion Matters

Performance Benefits

  • Reduced Payload Size: Only fetch the data you need, not everything
  • Faster Response Times: Smaller payloads transfer faster over the network
  • Lower Bandwidth: Critical for mobile applications with limited data
  • Better UX: Faster data loading means better user experience

Real-World Impact

// ❌ Without field expansion: ~5KB per member (all fields)
let member = try await congregation.members.fetch(id: memberId)

// ✅ With field expansion: ~1KB per member (only contact info)
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.contactInformation]
)

// For 1000 members:
// Without expansion: ~5MB total
// With expansion: ~1MB total (80% reduction!)

Available Expansion Fields

The MemberExpand enum defines which related information to include:
public enum MemberExpand: String, Codable, Sendable {
    /// Expand employment information
    case employmentInformation
    
    /// Expand contact information
    case contactInformation
    
    /// Expand marital information
    case martialInformation
    
    /// Expand discipleship and spiritual information
    case discipleshipInformation
}

Employment Information

Includes work-related data:
  • Employment status (employed, self-employed, student, etc.)
  • Organization name
  • Occupation category
  • Industry sector
  • Occupation subcategory
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.employmentInformation]
)

if let employment = member.employmentInformation {
    print("Status: \(employment.employmentStatus?.displayName ?? "Unknown")")
    print("Organization: \(employment.nameOfTheOrganization ?? "N/A")")
    print("Occupation: \(employment.occupation?.displayName ?? "N/A")")
    print("Sector: \(employment.sector?.displayName ?? "N/A")")
}

Contact Information

Includes communication details:
  • Phone number
  • Email address
  • Physical address
  • Area/region
  • WhatsApp number
  • Alternate phone number
  • Professional designation
  • Location
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.contactInformation]
)

if let contact = member.contactInformation {
    print("Email: \(contact.email ?? "N/A")")
    print("Phone: \(contact.phoneNumber ?? "N/A")")
    print("Address: \(contact.address ?? "N/A")")
    print("WhatsApp: \(contact.whatsappNumber ?? "N/A")")
}

Marital Information

Includes family-related data:
  • Marital status (single, married, widowed, etc.)
  • Spouse name
  • Wedding anniversary with age calculations
  • Number of children
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.martialInformation]
)

if let marital = member.maritalInformation {
    print("Status: \(marital.maritalStatus?.displayName ?? "Unknown")")
    print("Spouse: \(marital.spouseName ?? "N/A")")
    
    if let anniversary = marital.weddingAnniversaryInfo {
        print("Anniversary: \(anniversary.formattedDate)")
        print("Years married: \(anniversary.yearsMarried)")
    }
    
    if let children = marital.numberOfChildren {
        print("Children: \(children)")
    }
}

Discipleship Information

Includes spiritual journey data:
  • Born again date
  • Water baptism status and date
  • Holy Spirit filling experience
  • Course completion (Prayer, Foundation, Bible)
  • Life transformation camp attendance
  • Ministry involvement and serving status
  • YouTube/WhatsApp subscriptions
  • Missionary involvement
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.discipleshipInformation]
)

if let discipleship = member.discipleshipInformation {
    // Baptism status
    if let baptism = discipleship.waterBaptism {
        print("Baptized: \(baptism.received)")
        if let date = baptism.date {
            print("Baptism date: \(date)")
        }
    }
    
    // Ministry involvement
    if let serving = discipleship.serving {
        print("Serving: \(serving.involved?.displayName ?? "Not serving")")
        print("Department: \(serving.primaryDepartment?.displayName ?? "N/A")")
    }
    
    // Course completion
    if let prayerCourse = discipleship.prayerCourse {
        print("Prayer course: \(prayerCourse.completed ? "Completed" : "Not completed")")
    }
}

Fetching a Single Member with Expansion

Use the fetch(id:expanded:) method to retrieve one member with specific fields:
import CongregationKit
import Congregation

// Fetch member with contact and employment info
let memberId = try MemberID(validating: "TKT123456")

let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.contactInformation, .employmentInformation]
)

// Access expanded fields
print("Name: \(member.memberName ?? "Unknown")")

if let contact = member.contactInformation {
    print("Email: \(contact.email ?? "N/A")")
}

if let employment = member.employmentInformation {
    print("Occupation: \(employment.occupation?.displayName ?? "N/A")")
}
id
MemberID
required
Type-safe member ID (must start with “TKT”)
expanded
[MemberExpand]
required
Array of fields to expand (e.g., [.contactInformation, .employmentInformation])

Fetching Multiple Members with Expansion

Use the fetchAll(pageNumber:pageSize:expanded:) method for paginated results with expansion:
// Fetch 100 members with contact and discipleship info
let response = try await congregation.members.fetchAll(
    pageNumber: 1,
    pageSize: 100,
    expanded: [.contactInformation, .discipleshipInformation]
)

print("Total members: \(response.totalSize)")
print("Fetched: \(response.members.count) members")

for member in response.members {
    // Contact info is available
    if let email = member.contactInformation?.email {
        print("\(member.memberName ?? "Unknown"): \(email)")
    }
    
    // Discipleship info is available
    if let baptized = member.discipleshipInformation?.waterBaptism?.received {
        print("  Baptized: \(baptized)")
    }
}

Combining Multiple Expansions

You can expand multiple field groups in a single request:
// Get comprehensive member profile
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [
        .contactInformation,
        .employmentInformation,
        .martialInformation,
        .discipleshipInformation
    ]
)

// All information is now available
if let contact = member.contactInformation {
    print("Contact: \(contact.email ?? "N/A")")
}

if let employment = member.employmentInformation {
    print("Employment: \(employment.employmentStatus?.displayName ?? "N/A")")
}

if let marital = member.maritalInformation {
    print("Marital: \(marital.maritalStatus?.displayName ?? "N/A")")
}

if let discipleship = member.discipleshipInformation {
    print("Baptized: \(discipleship.waterBaptism?.received ?? false)")
}
Including all expansions increases payload size. Only expand fields you actually need for your use case.

Filtering Expanded Fields Locally

The Member struct includes a withFilteredFields(expanded:) method to filter fields locally:
// Fetch member with all fields
let fullMember = try await congregation.members.fetch(
    id: memberId,
    expanded: [
        .contactInformation,
        .employmentInformation,
        .martialInformation,
        .discipleshipInformation
    ]
)

// Create a filtered copy with only contact info
let filteredMember = fullMember.withFilteredFields(
    expanded: [.contactInformation]
)

// Only contact information is present
assert(filteredMember.contactInformation != nil)
assert(filteredMember.employmentInformation == nil)
assert(filteredMember.maritalInformation == nil)
assert(filteredMember.discipleshipInformation == nil)
This is useful when you need to pass a subset of member data to different parts of your application while maintaining a single source of truth.

Performance Comparison

Here’s a real-world performance comparison:
ScenarioPayload SizeNetwork TimeUse Case
No expansion (core only)~500 bytes~50msMember lists, search results
Contact info only~1KB~75msContact management, email campaigns
Employment only~800 bytes~60msDemographics, reports
All fields expanded~5KB~200msDetailed member profiles

Best Practices

1. Only Expand What You Need

// ✅ Good: Only expand fields you'll use
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [.contactInformation]  // Just need email/phone
)

// ❌ Bad: Expanding everything when you only need contact info
let member = try await congregation.members.fetch(
    id: memberId,
    expanded: [
        .contactInformation,
        .employmentInformation,
        .martialInformation,
        .discipleshipInformation
    ]
)

2. Use Different Expansions for Different Views

// List view: No expansion (fast loading)
let listResponse = try await congregation.members.fetchAll(
    pageNumber: 1,
    pageSize: 50,
    expanded: []  // Core fields only
)

// Detail view: Full expansion (complete profile)
let detailMember = try await congregation.members.fetch(
    id: selectedMemberId,
    expanded: [
        .contactInformation,
        .employmentInformation,
        .martialInformation,
        .discipleshipInformation
    ]
)

3. Consider Mobile Data Usage

For mobile apps, minimize data usage:
// Check network connection type
if isOnCellularData {
    // Fetch minimal data
    let member = try await congregation.members.fetch(
        id: memberId,
        expanded: [.contactInformation]  // Only essential fields
    )
} else {
    // On WiFi: fetch everything
    let member = try await congregation.members.fetch(
        id: memberId,
        expanded: [
            .contactInformation,
            .employmentInformation,
            .martialInformation,
            .discipleshipInformation
        ]
    )
}

4. Cache Expanded Data

Cache fully expanded member data to avoid repeated fetches:
actor MemberCache {
    private var cache: [String: Member] = [:]
    
    func getMember(id: MemberID, congregation: CongregationKit) async throws -> Member {
        if let cached = cache[id.rawValue] {
            return cached
        }
        
        // Fetch with full expansion and cache
        let member = try await congregation.members.fetch(
            id: id,
            expanded: [
                .contactInformation,
                .employmentInformation,
                .martialInformation,
                .discipleshipInformation
            ]
        )
        
        cache[id.rawValue] = member
        return member
    }
}

Next Steps

Error Handling

Learn how to handle errors when fetching member data

Fetching Members

Complete guide to retrieving member data

Build docs developers (and LLMs) love