Table of Contents

IFeed<T> Interface

Base interface for all reactive feeds in DevBitsLab.Feeds.

Namespace: DevBitsLab.Feeds
Assembly: DevBitsLab.Feeds.dll

public interface IFeed<out T>

Overview

IFeed<T> defines the contract for reactive data sources with:

  • State tracking — Loading, HasValue, HasError states
  • Event notifications — StateChanged, PausedChanged
  • Flow control — Pause/Resume updates

Implementations include Feed<T>, State<T>, and ListFeed<T>.


Properties

Property Type Description
State FeedState Current state as flags
IsLoading bool Currently fetching data
HasValue bool Data successfully loaded
HasError bool Error occurred
Value T? Current value (null if not loaded)
Error Exception? Exception details (null if no error)
IsPaused bool Updates are paused

Events

StateChanged

Fires when the feed transitions between states.

event EventHandler<FeedStateChangedEventArgs>? StateChanged;

Example:

feed.StateChanged += (sender, e) => {
    Console.WriteLine($"{e.OldState} → {e.NewState}");
    
    // Detect load completion
    if (e.OldState.HasFlag(FeedState.Loading) && 
        e.NewState == FeedState.HasValue) {
        OnLoadCompleted();
    }
};

PausedChanged

Fires when pause state changes.

event EventHandler<bool>? PausedChanged;

Methods

RefreshAsync

Loads or reloads data from the source.

Task RefreshAsync();

Behavior:

  • Cancels any in-progress load
  • Sets IsLoading = true
  • Preserves existing value during refresh (state = Refreshing)

Example:

async Task OnPullToRefreshAsync()
{
    await customerFeed.RefreshAsync();
}

Pause / Resume / TogglePause

Controls whether the feed accepts updates.

void Pause();
void Resume();
bool TogglePause(); // Returns new IsPaused state

Use Case: Prevent background updates during user editing.

void BeginEdit() {
    customerFeed.Pause();
    EnableForm();
}

void EndEdit() {
    customerFeed.Resume();
}

State Transitions

           ┌─────────┐
    start→ │  None   │
           └────┬────┘
                │ RefreshAsync()
                ▼
           ┌─────────┐        ┌──────────┐
           │ Loading │───────→│ HasValue │
           └────┬────┘ success└────┬─────┘
                │                  │ RefreshAsync()
                │ error            ▼
                └──────────┐
                           │Refreshing │ (Loading|HasValue)
                           └───────────┘

Checking States

// Simple checks
if (feed.IsLoading) ShowSpinner();
if (feed.HasValue) Display(feed.Value!);
if (feed.HasError) ShowError(feed.Error!);

// Combined state checks
if (feed.IsLoading && !feed.HasValue) {
    // Initial load - full page spinner
}
if (feed.IsLoading && feed.HasValue) {
    // Refreshing - subtle indicator
}

// Using State flags
if (feed.State == FeedState.Refreshing) {
    ShowRefreshOverlay();
}

Best Practices

1. Always Check HasValue Before Accessing Value

// ✅ Good
if (feed.HasValue && feed.Value is { } customer) {
    DisplayCustomer(customer);
}

// ❌ Risky - Value may be null
var name = feed.Value!.Name;

2. Handle All States in UI

feed.StateChanged += (s, e) => {
    loadingPanel.Visible = feed.IsLoading;
    contentPanel.Visible = feed.HasValue && !feed.IsLoading;
    errorPanel.Visible = feed.HasError && !feed.IsLoading;
};

3. Dispose Event Subscriptions

public class MyViewModel : IDisposable {
    private readonly Feed<Data> _feed;
    
    public MyViewModel() {
        _feed = Feed<Data>.Create(LoadAsync);
        _feed.StateChanged += OnStateChanged;
    }
    
    public void Dispose() {
        _feed.StateChanged -= OnStateChanged;
        _feed.Dispose();
    }
}

Thread Safety

All IFeed<T> implementations in DevBitsLab.Feeds are thread-safe:

Thread Safety Guarantees

Operation Thread-Safe
Property access (Value, State, IsLoading, etc.) ✅ Yes
RefreshAsync ✅ Yes
Pause / Resume / TogglePause ✅ Yes
Event subscription/unsubscription ✅ Yes

Safe Concurrent Access

// Access from multiple threads is safe
await Task.WhenAll(
    Task.Run(() => Console.WriteLine(feed.Value)),
    Task.Run(() => feed.RefreshAsync()),
    Task.Run(() => feed.Pause())
);

Event Handler Threading

Important: Event handlers (StateChanged, PausedChanged) execute on the thread that triggered the state change. When updating UI controls, marshal to the UI thread:

feed.StateChanged += (s, e) => {
    // WPF
    Application.Current.Dispatcher.Invoke(() => {
        loadingSpinner.Visibility = feed.IsLoading 
            ? Visibility.Visible 
            : Visibility.Collapsed;
    });
    
    // WinUI 3 / MAUI
    DispatcherQueue.TryEnqueue(() => {
        LoadingSpinner.IsActive = feed.IsLoading;
    });
};

See Also