Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,41 @@ The model is [`ReactiveUI.Binding.SourceGenerators/README.md`](https://github.co
the problem first, then a numbered walkthrough of the first use, then reference sections. A reader who stops
after the walkthrough can already do the thing.

### Diagrams: soft-tone Material 3

The site renders ```` ```mermaid ```` fences (NuStreamDocs.Mermaid, Mermaid 10). Every diagram uses the same palette,
taken from Material 3 container roles and seeded from the site's ReactiveUI blue (`#319af3`). Fills are light tones with
dark text, so a diagram reads in both the light and dark site themes. Copy the `init` line and the `classDef` lines
exactly. Do not invent colours.

```mermaid
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Roboto, Helvetica, Arial, sans-serif", "fontSize": "15px", "primaryColor": "#DCE9FF", "primaryBorderColor": "#6C8EC4", "primaryTextColor": "#0B2447", "secondaryColor": "#E3F2E8", "secondaryBorderColor": "#7FA88C", "secondaryTextColor": "#12301C", "tertiaryColor": "#F3E5F5", "tertiaryBorderColor": "#A98BB0", "tertiaryTextColor": "#2E1437", "lineColor": "#7B8699", "textColor": "#1B1F27", "noteBkgColor": "#FFF4D6", "noteBorderColor": "#C9A94F", "noteTextColor": "#3A2A00", "actorBkg": "#DCE9FF", "actorBorder": "#6C8EC4", "actorTextColor": "#0B2447", "signalColor": "#7B8699", "signalTextColor": "#1B1F27", "labelBoxBkgColor": "#F1F3F8", "labelBoxBorderColor": "#A7AEBB", "edgeLabelBackground": "#F7F9FC", "clusterBkg": "#F7F9FC", "clusterBorder": "#C9D1DE"}}}%%
flowchart LR
classDef view fill:#DCE9FF,stroke:#6C8EC4,color:#0B2447
classDef vm fill:#E3F2E8,stroke:#7FA88C,color:#12301C
classDef model fill:#F3E5F5,stroke:#A98BB0,color:#2E1437
classDef warn fill:#FDE7E4,stroke:#C98A82,color:#410E0B
classDef neutral fill:#F1F3F8,stroke:#A7AEBB,color:#1B1F27
View(["View"]):::view -- "binds to" --> VM(["View model"]):::vm
VM -- "reads and writes" --> Model(["Model"]):::model
```

| Class | Tone | Use for |
| --- | --- | --- |
| `view` | soft blue (primary container) | views, controls, anything the user sees |
| `vm` | soft green (secondary container) | view models, commands, reactive objects |
| `model` | soft lavender (tertiary container) | models, stores, services, data |
| `warn` | soft rose (error container) | errors, exceptions, failure paths |
| `neutral` | soft grey (surface variant) | infrastructure: sequencers, the builder, the resolver |

- Use a diagram only where it shows something faster than words: data flowing between view, view model and model; a
command's life cycle; a navigation stack; activation and deactivation; where work runs; suspension states. Most
pages need at most two.
- Use rounded shapes (`(["text"])` or `("text")`) and `flowchart LR` unless the flow is a stack or a timeline. Use
`sequenceDiagram` for "who calls whom, in what order" and `stateDiagram-v2` for life cycles.
- Keep to about eight nodes. Labels are plain words a grade 8 reader follows, in the page's own terms.
- Put each diagram after the paragraph that introduces it, and follow it with one sentence saying what to notice.

## In Flight: Per-Operator Pages For ReactiveUI.Primitives

The site is to carry one page per operator family for `ReactiveUI.Primitives`, with an example for every
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ Order: 2
---
# Avalonia

`ReactiveUI.Avalonia` lives in its own repository, [reactiveui/ReactiveUI.Avalonia](https://github.com/reactiveui/ReactiveUI.Avalonia),
not in `reactiveui/ReactiveUI`. It has its own target frameworks, release schedule and documentation.
`ReactiveUI.Avalonia` lives in its own repository, not in `reactiveui/ReactiveUI`. That repository is
[reactiveui/ReactiveUI.Avalonia](https://github.com/reactiveui/ReactiveUI.Avalonia). It has its own supported
.NET versions. It has its own release schedule and its own docs too.

Add `ReactiveUI.Avalonia` to your Avalonia application project.

```bash
dotnet add package ReactiveUI.Avalonia
```

A dependency-injection container of your choice adds one more package: `ReactiveUI.Avalonia.Autofac`,
Pick a dependency-injection container, and add one more package for it: `ReactiveUI.Avalonia.Autofac`,
`ReactiveUI.Avalonia.DryIoc`, `ReactiveUI.Avalonia.Microsoft.Extensions.DependencyInjection` or
`ReactiveUI.Avalonia.Ninject`. Each also ships as a `.Reactive` package, built from the same source, for an app
that uses System.Reactive.
`ReactiveUI.Avalonia.Ninject`. Each of these also ships as a `.Reactive` package, built from the same source,
for an app that uses System.Reactive.

See the [ReactiveUI.Avalonia repository](https://github.com/reactiveui/ReactiveUI.Avalonia) for setup and its
own examples, and [Data Binding in AvaloniaUI with ReactiveUI](../../handbook/data-binding/avalonia.md) for
own examples. See [Data Binding in AvaloniaUI with ReactiveUI](../../handbook/data-binding/avalonia.md) for
binding a view to its view model with `WhenActivated`.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ System.Reactive.

## Configure ReactiveUI at startup

Call `UseReactiveUI` on the `MauiAppBuilder` in `MauiProgram.CreateMauiApp`, with a delegate that configures the
`IReactiveUIBuilder` the way `RxAppBuilder` would elsewhere. `WithMauiConverters` here registers MAUI's
boolean-to-visibility converters; a real app's delegate also registers its views and services.
Call `UseReactiveUI` on the `MauiAppBuilder` in `MauiProgram.CreateMauiApp`. Pass it a delegate that configures
the `IReactiveUIBuilder`, the same way `RxAppBuilder` would elsewhere. `WithMauiConverters` here registers
MAUI's boolean-to-visibility converters. A real app's delegate also registers its views and services.

```csharp
_ = mauiBuilder.UseReactiveUI(builder =>
Expand All @@ -30,11 +30,11 @@ _ = mauiBuilder.UseReactiveUI(builder =>
```

`UseReactiveUI` registers MAUI's platform module, converters and dispatcher-backed main-thread sequencer.
[RxAppBuilder](../../handbook/rxappbuilder.md) covers the builder itself, and [.NET MAUI](../../handbook/platforms/maui.md)
covers everything you build with the package: `ReactiveContentPage<T>` and the other reactive page bases, binding
a page to its view model, showing a router's current page, and navigating between pages.
[RxAppBuilder](../../handbook/rxappbuilder.md) covers the builder itself. [.NET MAUI](../../handbook/platforms/maui.md)
covers everything you build with the package. That includes `ReactiveContentPage<T>` and the other reactive page
bases, binding a page to its view model, showing a router's current page, and navigating between pages.

A view model implementing `IScreen` gives it a router to navigate; see [Routing](../../handbook/routing.md).
The [compelling example](../compelling-example.md) walks through a first view model and view. There is no
`ReactiveUI.Validation` code in this installation's example project; see [Validation](../../../validation.md)
A view model that implements `IScreen` gets a router to navigate with. See [Routing](../../handbook/routing.md).
The [compelling example](../compelling-example.md) walks through a first view model and view. This
installation's example project has no `ReactiveUI.Validation` code. See [Validation](../../../validation.md)
for validation rules on a view model.
51 changes: 25 additions & 26 deletions docs/documentation/reactiveui/guidelines/debugging/debug-symbols.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
# Debugging ReactiveUI

Every ReactiveUI package on NuGet ships with [SourceLink](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink)
metadata embedded in the assembly's PDB (`<DebugType>embedded</DebugType>` +
`Microsoft.SourceLink.GitHub`, configured once in
[`src/Directory.Build.props`](https://github.com/reactiveui/reactiveui/blob/main/src/Directory.Build.props)).
That means you can step from your own code straight into the framework source
on GitHub, at the exact commit your installed version was built from. You need
no matching local clone and no symbol-server hunting.
metadata. The metadata is embedded in the assembly's PDB, a file that holds debug information. This comes from
`<DebugType>embedded</DebugType>` and `Microsoft.SourceLink.GitHub`, configured once in
[`src/Directory.Build.props`](https://github.com/reactiveui/reactiveui/blob/main/src/Directory.Build.props).
That means you can step from your own code straight into the framework source on GitHub. You land at the exact
commit your installed version was built from. You need no matching local clone. You need no symbol-server
hunting either.

## Visual Studio (2022 and newer)

Two debugger settings unlock SourceLink stepping. Both live under
**Tools → Options → Debugging**:

1. **General** page
- **Enable Just My Code** — *uncheck*. With Just My Code on, the debugger
never asks the SourceLink resolver for framework frames; turning it off
- **Enable Just My Code** — *uncheck*. When Just My Code is on, the debugger
never asks the SourceLink resolver for framework frames. Turning it off
lets the resolver fetch the matching ReactiveUI source on demand.
- **Enable Source Link support** — *check*. This is the master switch
that wires `Microsoft.VisualStudio.Debugger.SourceLink` into the
symbol load path.
- *(Recommended)* **Enable source server support** + **Suppress JIT
optimization on module load (Managed only)** — gives you readable
- *(Recommended)* **Enable source server support** and **Suppress JIT
optimization on module load (Managed only)**. These give you readable
locals when stepping into release-built framework code.
2. **Symbols** page
- Make sure **Microsoft Symbol Servers** (or another server hosting the
ReactiveUI PDBs — typically not needed since `embedded` ships the PDB
inside the .dll itself) is enabled. With embedded PDBs, no extra
server entry is required.
- Make sure **Microsoft Symbol Servers** is enabled, or another server
that hosts the ReactiveUI PDBs. You usually don't need this step,
because the embedded PDB already ships inside the .dll.

Set a breakpoint, hit it, then *Step Into* (`F11`) any ReactiveUI call
(e.g. `this.WhenAnyValue(...)`). Visual Studio prompts once with
*"Source Link will download <https://raw.githubusercontent.com/reactiveui/...>
— OK?"*; accept and you land in the framework's source file at the exact
— OK?"* Accept it, and you land in the framework's source file at the exact
commit your NuGet package was published from.

## Rider / VS Code (C# Dev Kit)

Rider honors SourceLink out of the box — make sure
Rider honors SourceLink out of the box. Make sure
**Settings → Build, Execution, Deployment → Debugger → Enable external source
debug** is on, and disable Just My Code under the same screen.
debug** is on. Disable Just My Code under the same screen.

In VS Code with the C# Dev Kit, set in `launch.json`:

Expand All @@ -55,19 +54,19 @@ In VS Code with the C# Dev Kit, set in `launch.json`:

## Quick demo

<div class="youtube-video-container"><iframe src="https://www.youtube.com/embed/gyRGhCQPkB4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>
[Watch the SourceLink debugging demo on YouTube](https://www.youtube.com/watch?v=gyRGhCQPkB4)

## Troubleshooting

- **Step Into still skips framework frames.** Confirm Just My Code is off
and that the package version on disk matches a published release tag (the
SourceLink URL embeds the commit SHA — local builds without a release
commit can't be resolved).
- **Step Into still skips framework frames.** Confirm Just My Code is off.
Also confirm the package version on disk matches a published release tag.
The SourceLink URL embeds the commit SHA, so a local build without a
release commit can't be resolved.
- **`The remote endpoint could not be reached`.** Your network blocks
`raw.githubusercontent.com`. Either whitelist that host or run with the
`raw.githubusercontent.com`. Either allow that host, or run with the
source already on disk and point Visual Studio at it via
**Debug → Options → Symbols → Specify excluded modules**.
- **Mobile heads (iOS / Android via .NET MAUI).** The platform debuggers
honor SourceLink for managed code today, but native interop frames stay
opaque. For pure-managed ReactiveUI calls the experience matches WPF /
WinForms.
honor SourceLink for managed code today. Native interop frames stay
opaque, though. For pure-managed ReactiveUI calls, the experience matches
WPF and WinForms.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Disable Just My Code

Turn off Visual Studio's [Just My Code](https://learn.microsoft.com/visualstudio/debugger/just-my-code) feature.
With it on, the debugger steps over framework code, including `IObservable<T>` pipelines and `async`/`await`
state machines, so **Step Into** on a `WhenAnyValue` or `Subscribe` call skips straight past it. Turning it off
lets you step through the framework's own code, which is where most reactive bugs actually show up.
With it on, the debugger steps over framework code. This includes `IObservable<T>` pipelines and `async`/`await`
state machines. So **Step Into** on a `WhenAnyValue` or `Subscribe` call skips straight past it. Turning it off
lets you step through the framework's own code. That is where most reactive bugs actually show up.

See [debugging ReactiveUI](debug-symbols.md) for the full set of debugger settings SourceLink needs, including
this one.
Expand All @@ -19,6 +19,6 @@ Reactive debugging tip: turn on breaking on thrown exceptions before they are ha
[Understanding exceptions while debugging with Visual
Studio](https://devblogs.microsoft.com/devops/understanding-exceptions-while-debugging-with-visual-studio)

An exception inside a stream's callback ends that subscription: the stream calls `OnError` instead of `OnNext`
or `OnCompleted`, and stops. Breaking on the exception itself, rather than after it becomes an unhandled error,
puts you at the exact line that failed.
An exception inside a stream's callback ends that subscription. The stream calls `OnError` instead of `OnNext`
or `OnCompleted`, and it stops. Break on the exception itself, rather than after it becomes an unhandled error.
That puts you at the exact line that failed.
20 changes: 10 additions & 10 deletions docs/documentation/reactiveui/guidelines/platform/android.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@
`ReactiveUI.AndroidX` connects ReactiveUI to Android through the AndroidX support libraries. Add it by following
[Installation](../../getting-started/installation/androidx.md). `ReactiveUI.AndroidX` also ships as
`ReactiveUI.AndroidX.Reactive`, built from the same source, for an app that uses System.Reactive.
[Android](../../handbook/platforms/android.md) walks through every pattern below with a school timetable app. The core
`ReactiveUI` package's Android build adds `ReactiveActivity`, `SharedPreferencesExtensions` and
`UsbManagerExtensions` for apps that do not use AndroidX.
[Android](../../handbook/platforms/android.md) walks through every pattern below with a school timetable app. For
an app that does not use AndroidX, the core `ReactiveUI` package's Android build adds `ReactiveActivity`,
`SharedPreferencesExtensions` and `UsbManagerExtensions`.

## Guidelines

- **View models inherit from `ReactiveObject`.** They stay free of any Android type, so you can unit test them
- **View models inherit from `ReactiveObject`.** They stay free of any Android type. So you can unit test them
without a device or an emulator.
- **Activities, fragments and dialogs inherit from the reactive base classes.** `ReactiveAppCompatActivity<TViewModel>`,
`ReactiveFragmentActivity<TViewModel>`, `ReactiveFragment<TViewModel>`, `ReactiveDialogFragment<TViewModel>` and
`ReactivePreferenceFragment<TViewModel>` each implement `IViewFor<TViewModel>`. Each one exposes a `ViewModel`
property, so you never wire one up by hand.
property. You never wire one up by hand.
- **Wire up controls with `WireUpControls()`, not `FindViewById`.** `ControlFetcherMixins.WireUpControls` reads a
view's properties by reflection and finds each one's matching resource by name, once, instead of a
`FindViewById` call per control. Its `ResolveStrategy` decides which properties it wires: `Implicit` wires every
writable `View`-typed property, `ExplicitOptIn` wires only properties marked to opt in, and `ExplicitOptOut`
view's properties by reflection. It finds each one's matching resource by name, once, instead of a
`FindViewById` call per control. Its `ResolveStrategy` decides which properties it wires. `Implicit` wires every
writable `View`-typed property. `ExplicitOptIn` wires only properties marked to opt in. `ExplicitOptOut`
wires every property except those marked to opt out.
- **Use `IActivatableViewModel` and `WhenActivated` for lifecycle.** See [When Activated](../../handbook/when-activated.md)
for how a subscription's lifetime follows the view's.
- **Keep every subscription disposed.** See [Cleaning up subscriptions](../../../reactive-programming/observables.md#cleaning-up)
and [Disposables](../../../primitives/disposables.md).
- **Convert Android events to streams instead of attaching event handlers.** The extension methods in
`ReactiveUI.Primitives.ObservableEvents` turn an event into an `IObservable<T>`. Compose it with the rest of a
view model's streams; see [Events](../../handbook/events.md) and
view model's streams. See [Events](../../handbook/events.md) and
[ObservableEvents](../../../primitives/observable-events/index.md).
- **Bind a list without a hand-written adapter.** `ReactiveRecyclerViewAdapter<TViewModel, TCollection>` and
`ReactiveRecyclerViewViewHolder` bind a `RecyclerView` to a collection of view models. `ReactivePagerAdapter<TViewModel, TCollection>`
does the same for a `ViewPager`.
- **Await an activity result instead of overriding `OnActivityResult`.** `StartActivityForResultAsync` on the reactive
activity base classes turns a launched activity's result into a `Task`, so the calling code reads like ordinary
activity base classes turns a launched activity's result into a `Task`. The calling code then reads like ordinary
asynchronous code.
Loading
Loading