Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Automated Setter method for Two-Way Binding and Default BindableProperties #345

Open
8 tasks
egvijayanand opened this issue Dec 4, 2024 · 5 comments
Assignees
Labels
approved champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail

Comments

@egvijayanand
Copy link

egvijayanand commented Dec 4, 2024

Feature name

Automated Setter method for Two-Way Binding and Default BindableProperties

Link to discussion

#343

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

The Bind method behaves differently in the Typed Bindings approach compared to the classic String-based one.

In the classic approach, it is enough to specify the property to be bound. If the binding property's mode is two-way, the back update to the ViewModel property will happen automatically (similar to that of an XAML-based definition).

In the improved Typed Bindings approach, getters and setters are separated. Even for the default implementation, a setter method must be written to establish two-way data binding. This also didn't allow the use of default bindable properties.

A solution has now been found to address this gap using C# Expressions, without affecting performance or incurring runtime overhead as with reflection.

The resulting behavior will align with the classic model while being as extensible as Typed Binding.

The implementation will only take effect if the user has not defined a setter method. So it's backward compatible.

Motivation

Similar binding behavior across all approaches, permitting skill reuse.

Detailed Design

Defining a default setter method using C# Expressions:

A working sample is available here - https://github.com/egvijayanand/markup-issue-272

if (setter is null)
{
    // Include the setter only if it is defined as two-way or if the mode is overridden.
    if (targetProperty.DefaultBindingMode == BindingMode.TwoWay || mode == BindingMode.TwoWay)
    {
        // Already defined in the toolkit to retrieve the member name - To be reused
        // Assuming MemberExpression for sample
        var propertyName = ((MemberExpression)getter.Body).Member.Name;
        var param1 = Expression.Parameter(typeof(TBindingContext), "context");
        var param2 = Expression.Parameter(typeof(TSource), "value");
        var memExp = Expression.Property(param1, propertyName);
        var assignExp = Expression.Assign(memExp, param2);
        // TODO: Need to check whether the action can be a static lambda
        var action = Expression.Lambda<Action<TBindingContext, TSource>>(assignExp, [param1, param2]).Compile();

        // Structure of the generated definition
        // setter = (TBindingContext context, TSource value) => context.Property = value;
        setter = action;
    }
}

Usage Syntax

// As you can observe, the resulting code is quite simple. Easy to understand.
// This will improve the overall productivity

// Existing

// Requires the target property even if it is the default one, and a setter definition if it is two-way binding.
new Picker().Bind(Picker.SelectedIndexProperty, static (MyViewModel vm) => vm.Index, static (MyViewModel vm, int value) => vm.Index = value);

// Proposed

// SelectedIndexProperty is the default bindable property for Picker control
// It's two-way binding by default
new Picker().Bind(static (MyViewModel vm) => vm.Index);

Drawbacks

I don't think of any as it's compiled. Maybe a different perspective can fill-in.

Alternatives

Unresolved Questions

Static lambda for the setter action method to maintain the performance.

Explore the docs to ascertain whether the compiled method is static or if any further definition is required to make it static.

@egvijayanand egvijayanand added new proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail labels Dec 4, 2024
@egvijayanand
Copy link
Author

For default bindable properties, method overloads can be written without the bindable property parameter. Once resolved, these overloads will call the existing implementation with the resolved value.

@egvijayanand
Copy link
Author

The setter method generated is type-safe, as it uses the generic type parameters of the method.

@brminnick brminnick added needs discussion The team will aim to discuss this at the next monthly standup champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature labels Dec 4, 2024
@egvijayanand
Copy link
Author

@brminnick I have a few tasks planned for this week, so I'll take this up early next week.

@brminnick brminnick added approved and removed new needs discussion The team will aim to discuss this at the next monthly standup labels Jan 9, 2025
@brminnick
Copy link
Collaborator

Hey Vijay! Good news - this Proposal was approved in our January Standup!!

You are cleared to continue implementing this PR 🙌

@egvijayanand
Copy link
Author

Hey Vijay! Good news - this Proposal was approved in our January Standup!!

You are cleared to continue implementing this PR 🙌

That's great to hear. I began the work but got tied up with other commitments, so I couldn't focus much on it. Now that it's approved, I will prioritize this and submit the PR at the earliest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail
Projects
None yet
Development

No branches or pull requests

2 participants