ReactiveProperty

repository·main·Indexed 21 days ago

https://github.com/runceel/reactiveproperty

A library that brings MVVM and asynchronous support to .NET applications using Reactive Extensions (Rx). It enables declarative ViewModel definitions by bridging standard object properties and IObservable<T> streams, replacing manual property change notifications and command logic. Key features include two-way synchronization with INotifyPropertyChanged, LINQ integration for property streams, and ReactiveCommands driven by IObservable<bool>. Supports .NET 8.0+, .NET Framework 4.7.2, and .NET Standard 2.0.

Tokens
63.7K
Snippets
164
Records
209
Agent score
75%

What's inside ReactiveProperty

  1. What is ReactiveProperty?

    main
    ReactiveProperty is an extension of Reactive Extensions (Rx) designed specifically for the MVVM (Model-View-ViewModel) pattern and asynchronous programming. It provides reactive properties that can be easily integrated into ViewModels to facilitate data binding and reactive state management.
  2. What is ReactiveProperty

    main

    ReactiveProperty is a library designed to support MVVM and asynchronous processing for Reactive Extensions (Rx). It targets .NET Standard 2.0 and allows for declarative programming by bridging the gap between standard object properties and IObservable<T> streams.

    Key capabilities include:

    • Two-way synchronization: Synchronize ReactiveProperty<T> with standard INotifyPropertyChanged model properties.
    • LINQ integration: Use Rx operators (like Where, Select, Delay) to transform property streams.
    • Reactive Commands: Create ICommand implementations (ReactiveCommand) that are driven by IObservable<bool> streams (e.g., enabling a command only when a property meets a certain condition).
    • Declarative Pipelines: Chain operations from an input ReactiveProperty to an output ReactiveProperty or ReactiveCommand using method chaining.
    // Example: Creating a reactive pipeline from an input property to an output property
    class ViewModel
    {
        public ReactiveProperty<string> Input { get; }
        public ReactiveProperty<string> Output { get; }
    
        public ViewModel()
        {
            Input = new ReactiveProperty("");
            Output = Input
                .Delay(TimeSpan.FromSeconds(1))
                .Select(x => x.ToUpper())
                .ToReactiveProperty(); 
        }
    }
  3. Overview of ReactiveProperty

    main

    ReactiveProperty is a library providing MVVM and asynchronous support features using Reactive Extensions (Rx). It is designed to simplify ViewModel implementation by using declarative code instead of manual property change notifications and command state management.

    Key Characteristics:

    • No Base Classes: Unlike many MVVM frameworks, ReactiveProperty does not require your ViewModels to inherit from a specific base class, making it compatible with other libraries like Prism or Microsoft.Toolkit.Mvvm.
    • Core Abstractions: The core classes like ReactiveProperty[Slim] wrap a value and implement both IObservable<T> (for Rx LINQ chains) and INotifyPropertyChanged (for data binding in WPF, WinUI, and MAUI).
    • Functional Approach: It encourages a functional programming style where properties are transformed through Rx operators.

    Note for New Projects: If you are starting a new application, the author recommends using R3 instead of ReactiveProperty, as R3 is a redesigned version aligned with the current .NET ecosystem.

  4. Use ReactiveProperty.XamarinAndroid for MVVM

    main
    ReactiveProperty.XamarinAndroid is an MVVM support library designed for use with ReactiveProperty in Xamarin.Android environments. It provides data binding capabilities between View properties and ReactiveProperties, collection adapters for Android lists, and extension methods to convert Android View events into Observables.
  5. What is ReactiveProperty.R3 and when to use it

    main

    ReactiveProperty.R3 is a bridge package designed to fill the functional gaps between the original ReactiveProperty and the R3 ecosystem (including ObservableCollections.R3).

    When to use it

    • During Migration: Use it to provide types that R3 lacks but your existing code requires.
    • Post-Migration: You may continue to depend on ReactiveProperty.R3 indefinitely to access specific features that have not yet been implemented in native R3.

    Design Principles

    • Gap-only: It does not duplicate R3 functionality. If R3 provides a feature (even under a different name), the migration skill will map it to native R3 instead of using this library.
    • R3-native shapes: Public surfaces return R3 Observable<T> (not System.IObservable<T>), use TimeProvider for time-based features, and use R3 ReactiveProperty<bool> for shared command state.
    • Minimal dependencies: It depends only on R3. It does not require System.Reactive or MessagePipe.
  6. What is ReactiveProperty and its core features

    main

    The ReactiveProperty<T> class is the core component of this library. It is designed to bridge the gap between Reactive Extensions (Rx) and XAML-based data binding.

    Key features include:

    • Implements INotifyPropertyChanged: The Value property triggers PropertyChanged events, making it compatible with XAML data binding.
    • Implements IObservable<T>: It functions as an observable stream, calling the OnNext method whenever the value is updated.

    Important distinction:

    • OnNext (via Subscribe) is called immediately upon subscription with the current value.
    • PropertyChanged is only triggered when the value is actually changed via the Value property and is intended for UI data binding. For logic, you should generally prefer Reactive Extensions methods over PropertyChanged handlers.
    using Reactive.Bindings;
    using System;
    
    // Create a ReactiveProperty
    var name = new ReactiveProperty<string>();
    
    // Use PropertyChanged for UI/Binding logic
    name.PropertyChanged += (_, e) => Console.WriteLine($"PropertyChanged: {e.PropertyName}");
    
    // Use Subscribe for Rx-based logic
    name.Subscribe(x => Console.WriteLine($"OnNext: {x}"));
    
    // Updating the value triggers both
    name.Value = "neuecc";
  7. What is ReactiveCommand and how to use it

    main

    The ReactiveCommand class is used for commanding in UI applications. It implements both the ICommand interface (for UI binding) and the IObservable<T> interface (allowing it to be treated as a stream of execution events).

    Key Behaviors:

    • Execution Logic: When Execute() is called, the command triggers its OnNext callback. You register this logic using .Subscribe().
    • CanExecute Control: You can control whether a command is enabled or disabled by providing an IObservable<bool> source. When the source publishes a value, the CanExecuteChanged event is raised.
    • Always Executable: If you need a command that is never disabled, use the default constructor.
    // Create a command that is always executable
    var alwaysExecutableCommand = new ReactiveCommand();
    
    // Create a command with a parameter that is always executable
    var alwaysExecutableAndHasCommandParameterCommand = new ReactiveCommand<string>();
    
    // Register execution logic
    alwaysExecutableCommand.Subscribe(_ => { /* logic */ });
    
    // Trigger execution
    alwaysExecutableCommand.Execute();
  8. Manage threading for ReactiveCommand and AsyncReactiveCommand

    main

    Controlling which thread handles CanExecute events is essential for UI stability.

    • For ReactiveCommand: The CanExecute event is raised on a scheduler (defaulting to the UI thread). To change this, use the ToReactiveCommand overload that accepts an IScheduler: canExecuteSource.ToReactiveCommand(theSchedulerInstanceYouWant);

    • For AsyncReactiveCommand: This class does not change threads automatically. To specify a thread, use the ObserveOn method on the source before converting to a command: canExecuteSource.ObserveOn(theSchedulerInstanceYouWant).ToAsyncReactiveCommand();

    • For ReactiveCommandSlim: This is a lightweight version where CanExecuteChanged is not automatically dispatched on the UI thread. If you need UI thread dispatching, you must explicitly use ObserveOn on the IObservable<bool> source.

  9. Use `ReadOnlyReactiveProperty` for read-only streams

    main

    When a property should only be observed and never manually updated (e.g., a derived value from another stream), use the ReadOnlyReactiveProperty<T> class. This prevents external callers from setting the Value property. You can create one using the ToReadOnlyReactiveProperty() extension method on an IObservable<T>.

    public class ViewModel
    {
        public ReactiveProperty<string> Input { get; }
        public ReadOnlyReactiveProperty<string> Output { get; }
    
        public ViewModel()
        {
            Input = new ReactiveProperty<string>("");
            Output = Input
                .Delay(TimeSpan.FromSeconds(1))
                .Select(x => x.ToUpper())
                .ToReadOnlyReactiveProperty(); // Converts IObservable to ReadOnlyReactiveProperty
        }
    }
  10. Handle UI thread dispatching with ReactivePropertySlim

    main

    ReactivePropertySlim<T> does not automatically dispatch updates to the UI thread. If you are working with UI frameworks that require updates on a specific thread, you must either use the standard ReactiveProperty<T> (which handles dispatching) or explicitly dispatch the observable stream using .ObserveOnUIDispatcher() before converting it to a slim property.

    var rp = Observable.Interval(TimeSpan.FromSeconds(1))
        .ObserveOnUIDispatcher() // Explicitly dispatch to the UI thread
        .ToReadOnlyReactivePropertySlim();
  11. Ignore initial validation errors

    main

    By default, ReactiveProperty reports errors for the initial value. If you want to avoid showing errors immediately upon startup, you have two options:

    1. Skip(1): Use the Skip operator on the error observable. Note: On platforms like WPF that use INotifyDataErrorInfo, the UI will still show a red error border because the error was still technically reported.
    2. ReactivePropertyMode.IgnoreInitialValidationError: Pass this flag to the constructor. This prevents the error from being reported entirely, so the UI (like WPF) will not show an error state for the initial value.
    // Option 1: Skip via Observable (UI might still show error border in WPF)
    NameErrorMessage = Name.ObserveErrorChanged
        .Skip(1)
        .Select(x => x?.OfType<string>()?.FirstOrDefault())
        .ToReadOnlyReactiveProperty();
    
    // Option 2: Ignore via Constructor (UI will NOT show error border in WPF)
    Name = new ReactiveProperty<string>(mode: ReactivePropertyMode.Default | ReactivePropertyMode.IgnoreInitialValidationError)
        .SetValidateAttribute(() => Name);
  12. Await ReactiveProperty and ReactiveCommand

    main

    You can use the await operator with ReactiveProperty, ReactivePropertySlim, ReadOnlyReactiveProperty, ReadOnlyReactivePropertySlim, and ReactiveCommand. When you await these objects, the program execution pauses until the next value is emitted.

    While you can await a ReactiveProperty directly, it is recommended to use one of the following methods to ensure proper cancellation support:

    1. For multiple awaits (Looping/Continuous monitoring): Use GetAsyncHandler(CancellationToken) to obtain an ObservableAsyncHandler<T>. This handler can be awaited multiple times without re-allocation.
    2. For a single await: Use await command.WaitUntilValueChangedAsync(CancellationToken) if you only need to wait for a single value change.

    Always pass a CancellationToken to these methods to ensure that pending awaits are cancelled when the component (e.g., a Window or ViewModel) is disposed or closed.

    // Pattern 1: Multiple awaits using GetAsyncHandler
    using (var handler = MyCommand.GetAsyncHandler(closeToken))
    {
        while (true)
        {
            await handler; // Waits until the command is executed/value changes
            // Perform logic here
        }
    }
    
    // Pattern 2: Single await using WaitUntilValueChangedAsync
    await myReactiveProperty.WaitUntilValueChangedAsync(closeToken);