unoplatform / uno.extensions

Libraries to ease common developer tasks associated with building multi-platform mobile, desktop and web applications using Uno Platform or WinAppSDK.
https://platform.uno/
Other
73 stars 47 forks source link

[MVUX] Expose more properties inside the Data bindable property #1947

Closed pictos closed 10 months ago

pictos commented 1 year ago

What would you like to be added:

I would like to expose more bindable properties inside Data or in the FeedView in order to make it more robust. Let me explain with code. Let's say that I've a map page and I want to provide pins and routes to this map, and I have 2 distinct services that will provide this information.

So the code will look like something like...

<FeedView Source = {"Binding MapFeedModel"}>
    <DataTemplate>
        <map:mapControl PinsSource = {"Binding Data.FavoriteLocations"}  RoutePoints = {"Binding Data.Route"} />
    </DataTemplate>
</FeedView>

and my Model, will be:

public record MapModel(MapService MapService)
{
    public IListFeed<Pin> FavoriteLocations => Feed.Async(MapService.GetFavoritesLocations);

    public IListFeed<Point> Route => Feed.Async(MapService.GetRoute);
}

This way I can use the FeedView and have more than one property exposed and the extension part of the framework will handle all these async calls for me.

Today this is impossible since the FeedView just exposes one bindable property, that's Data. One solution is to merge both Pin and Point values in a single Model and pass them to the control, but it's not great to have to separate them before using, it should be done on the framework side.

Why is this needed:

This will allow more flexibility for mobile developers, where they can use more than one reactive property inside their control. I see this as a limitation for this kind of scenario.

For which Platform:

Anything else we need to know?

In order to make that happen, we can use Attributes and SourceGenerator, that will create the extra properties inside the Data bindable property that can be referenced on the view.

nickrandolph commented 12 months ago

@dr1rrb @francoistanguay is there a workaround for this? Or a suggestion on how we can provide a better experience where there are multiple feeds of data that need to be displayed in the same UI such as this scenario

francoistanguay commented 12 months ago

Have you looked into Feed.Combine?

ajpinedam commented 11 months ago

I tested the Feed.Combine, and even though it's working fine (successfully combining the Two FeedsI am not able to bind the data on aFeedView`

For this Feed

public IFeed<WeatherInfo> CurrentWeather =>
    Feed.Async(WeatherService.GetCurrentWeather);

public IFeed<CounterValue> CurrentCount =>
    Feed.Async(CounterService.CountOne);

This way

public IFeed<(WeatherInfo, CounterValue)> FeedCombined => 
    Feed.Combine(CurrentWeather, CurrentCount);

Or this, using named Tuples

public IFeed<(WeatherInfo WeatherInfo, CounterValue CounterValue)> FeedCombined => 
    Feed.Combine(CurrentWeather, CurrentCount);

I tried many things on the XAML, including

<mvux:FeedView Source="{Binding FeedCombined}"
               x:Name="FeedViewWeather">
  <DataTemplate>
    <StackPanel>
      <TextBlock Text="Feeds Combined" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding WeatherInfo.Temperature}" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding CounterValue.Value}" />
    </StackPanel>
  </DataTemplate>
</mvux:FeedView>

and

<mvux:FeedView Source="{Binding FeedCombined}"
               x:Name="FeedViewWeather">
  <DataTemplate>
    <StackPanel>
      <TextBlock Text="Feeds Combined" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding Item1.Temperature}" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding Item2Value}" />
    </StackPanel>
  </DataTemplate>
</mvux:FeedView>

No matter what I tried, I could not get the value of the CombinedFeed to be displayed.

Then I saw a couple of Binding error messages.

uno_feed_binding_expression_not_working_2

I keep investigating, and it seems it's not possible (maybe some of you already knew this) that it's not possible to use Binding expressions with Tuples, and the object resulting from the Feed.Combine() method is exactly that, a Tuple.

cc'ing @dr1rrb, maybe he can provide some light 💡

ajpinedam commented 11 months ago

Actually, I was able to do the bindings but used a third entity.

public IFeed<WeatherInfo> CurrentWeather =>
    Feed.Async(WeatherService.GetCurrentWeather);

public IFeed<CounterValue> CurrentCount =>
    Feed.Async(CounterService.CountOne);

public IFeed<DataCombined> FeedCombined => 
    Feed.Combine(CurrentWeather, CurrentCount)
        .Select(x => new DataCombined(x.Item1, x.Item2 ));

public record DataCombined(WeatherInfo Weather, CounterValue Counter);

And with this XAML

<mvux:FeedView Source="{Binding FeedCombined}"
               x:Name="FeedViewWeather">
  <DataTemplate>
    <StackPanel>
      <TextBlock Text="Feeds Combined" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding Weather.Temperature}" />
      <TextBlock FontSize="28" DataContext="{Binding Data}" Text="{Binding Counter.Value}" />
    </StackPanel>
  </DataTemplate>
</mvux:FeedView>

Is this the way to go? If so, I will look into adding this to the docs.

image

francoistanguay commented 11 months ago

@dr1rrb Do we support Tuples in code gen as well as regular records or just records? If not, should we add it to the backlog?

dr1rrb commented 10 months ago

I think here the issue is not about the code gen of mvux, but more the fact that names in tuples are only at compilation time, the actual type used Tuple<T1, T2> has 2 properties Item1 and Item2 on which you should be able to data-bind.

About the original issue, the issue is that if you have :

public record MyEntity(string Id, string Name);

// Model
public IFeed<string> Name { get; }
public IFeed<MyEntity> Entity { get; }

If we try to somehow aggregate properties in the Data we could have name conflicts (on Name in my example) unless if we add a complete new layer (e.g. Data.Entity.Name) ... which sounds verbose and somehow weird when you are using a FeedView Source={Binding Entity}. (The Data is already source a lot of headaches :/)

BUT

You can already access "outside" of your Data: There is also a property named Parent which gives you access to the DataContext of the FeedView itself, i.e. the BindableModel.

So, based on your example, if you don't want to use Feed.Combine (because it's acceptable for you to display partial data with only Route for instance), you can write:

<FeedView Source="{Binding Route}">
    <DataTemplate>
        <map:mapControl PinsSource="{Binding Parent.FavoriteLocations}"  RoutePoints="{Binding Data}" />
    </DataTemplate>
</FeedView>

Workaround