dotnet / Comet

Comet is an MVU UIToolkit written in C#
MIT License
1.65k stars 117 forks source link

Added dirty checking to GenerateStateClass #199

Closed DevronB closed 3 years ago

DevronB commented 3 years ago

Added dirty checking to GenerateStateClass to allow model Sync and Comet property change notifications.

I've tested with below scenario and it works well.

Usage Sample: Update underlying model with comet.OriginalModel.Rides++; and call the state classes comet.SyncModels() method to notify Comet of changes. In reality this would be done outside of a Comet click handler and invoked when the developer knows the underlying model has changed outside of a Comet state/binding.

Usage:

[GenerateStateClass] //OR [assembly: GenerateStateClass(typeof(NS.POCOPlain))]
public class POCOPlain
{
    public int Rides { get; set; }
    public string CometTrain => "☄️".Repeat(Rides);
}

public class MainPage : View
{
    static readonly POCOPlain POCOPlain = new();
    readonly POCOPlainState comet = new(POCOPlain);

    [Body]
    View body()
        => new VStack {
            new Text(()=> $"({comet.Rides}) rides taken:{comet.CometTrain}")
                .Frame(width:300)
                .LineBreakMode(LineBreakMode.CharacterWrap)
                .FillHorizontal(),

            new Button("Ride the Comet Sync! ☄️", ()=>{
                //comet.Rides++;
                comet.OriginalModel.Rides++;
                comet.NotifyChanged();
                })
                .Frame(height:44)
                .Margin(8)
                .Background(Colors.Green)
                .Color(Colors.White)
                .TextAlignment(TextAlignment.Center)
                .FillHorizontal()
        };
}

Sample generated state class:

public partial class POCOPlainState :INotifyPropertyRead, IAutoImplemented 
{
    public event PropertyChangedEventHandler PropertyRead;
    public event PropertyChangedEventHandler PropertyChanged;

    public readonly GTApp.POCOPlain OriginalModel;

    bool shouldNotifyChanged = true;

    public POCOPlainState (GTApp.POCOPlain model)
    {
        OriginalModel = model;
        if (model is INotifyPropertyChanged inpc)
        {
            inpc.PropertyChanged += Inpc_PropertyChanged;
            shouldNotifyChanged = false;
        }
    }

    void Inpc_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        StateManager.OnPropertyChanged(sender, e.PropertyName, null);
        PropertyChanged?.Invoke(sender, e);
    }

    void NotifyPropertyChanged(object value, [CallerMemberName] string memberName = null){
        if (shouldNotifyChanged) {
            StateManager.OnPropertyChanged(this, memberName, value);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName));
        }
    }

    void NotifyPropertyRead([CallerMemberName] string memberName = null){ 
        InitDirtyProperty(memberName);
        StateManager.OnPropertyRead(this, memberName);
        PropertyRead?.Invoke(this, new PropertyChangedEventArgs(memberName));
    }

    /// <summary>
    /// Notifies Comet of all changes in the underlying model (OriginalModel) observed properties 
    /// </summary>
    public void NotifyChanged(){
        if (shouldNotifyChanged){
            if (ridesIsObserved){
                UpdateDirtyProperty("Rides");
            }
            if (cometTrainIsObserved){
                UpdateDirtyProperty("CometTrain");
            }
        }
    }

    void InitDirtyProperty([CallerMemberName] string memberName = null){
        switch (memberName){
            case "Rides":
                if (!ridesIsObserved){
                    ridesLastValue = OriginalModel.Rides;
                    ridesIsObserved = true;
                }
            break;
            case "CometTrain":
                if (!cometTrainIsObserved){
                    cometTrainLastValue = OriginalModel.CometTrain;
                    cometTrainIsObserved = true;
                }
            break;
        }
    }

    void UpdateDirtyProperty([CallerMemberName] string memberName = null){
        switch (memberName){
            case "Rides": 
                if (ridesIsDirty){
                    ridesLastValue = OriginalModel.Rides;
                    NotifyPropertyChanged(ridesLastValue, memberName);
                }
            break;
            case "CometTrain": 
                if (cometTrainIsDirty){
                    cometTrainLastValue = OriginalModel.CometTrain;
                    NotifyPropertyChanged(cometTrainLastValue, memberName);
                }
            break;
        }
    }

    bool ridesIsObserved = false;
    bool ridesIsDirty => ridesIsObserved && !OriginalModel.Rides.Equals(ridesLastValue);
    System.Int32 ridesLastValue;
    public System.Int32 Rides {
        get {
            NotifyPropertyRead();
            return OriginalModel.Rides;
        }
        set {
            OriginalModel.Rides = value;
            ridesLastValue = value;
            NotifyPropertyChanged(value);
        }
    }

    bool cometTrainIsObserved = false;
    bool cometTrainIsDirty => cometTrainIsObserved && !OriginalModel.CometTrain.Equals(cometTrainLastValue);
    System.String cometTrainLastValue;
    public System.String CometTrain {
        get {
            return OriginalModel.CometTrain;
        }
    }
}
DevronB commented 3 years ago

Removed NPR from GenerateStateClass