diff --git a/doc/Overview/Mvux/FeedView.md b/doc/Overview/Mvux/FeedView.md index 10304f3d4a..430d38aba6 100644 --- a/doc/Overview/Mvux/FeedView.md +++ b/doc/Overview/Mvux/FeedView.md @@ -1,17 +1,17 @@ # The `FeedView` control -The `FeedView` control is the heart of MVUX on the View side. -It seamlessly fulfills everything Feeds and States offer and displays all metadata on the screen providing rich customizability for many modes, as detailed below. +The `FeedView` control is the heart of MVUX on the View side and is one of the main way to consume Feeds and States within an application. The `FeedView` uses different visual states to control what is displayed on the screen depending on the state of the underlying feed, or state. -## How to use the FeedView control +## How to use the `FeedView` control -To use the `FeedView` you have to add the MVUX UI controls namespace to your XAML file as follows: +To use the `FeedView` you have to add the Uno.Extensions.Reactive.UI namespace to your XAML file as follows: ```xml - + ``` ## Common properties @@ -20,15 +20,14 @@ Here are some of the notable properties of the `FeedView`: ### Source -The `Source` property is the entry point of the `FeedView` and it's to be set with a Feed or State object (or their List variant). - -> [!TIP] -> `IFeed`, `IListFeed`, `IState`, and `IListState` all share a common interface - `ISignal`, this the core low-level interface that enables access to the asynchronous data. +The `Source` property is the entry point of the `FeedView` and it's to be set with a `IFeed` or `IState` object (or their list variant). Example: ```csharp -public IFeed CurrentContact => ... +public partial record MainModel { + public IFeed CurrentContact => ... +} ``` Then in the XAML: @@ -46,14 +45,15 @@ Then in the XAML: ``` -The `Source` property of the `FeedView` is set to the generated bindable model's `CurrentContact` property, which asynchronously requests the `Contact` from the service. +The `Source` property of the `FeedView` is data bound to the `CurrentContact` property on the bindable proxy (which will correlate to the `IFeed` property with the same name on the Model). -In the above example, `Data` is a property of the `FeedViewState` provided via the template, [read on](#data) for details. +In the above example, [`Data`](#data) is a property of the `FeedViewState` instance that the `FeedView` creates from the `IFeed` and sets as the `DataContext` for the various templates. ### State -You might not have to use the `State` property but it's important to know exists. -This property provides a `FeedViewState` which exposes the current state of the `FeedView`'s underlying data Feed wrapped in its metadata in an accessible way for the View to represent easily using the built-in or the customized templates accordingly. +The `State` property provides a `FeedViewState` which exposes the current state of the `FeedView`'s underlying data Feed wrapped in its metadata in an accessible way for the View to represent easily using the built-in or the customized templates accordingly. + +It's unlikely that you'll need to access the `State` property directly since the `FeedViewState` is automatically set as the `DataContext` of the various templates. #### The `FeedViewState` object @@ -104,10 +104,7 @@ For example: ``` -The `Button`'s `Command` property binds to the `FeedViewState`'s `Refresh` property which exposes a special asynchronous command that when called, triggers a refresh of the parent Feed (in our example `CurrentContact`, and the data is re-obtained from the server. - -> [!NOTE] -> The `FeedViewState` uses its `FeedView`'s `Refresh` property to provide this Command. See [here](#refresh-command-property). +The `Button`'s `Command` property binds to the `FeedViewState`'s `Refresh` property which exposes a special asynchronous command that when called, triggers a refresh of the parent feed (in our example `CurrentContact`, and the data is re-obtained from the server. ##### Progress @@ -131,7 +128,7 @@ However, in some scenarios, you need to disable the default visual state and pro This property accepts a value of the `FeedViewRefreshState` enumeration, which supports one of the values below which you can set to change its behavior. -- `None` +- `None` - `Default` / `Loading` ## Customizing the FeedView's templates @@ -189,7 +186,6 @@ But you can customize that by overriding the `ProgressTemplate`: ``` - ### NoneTemplate If you set a template to this property, it will show if the data that was returned from the service contained no entries. @@ -212,15 +208,14 @@ Example: ### ErrorTemplate -The `FeedView` will display this template if an Exception was thrown by the underlying service request. +The `FeedView` will display this template if an Exception was thrown by the underlying asynchronous operation. ### UndefinedTemplate -This template is displayed when the control loads, before the underlying asynchronous service request has even been called. -As soon as the asynchronous request is invoked and awaited, the `FeedView` will switch to its `ProgressTemplate`, until the request has resulted in data, which it will then switch to the `ValueTemplate`, or `NoneTemplate`, depending on the data result. +This template is displayed when the control loads, before the underlying asynchronous operation has even been called. +As soon as the asynchronous operation is invoked and awaited, the `FeedView` will switch to its `ProgressTemplate`, until the operation has resulted in data, which it will then switch to the `ValueTemplate`, or `NoneTemplate`, depending on the data result. -> [!TIP] -> Typically this template will only show for a very short period - a split second or so, depending on how long it takes for the page and its Model to load. +Typically this template will only show for a very short period - a split second or so, depending on how long it takes for the page and its Model to load. ## Other notable features diff --git a/doc/Overview/Mvux/Feeds.md b/doc/Overview/Mvux/Feeds.md index aa183ac84a..f5b16975ac 100644 --- a/doc/Overview/Mvux/Feeds.md +++ b/doc/Overview/Mvux/Feeds.md @@ -2,26 +2,25 @@ uid: Overview.Mvux.Feeds --- -# What are Feeds? +# What are feeds? -Feeds are there to manage asynchronous data requests from a service and provide their result to the View in an efficient manner. +Feeds are there to manage asynchronous operations (for example requesting data from a service) and expose the result to the View in an efficient manner. -It provides out of the box support for data coming from task-based methods as well Async-Enumerables ones. +They provide out of the box support for task-based methods as well as [Async-Enumerables](https://learn.microsoft.com/dotnet/api/system.collections.generic.iasyncenumerable-1) ones. -They accompany the requests with additional metadata that indicates whether the request is still in progress, ended in an error, or if it was successful, whether the data that was returned contains any entries or was empty. +Feeds include additional metadata that indicates whether the operation is still in progress, ended in an error, or if it was successful, whether the data that was returned contains any entries or was empty. ## Feeds are stateless -Feeds are used as a gateway to request data from services and expose it in a stateless manner so that it can be displayed by the View. +Feeds are typically used to request data from services and expose it in a stateless manner so that the resulting data can be displayed by the View. -Feeds are stateless and do not provide support for reacting upon changes the user makes to the data on the View, the data can only be reloaded and refreshed upon request which is when the underlying task or Async-Enumerable will be invoked and the data refreshed. -In other words, a Feed is a read-only representation of the data received from the server. +Feeds are stateless and do not provide support for reacting upon changes the user makes to the data on the View, the data can only be reloaded and refreshed upon request which is when the underlying task or Async-Enumerable will be invoked and the data refreshed. In other words, a feed is a read-only representation of the data received from the server. -In contrast to Feeds, [States](xref:Overview.Mvux.States), as the name suggests, are stateful and keep track of the latest value, as updates are applied. +In contrast to feeds, [states](xref:Overview.Mvux.States) (`IState` or `IListState`), as the name suggests, are stateful and keep track of the latest value, as updates are applied. -## How to use Feeds? +## How to use feeds? -### Creation of Feeds +### Creation of feeds For the examples below, let's use a counter service that returns the current count number, starting from 1. It will be run 3 consecutive times delayed by a second each. For the data type, we'll create a record type called `CounterValue`: @@ -55,7 +54,7 @@ The `Feed.Async` factory method can be used to create an `IFeed` by calling the public IFeed Value => Feed.Async(_myService.CountOne); ``` -This is known as a 'pull' method, as the `CountOne` method is awaited while retrieving the data. To get the next counter value, the IFeed needs to be signaled to call the `CountOne` method again. +This is known as a 'pull' method, as the `CountOne` method is awaited while retrieving the data. To get the next counter value, the `IFeed` needs to be signaled to call the `CountOne` method again. For the most part `Task` and `ValueTask` are interchangeable. However, with MVUX if the method returns `Task` the method needs to be awaited in the `Feed.Async` callback. @@ -69,7 +68,7 @@ public IFeed CurrentCount => Feed.Async(async ct => await _myServi #### Feed.AsyncEnumerable factory -In contrast to Tasks which operate as 'pull' methods, the 'push' method is where we call a method and establish some sort of connection with it, while it sends new data as it becomes available: +In contrast to Tasks which operate as 'pull' methods, the 'push' method is where data is returned as it becomes available via an `IAsyncEnumerable` instance. For example, the `StartCounting` method will return a new counter value every second until the `CancellationToken` is cancelled. ```csharp public async IAsyncEnumerable StartCounting([EnumeratorCancellation] CancellationToken ct) @@ -88,36 +87,23 @@ public async IAsyncEnumerable StartCounting([EnumeratorCancellatio } ``` -Referring to the Async Enumerable from the example a Feed can be created in the following way: +Referring to the `StartCounting` method from this example, a feed can be created as follows: ```csharp -public async IAsyncEnumerable StartCounting(CancellationToken ct) { ... } - public IFeed CurrentCount => Feed.AsyncEnumerable(_myService.StartCounting); ``` -`CancellationToken`s are essential to enable halting an ongoing async operation. -However, if the API you're consuming does not have a `CancellationToken` parameter, you can disregard the incoming `CancellationToken` parameter as follows: +`CancellationToken`s are essential to enable halting an ongoing async operation. However, if the API you're consuming does not have a `CancellationToken` parameter, you can disregard the incoming `CancellationToken` parameter as follows: ```csharp public IFeed CurrentCount => Feed.AsyncEnumerable(ct => StartCounting()); ``` -> [!NOTE] -> `Feed` is a static class that provides factory methods that create `IFeed`s, as well as extension methods for `IFeed`. - -> [!NOTE] -> There are additional ways to load data (e.g. Observables), but most of them are easily convertible to one of the above two. - -> [!TIP] -> Feeds can also be constructed manually using the `Feed.Create` method. - -### Consumption of Feeds +### Consumption of feeds -#### Directly await Feeds +#### Awaiting feeds -Feeds are directly awaitable, so to get the data currently held in the feed, this is useful when you want to use the current value in a command, etc. -You can await it in the following manner: +Feeds are directly awaitable, so to get the data currently held in the feed, this is useful when you want to use the current value in a command, etc. You can await it in the following manner: ```csharp public IFeed CurrentCount => ... @@ -128,25 +114,19 @@ private async ValueTask SomeAsyncMethod() } ``` -> [!TIP] -> This is possible thanks to the `GetAwaiter` extension method of `IFeed`. Read [this](https://devblogs.microsoft.com/pfxteam/await-anything) for more. +#### Use feeds in an MVUX Model -#### Use Feeds in an MVUX Model +The MVUX analyzers generate a bindable proxy for each of the models in your app (those with `Model` suffix). For the code generation to work, mark the Models and entities with the `partial` modifier. -The MVUX analyzers generate a proxy entity for each of the models in your app (those with `Model` suffix). For every Feed property (returning `IFeed` or `IListFeed`) found in the model, a corresponding Feed (or List-Feed) property is generated on the proxy entity. -MVUX recommends using plain [POCO](https://en.wikipedia.org/wiki/Plain_old_CLR_object) (Plain Old CLR Object) `record` types for the models in your app as they're immutable, and will not require any property change notifications to be raised. -The generated proxy and its properties ensure that data-binding will work, even though property change notifications aren't being raised by the models themselves. +For every `public` feed property (returning `IFeed` or `IListFeed`) found in the model, a corresponding property is generated on the bindable proxy. -> [!Note] -> For the code generation to work, mark the Models and entities with the `partial` modifier, and have the Feed properties' access modifier as `public`. -You can learn more about partial classes and methods in [this article](https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods). +MVUX recommends using plain [POCO](https://en.wikipedia.org/wiki/Plain_old_CLR_object) (Plain Old CLR Object) `record` types for the models in your app as they're immutable, and will not require any property change notifications to be raised. The generated bindable proxy and its properties ensure that data-binding will work, even though property change notifications aren't being raised by the models themselves. #### With regular data-binding -Feeds can be consumed directly by data-binding to the Feed property declared on the Model. -MVUX code-generation engine ensures the Feeds and all entities they expose are going to be seamlessly data-bound with the XAML data-binding engine. +Feeds can be consumed directly by data-binding to the feed property declared on the Model. MVUX code-generation engine ensures the feeds and all entities they expose are going to be seamlessly data-bound with the XAML data-binding engine. -The Feed can be consumed directly from the View, it's as simple as binding a regular value property exposed on the Model: +The feed can be consumed directly from the View, by data binding to a property exposed on the Model: ```xml @@ -154,13 +134,11 @@ The Feed can be consumed directly from the View, it's as simple as binding a reg ``` -> [!TIP] -> The data-binding engine will await the feed under the hood and will display the awaited data once available. - #### With the `FeedView` control -The `FeedView` control has been designed to work with Feeds and is tailored to the additional metadata mentioned [earlier](#what-are-feeds) that are disclosed by the Feed and respond to it automatically and efficiently. -A `FeedView` has built-in templates that adapt the View according to the current state of the data, such as when the data request is still in progress, an error has occurred, or when the data contained no records. +The `FeedView` control has been designed to work with feeds and is tailored to the additional metadata mentioned [earlier](#what-are-feeds) that are disclosed by the feed and respond to it automatically and efficiently. + +The `FeedView` has templates that change the visible contents based on the current state of the data, such as when the data request is still in progress, an error has occurred, or when the data contained no records. Built-in templates are included with the `FeedView` for these states, but they can all be customized. Here's how to utilize the `FeedView` to display the same data as before: @@ -168,50 +146,21 @@ Here's how to utilize the `FeedView` to display the same data as before: ```xml - - - - + xmlns:mvux="using:Uno.Extensions.Reactive.UI"> + + + + ``` -> [!TIP] -> The `FeedView` wraps the data coming from the Feed in a special `FeedViewState` class which includes the Feed metadata. -One of its properties is `Data`, which provides access to the actual data of the Feed's current state, in our example the most recent integer value from the `CountOne` or `StartCounting` method [above](#consumption-of-feeds). - -## Messages - -Messages are one of the core components of MVUX. They refer to the metadata that wraps around the entities streaming along as discussed earlier. - -The Feed encapsulates a stream of Messages for each packet of data received from the underlying request. For a Task, it would be each execution of the Task and obtaining the refreshed/up-to-date value, and similarly with Async-Enumerable, it would be each iteration and yielding of a refreshed value, until it's cancelled using the `CancellationToken`. - -A Message provides several metadata types (called axis/axes). -The most common three metadata types are: - -- Data -This discloses information about the data that was allegedly returned by the request. -The data is encapsulated in an `Option` (where `T` refers to the data entity type): - - None - indicates absence of data, i.e. the data-request resulted in no records. - - Some - indicates presence of a value / values. - - Undefined - we can't currently determine if there's data, e.g. we're still awaiting it or if an error occurred. -- Progress -The underlying task/request is in progress, if there's data, it's regarded as transient till the request completes and a new result is available. -- Error -An error has occurred. The `Exception` is attached. - -The following illustration show how the classes are built. Note that this diagram shows a stripped-down version of the actual types, for brevity: - -![A diagram showing Feed Message class hierarchy](Assets/FeedMessagesDiagram.jpg) - -> [!TIP] -> MVUX provides you with peripheral tools that read the metadata Messages for you so that you don't normally even have to know about the Message structure! +The `FeedView` wraps the data coming from the feed in a special `FeedViewState` class which includes the feed metadata. One of its properties is `Data`, which provides access to the actual data of the feed's current state, in our example the most recent integer value from the `CountOne` or `StartCounting` method [above](#consumption-of-feeds). ## Feed Operators -The Feed supports some LINQ operators that enable readjusting it into a new one. +An `IFeed` supports some LINQ operators that can be used to apply a transform and return a new `IFeed`. ### Where @@ -222,8 +171,7 @@ For example: public IFeed OmitEarlyCounts => CurrentCount.Where(currentCount => currentCount.Value > 10); ``` -> [!Note] -> Be aware that unlike `IEnumerable`, `IObservable`, and `IAsyncEnumerable`, if the predicate returns false, a result is still received but it contains a `Message` with a data `Option` of `None`. +It's worth noting that unlike `IEnumerable`, `IObservable`, and `IAsyncEnumerable`, if the predicate returns false, a result is still received but it contains a `Message` with a data `Option` of `None`. ### Select or SelectAsync @@ -239,11 +187,12 @@ The selection can also be asynchronous, and even use an external method: public IFeed CountTrends => CurrentCount.SelectAsync(currentCount => myService.GetCountInfoAsync(currentCount)); ``` -> [!TIP] -> You can use the LINQ syntax if you prefer, or combine the operators: +### LINQ syntax + +You can use the LINQ syntax if you prefer, which can improve the readability of your code, particularly if you combine multiple operators: ```csharp -public IFeed OmitEearlyCounts => +public IFeed OmitEarlyCounts => from currentCount in CurrentCount where currentCount.Value > 10 select currentCount.Value; diff --git a/doc/Overview/Mvux/ListFeeds.md b/doc/Overview/Mvux/ListFeeds.md index 9bd8302512..92f7c8f710 100644 --- a/doc/Overview/Mvux/ListFeeds.md +++ b/doc/Overview/Mvux/ListFeeds.md @@ -2,56 +2,60 @@ uid: Overview.Mvux.ListFeeds --- -# What are List-Feeds? +# What are list-feeds? -The `IListFeed` is like a [Feed](xref:Overview.Mvux.Feeds) which is stateless and keeps no track of changes, but is specialized for handling collections. +A list-feed (`IListFeed`) is like a [feed](xref:Overview.Mvux.Feeds) which is stateless and keeps no track of changes, but is specialized for handling collections. -In Feeds, each data request from the service, returns one single item, whereas in List-Feed each request returns a collection of items. -Its distinctive feature is the ability to use its [Operators](xref:Overview.Mvux.ListFeeds#operators) directly on its items, rather than on the entire collection. +Unlike a feed, where each asynchronous operation, returns one single item (or a series of single items in the case of an `IAsyncEnumerable`), a list-feed returns a collection of items. -Another notable characteristic of List-Feed is how it handles the absence of data. When an empty collection is returned by the service, it's treated as an Empty message. The returned data axis Option will be `None`, even though the result was not `null`. +A couple of points to note about list-feeds: + +- [Operators](#operators) are applied to the items within the returned collection, rather than on the entire collection. + +- When an empty collection is returned by the service, it's treated as an Empty message. The returned data axis Option will be `None`, even though the result was not `null`. This is because when a control of data items is displayed with an empty collection (for instance a `ListView`), there is no reason to display the `FeedView`'s `ValueTemplate` with an empty `ListView`. The "No data records" `NoneTemplate` makes much more sense in this case. For that reason, both a `null` result and an empty collection are regarded as `None`. -For more information, review the [Feed Messages section](xref:Overview.Mvux.Feeds#messages). -> [!NOTE] -> The List-Feed is using the _key equality_ to track multiple versions of the same entity within different messages of the List-Feed. -(Read more about _key equality_.)[xref:Overview.KeyEquality.Concept] +- The list-feed is uses the _key equality_ to track multiple versions of the same entity within different messages of the list-feed. +[Read more about _key equality_](xref:Overview.KeyEquality.Concept). ## How to create a list feed -To create an `IListFeed`, use the static class `ListFeed` to call one of the same `Async`, `AsyncEnumerable`, and `Create` methods as in Feed. -The only difference is that it expects the Task or Async Enumerable to return an `IImmutableList` instead of `T`. +To create an `IListFeed`, use the static class `ListFeed` to call one of the same `Async`, `AsyncEnumerable`, and `Create` methods as in feed. The only difference is that it expects the Task or `IAsyncEnumerable` to return an `IImmutableList` instead of `T`. -Here are some examples of creating a List-Feed: +Here are some examples of creating a list-feed: 1. In this example the service returns a list of names on load/refresh - using a pull technique. Service code: + ```csharp public ValueTask> GetNames(CancellationToken ct = default); ``` Model code: + ```csharp public IListFeed Names => ListFeed.Async(service.GetNames); ``` - + 2. This one returns an immutable list of names when available - using push technique: Service code: - ```csharp + + ```csharp public IAsyncEnumerable> GetNames( [EnumeratorCancellation] CancellationToken ct = default); ``` Model code: - ``` + + ```csharp public IListFeed Names => ListFeed.AsyncEnumerable(service.GetNames); ``` - Pull and push are explained more in the [Feeds page](xref:Overview.Mvux.Feeds#creation-of-feeds). + Pull and push are explained more in the [feeds page](xref:Overview.Mvux.Feeds#creation-of-feeds). -3. There are also two helper methods that enable conversion from a Feed to a List-Feed and vice versa. +3. There are also two helper methods that enable conversion from a feed to a list-feed and vice versa. - On an `IFeed` where `TCollection` is an `IImmutableList`, call `ToListFeed()` to convert it to an `IListFeed`. - Otherwise, call `AsFeed()` on an `IListFeed` to convert it to an `IFeed>`. @@ -63,15 +67,14 @@ See more information on [Selection](xref:Overview.Mvux.Advanced.Selection) or [P ## Operators -As mentioned, unlike a `Feed>` operators on a List-Feed are directly interacting with the collection's items instead of the list itself. +As mentioned, unlike a `Feed>` operators on a list-feed are directly interacting with the collection's items instead of the list itself. ### Where -This operator allows the filtering of the items. - -> [!IMPORTANT] -> If all items of the collection are filtered out, the resulting Feed will go into `None` state. +This operator allows the filtering of the items. ```csharp public IListFeed LongNames => Names.Where(name => name.Length >= 10); ``` + +If all items of the collection are filtered out, the resulting feed will go into `None` state. diff --git a/doc/Overview/Mvux/ListStates.md b/doc/Overview/Mvux/ListStates.md index 7ce486a30c..78a2d0fbaf 100644 --- a/doc/Overview/Mvux/ListStates.md +++ b/doc/Overview/Mvux/ListStates.md @@ -2,22 +2,23 @@ uid: Overview.Mvux.ListStates --- -# What are List-States +# What are list-states -List-State is the collection counterpart of [State](xref:Overview.Mvux.States). -It's a State that adds extra operators which make it easier to apply updates on multiple items instead of just a single one, like what a State does. +List-state is the collection counterpart of [state](xref:Overview.Mvux.States). -Recall that Feeds are stateless and are only read-only data output to the View, and are not responding to changes, whereas States are stateful, and update the Model and its entities upon changes made in the View using two-way data-binding, or on demand via Commands. +List-state adds extra operators which make it easier to apply updates on multiple items instead of just a single item. + +Recall that feeds are stateless and are only read-only data output to the View, and are not responding to changes, whereas states are stateful, and update the Model and its entities upon changes made in the View using two-way data-binding, or on demand via Commands. So an `IState` is a stateful feed of a single item of `T`, whereas an `IListState` is a stateful feed of multiple items of `T`. -# How to create a List-State +# How to create a list-state The static `ListState` class provides factory methods for creating `IListState` objects, here they are: ## Empty -Creates an empty List-State: +Creates an empty list-state: ```csharp IListState MyStrings = ListState.Empty(this); @@ -25,7 +26,7 @@ IListState MyStrings = ListState.Empty(this); ## Value -Creates a List-State with an initial synchronous value: +Creates a list-state with an initial synchronous value: ```csharp private readonly IImmutableList _favorites = @@ -42,7 +43,7 @@ public IListState Favorites => ListState.Value(this, () => _favorites); ## Async -Creates a List-State from an async method: +Creates a list-state from an async method: ```csharp public ValueTask> GetStrings(CancellationToken ct) => new(_favorites); @@ -71,12 +72,12 @@ public IListState FavoritesState => ListState.FromFeed(this, FavoritesFe ## Others -There's also the `ListState.Create` method which allows you to manually build the List-State with Messages. +There's also the `ListState.Create` method which allows you to manually build the list-state with Messages. You can learn about manual creation of Messages [here](xref:Overview.Reactive.State#create). ## Operators -In the following examples, we'll refer to `MyStrings` which is an `IListState`, to demonstrate how to use the various operators the List-State provides to update its state with modified data. +In the following examples, we'll refer to `MyStrings` which is an `IListState`, to demonstrate how to use the various operators `IListState` provides to update its state with modified data. ### Add @@ -96,7 +97,7 @@ await MyStrings.InsertAsync("Margaret Atwood", cancellationToken); ### Update -There are various ways to update values in the List-State: +There are various ways to update values in the list-state: The `Update` method has an `updater` parameter like the `State` does. This parameter is a `Func, IImmutableList>`, which when called passes in the existing collection, allows you to apply your modifications to it, and then returns it. @@ -128,9 +129,6 @@ public async ValueTask TrimLongNames(CancellationToken ct = default) } ``` -> [!Note] -> There is also the `UpdateData` method, which enables manual creation of a data-axis Message wrapped in an `Option` that denotes whether the data has entities or is empty. - ### Remove The `RemoveAllAsync` method uses a predicate to determine which items are to be removed: @@ -143,7 +141,7 @@ The `RemoveAllAsync` method uses a predicate to determine which items are to be ### ForEachAsync -This operator can be called from an `IListState` to execute an asynchronous action on all items currently in the List-State each time its data is changed (values are either added or removed): +This operator can be called from an `IListState` to execute an asynchronous action on all items currently in the list-state each time its data is changed (values are either added or removed): ```csharp await MyStrings.ForEachAsync(async(list, ct) => await PerformAction(items, ct)); @@ -159,11 +157,11 @@ private async ValueTask PerformAction(IImmutableList items, Cancellation ## Selection operators -Like List-Feed, List-State provides out-the-box support for Selection. +Like list-feed, list-state provides out-the-box support for Selection. This feature enables flagging single or multiple items in the State as 'selected'. Selection works seamlessly and automatically with the `ListView` and other selection controls. -In case you need to select an item manually for example in response to a button pressed or when finding a searched item, you can use the following methods that enable manual changing of the Selection state of items in the List-State: +In case you need to select an item manually for example in response to a button pressed or when finding a searched item, you can use the following methods that enable manual changing of the Selection state of items in the list-state: ### TrySelectAsync diff --git a/doc/Overview/Mvux/Overview.md b/doc/Overview/Mvux/Overview.md index a1dc2139a0..22b6a0913d 100644 --- a/doc/Overview/Mvux/Overview.md +++ b/doc/Overview/Mvux/Overview.md @@ -25,7 +25,7 @@ MVUX is a response to such situations and makes it easier to handle the above sc ## What is MVUX? -MVUX is an extension to the MVU design pattern, and leverages code generation in order to take advantage of the uniuqe data-binding engine of WinUI and the Uno Platform. +MVUX is an extension to the MVU design pattern, and leverages code generation in order to take advantage of the unique data-binding engine of WinUI and the Uno Platform. ### Model @@ -40,11 +40,11 @@ public partial record WeatherModel(IWeatherService WeatherService) } ``` -The `CurrentWeather` property represents a feed (`IFeed`) of `WeatherInfo` entities (for those familiar with [Reactive](https://reactivex.io/) this is similar in many ways to an `IObservable`). When the `CurrentWeather` property is accessed, an `IFeed` is created via the `Feed.Async` factory method, which will asynchronously call the `GetcurrentWeather` service. +The `CurrentWeather` property returns a feed (`IFeed`) of `WeatherInfo` entities (for those familiar with [Reactive](https://reactivex.io/) this is similar in many ways to an `IObservable`). When the `CurrentWeather` property is accessed, an `IFeed` is created via the `Feed.Async` factory method, which will asynchronously call the `GetCurrentWeather` service. ### View -The **View** is the UI, which can be written in XAML, C#, or a combination of the two, much as you would if you were using another design pattern. For example, the following can be used to data bind the `Text` property of a `TextBlock` to the `CurrentWeather.Temperature` property. +The **View** is the UI, which can be written in XAML, C#, or a combination of the two, in the same way that you would if you were using another design pattern. For example, the following can be used to data bind the `Text` property of a `TextBlock` to the `CurrentWeather.Temperature` property. ```xml ``` -If you're familiar with MVVM, the above XAML would look familiar, as it's the same XAML you would write if you had a viewmodel that exposed a `CurrentWeather` property that returns a `WeatherInfo` entity, with a `Temperature` property. +If you're familiar with MVVM, the above XAML would look familiar, as it's the same XAML you would write if you had a viewmodel that exposed a `CurrentWeather` property that returns an entity that has a `Temperature` property. -What's unique to MVUX is the additional information that `IFeed` exposes, such as when data is being loaded and whether there was an error loading the data. For this, we can leverage the `FeedView` control which is part of MVUX. +What's unique to MVUX is the additional information that `IFeed` exposes, such as when data is being loaded and whether there was an error loading the data. For this, we can leverage the MVUX `FeedView` control. ```xml ``` -As refreshing a feed is such a common scenario, the `FeedView` control exposes a `Refresh` command that removes the requirement to have a `Refresh` method on the `WeatherModel` and can be data bound, again to the `Command` property, of a `Button`, as follows: +As refreshing a feed is such a common scenario, the `FeedView` control exposes a `Refresh` command, that removes the requirement to have a `Refresh` method on the `WeatherModel` and can be data bound, again to the `Command` property, of a `Button`, as follows: ```xml [!NOTE] -> States keep the state of the data, so every new subscription to them, (such as awaiting them or binding them to an additional control, etc.), will use the data currently loaded in the State (if any). -To reload the data, a refresh is required. +States keep the current value of the data, so every new subscription to them, (such as awaiting them or binding them to an additional control, etc.), will use the data currently loaded in the state (if any). + +Like a feed, states can be reloaded, which will invoke the asynchronous operation that is used to create the state. States and Feeds are different in the following: 1. When subscribing to a state, the currently loaded value is going to be replayed. -2. A State provides the `Update` method that allows changing its current value. +2. A state provides the `Update` method that allows changing its current value. 3. States are attached to an owner and share the same lifetime as that owner. -4. The main usage of a State is for two-way bindings. +4. The main usage of a state is for two-way bindings. ## States are attached to their owner -Besides holding the state information, a reference to the bindable proxy is shared with the States so that when the View is closed and disposed of, it tunnels down to the States and the Models and makes them available for garbage collection. It shares the same lifetime as its owner. +Besides holding the state information, a reference to the Model is shared with the states so that when the View is closed and disposed of, it tunnels down to the states and the Models and makes them available for garbage collection. States share the same lifetime as their owner. -## How to use States +## How to use states -### Creation of States +### Creation of states #### From Tasks States are created slightly differently, they require a reference to the Model for caching and GC as mentioned above: ```csharp -public IState MainContact => State.Async(this, ContactsService.GetMainContact); +public record MainModel { + public IState MainContact => State.Async(this, ContactsService.GetMainContact); +} ``` -Where `GetMainContact` is a `ValueTask`, and takes a parameter of `CancellationToken`. +Where `GetMainContact` is a `ValueTask`, and takes a parameter of `CancellationToken`. The `this` parameter is the owner of the state, which is the Model in this case. #### From Async-Enumerables @@ -60,7 +62,7 @@ There are additional ways to create States so that you can update them at a late - With a synchronous initial value, and update it at a later stage: ```csharp - public IState CurrentCity => State.Value(this, () => new City("Montréal")); + public IState CurrentCity => State.Value(this, () => new City("Montreal")); ``` - Without any initial value: @@ -99,8 +101,8 @@ States are built to be cooperating with the data-binding engine. A State will au ```xml @@ -120,17 +122,15 @@ States are built to be cooperating with the data-binding engine. A State will au ``` - > [!NOTE] - > `BindableSliderModel` refers to the generated bindable proxy for the `SliderModel`. - +In this scenario, the `DataContext` is set to an instance of the `BindableSliderModel` class, which is the generated bindable proxy for the `SliderModel` record. + 1. When you run the app, moving the `Slider` instantly affects the upper `TextBox`; the `Silder.Value` property has a two-way binding with the `SliderValue` State, so any change to the Slider immediately updates the State value, which in turn affects the data-bound `TextBlock` on top: ![A video of the previous slider app in action](Assets/SliderApp-1.gif) +### Change data of a state -### Change data of a State - -To manually update the current value of a State, use its `Update` method. +To manually update the current value of a state, use its `Update` method. In this example we'll add the method `IncrementSlider` that gets the current value and increases it by one (if it doesn't exceed 100): @@ -148,8 +148,7 @@ public async ValueTask IncrementSlider(CancellationToken ct = default) The `updater` parameter of the `Update` method accepts a `Func`, where the input parameter provides the current value of the State when called, and the return parameter is the one to be returned and applied as the new value of the State, in our case we use the `incrementValue` [local function](https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/local-functions) to increment `currentValue` by one (or return `1` if the value exceeds `100`). -> [!TIP] -> There are additional methods that update the data of a State such as `Set` and `UpdateMessage`, explained [here](xref:Overview.Reactive.State#update-how-to-update-a-state). +There are additional methods that update the data of a State such as `Set` and `UpdateMessage`, explained [here](xref:Overview.Reactive.State#update-how-to-update-a-state). ### Commands @@ -177,7 +176,5 @@ This is what the result will look like: The source code for the sample app can be found [here](https://github.com/unoplatform/Uno.Samples/tree/master/UI/MvuxHowTos/SliderApp). -> [!TIP] -> Although it's important to use the `CancellationToken` to enable cancellation of Commands while they're being executed, this parameter is not mandatory, and Commands will work regardless. To learn more about Commands read the Commands section in [this article](xref:Overview.Reactive.InApps#commands). diff --git a/doc/Overview/Mvux/Tutorials/HowTo-ListFeed.md b/doc/Overview/Mvux/Tutorials/HowTo-ListFeed.md index b61f807113..9678dab530 100644 --- a/doc/Overview/Mvux/Tutorials/HowTo-ListFeed.md +++ b/doc/Overview/Mvux/Tutorials/HowTo-ListFeed.md @@ -87,7 +87,7 @@ This is similar in concept to an `IObservable>`, where an `IListF > ``` To make it possible to data bind to feeds, the MVUX analyzers read the `PeopleModel` -and generate a proxy type called `BindableWeatherModel`, +and generate a bindable proxy called `BindableWeatherModel`, which exposes properties that the View can data bind to. 1. Open the file `MainView.xaml` and add the following namespace to the XAML: diff --git a/doc/Overview/Mvux/Tutorials/HowTo-PushListFeed.md b/doc/Overview/Mvux/Tutorials/HowTo-PushListFeed.md index 923b32b413..ce1ad7630f 100644 --- a/doc/Overview/Mvux/Tutorials/HowTo-PushListFeed.md +++ b/doc/Overview/Mvux/Tutorials/HowTo-PushListFeed.md @@ -103,7 +103,7 @@ signaling the UI about the new data. > ``` To make it possible to data bind to feeds, the MVUX analyzers read the `StockMarketModel` -and generate a proxy type called `BindableStockMarketModel`, which exposes properties that the View can data bind to. +and generate a bindable proxy called `BindableStockMarketModel`, which exposes properties that the View can data bind to. 1. Open the file `MainView.xaml` and replace anything inside the `Page` element with the following code: diff --git a/doc/Overview/Mvux/Tutorials/HowTo-SimpleFeed.md b/doc/Overview/Mvux/Tutorials/HowTo-SimpleFeed.md index 690bb4dfec..c18a2447c2 100644 --- a/doc/Overview/Mvux/Tutorials/HowTo-SimpleFeed.md +++ b/doc/Overview/Mvux/Tutorials/HowTo-SimpleFeed.md @@ -70,7 +70,7 @@ In this tutorial you will learn how to create a project that uses MVUX with a co > WeatherInfo currentWeather = await this.CurrentWeather; > ``` > -> To make it possible to data bind to a feeds, the MVUX analyzers read the `WeatherModel` and generate a proxy type called `BindableWeatherModel`, which exposes properties that the View can data bind to. +> To make it possible to data bind to a feeds, the MVUX analyzers read the `WeatherModel` and generate a bindable proxy called `BindableWeatherModel`, which exposes properties that the View can data bind to. In this case the `BindableWeatherModel` exposes a `CurrentWeather` property that can be uses in a data binding expression the same way you would with a regular property that returns a `WeatherInfo` entity. 1. Open the file `MainView.xaml` and replace the `Page` contents with the following: diff --git a/doc/Overview/Mvux/Tutorials/HowTo-SimpleState.md b/doc/Overview/Mvux/Tutorials/HowTo-SimpleState.md index 50231ee486..0ef4a98b77 100644 --- a/doc/Overview/Mvux/Tutorials/HowTo-SimpleState.md +++ b/doc/Overview/Mvux/Tutorials/HowTo-SimpleState.md @@ -121,7 +121,7 @@ When the user edits the text in the `TextBox`, MVUXs data-binding adapters trans ![A screenshot of a breakpoint added in Visual Studio](../Assets/SimpleState-2.jpg) - MVUX's analyzers will read the `HallCrowdednessModel` and will generate a special model-proxy called `BindableHallCrowdednessModel`, which provides binding capabilities for the View and performs all Update message for us, to keep the `IState` up to date. + MVUX's analyzers will read the `HallCrowdednessModel` and will generate a special bindable proxy called `BindableHallCrowdednessModel`, which provides binding capabilities for the View and performs all Update message for us, to keep the `IState` up to date. In addition, MVUX reads the `Save` method, and generates in the bindable Model a command named `Save` that can be used from the View, which is invoked asynchronously. @@ -145,7 +145,7 @@ When the user edits the text in the `TextBox`, MVUXs data-binding adapters trans this.DataContext = new BindableHallCrowdednessModel(new HallCrowdednessService()); ``` - The `BindableHallCrowdednessModel` is a special MVUX-generated model proxy class that represents a mirror of the `HallCrowdednessModel` adding binding capabilities, for MVUX to be able to recreate and renew the model when an update message is sent by the view. + The `BindableHallCrowdednessModel` is a special MVUX-generated bindable proxy class that represents a mirror of the `HallCrowdednessModel` adding binding capabilities, for MVUX to be able to recreate and renew the model when an update message is sent by the view. 1. Click F5 to run the project