Last active
October 21, 2017 17:01
-
-
Save jamsoft/ca0e6851d787cbaed963 to your computer and use it in GitHub Desktop.
View Model Value Change Tracking in MvvmCross
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyBaseVm : MvxViewModel | |
{ | |
#region IsDirty | |
private string _cleanHash; | |
protected string CleanHash | |
{ | |
get { return _cleanHash; } | |
} | |
private bool? _isDirtyMonitoring; | |
/// <summary> | |
/// Set this to true to start monitoring for changes to this object. | |
/// </summary> | |
public bool IsDirtyMonitoring | |
{ | |
get | |
{ | |
if (!_isDirtyMonitoring.HasValue) | |
{ | |
return false; | |
} | |
return _isDirtyMonitoring.Value; | |
} | |
set | |
{ | |
if (value) | |
{ | |
// starts the monitoring and stores non-nulls | |
// and ignores default bools values in binding | |
// situations where RaiseAllPropertyChanged() has | |
// been used | |
_isDirtyMonitoring = true; | |
// IsDirty = false; | |
_cleanHash = GetObjectHash(); | |
} | |
} | |
} | |
/// <summary> | |
/// Gets or sets a value indicating whether this instance is dirty. | |
/// </summary> | |
/// <value> | |
/// <c>true</c> if this instance is has changed; otherwise, <c>false</c>. | |
/// </value> | |
/// <remarks> | |
/// Monitoring an objects contents only starts when <seealso cref="IsDirtyMonitoring"></seealso> is explicitly set to true />. | |
/// </remarks> | |
public bool IsDirty | |
{ | |
get | |
{ | |
if (_cleanHash == null) | |
{ | |
return false; | |
} | |
return !string.IsNullOrEmpty(CleanHash) && GetObjectHash() != CleanHash; | |
} | |
} | |
/// <summary> | |
/// Gets the object hash from the objects property values. | |
/// </summary> | |
/// <returns>An MD5 hash representing the object</returns> | |
private string GetObjectHash() | |
{ | |
string md5; | |
try | |
{ | |
using (var ms = new MemoryStream()) | |
{ | |
using (StreamWriter sw = new StreamWriter(ms)) | |
{ | |
using (JsonWriter writer = new JsonTextWriter(sw)) | |
{ | |
JsonSerializer serializer = new JsonSerializer | |
{ | |
ContractResolver = IsDirtyViewModelJsonContractResolver.Instance | |
}; | |
serializer.Serialize(writer, this); | |
writer.Flush(); | |
serializer.DisposeIfDisposable(); | |
md5 = GetMd5Sum(ms.ToArray()); | |
} | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
// you should make this more specific really :) OK for testing | |
// but since ViewModels are often not directly created | |
// throwing exceptions isn't a great idea really | |
throw new Exception("Cannot calculate hash.", ex); | |
} | |
return md5; | |
} | |
/// <summary> | |
/// Gets the MD5 sum from the buffer byte data. | |
/// </summary> | |
/// <param name="buffer">The buffer.</param> | |
/// <returns>a string MD5 value</returns> | |
private static string GetMd5Sum(byte[] buffer) | |
{ | |
return MD5.GetHashString(buffer); | |
} | |
#endregion | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class PersonViewModel : MvxViewModel | |
{ | |
public string Name { get; set; } | |
[IsDirtyMonitoring] | |
public string DisplayName { get; set; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public async void Init(int id) | |
{ | |
var data = await _myapiClient.GetPersonAsync(id); | |
Mapper.Map(data, this); | |
IsMonitoring = true; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] | |
public class IsDirtyMonitoringAttribute : Attribute | |
{ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Provides customised contracts for JSON serialising objects for more efficient change tracking | |
/// </summary> | |
/// <seealso cref="Newtonsoft.Json.Serialization.DefaultContractResolver" /> | |
public class IsDirtyViewModelJsonContractResolver : DefaultContractResolver | |
{ | |
public static readonly IsDirtyViewModelJsonContractResolver Instance = new IsDirtyViewModelJsonContractResolver(); | |
public readonly List<Type> TypeFilterList = new List<Type> {typeof (IMvxCommand), typeof(ICommand)}; | |
/// <summary> | |
/// The list of property name strings to use to filter out untracked properties | |
/// </summary> | |
public readonly List<string> PropertyNameFilterList = new List<string> { "CleanHash", "RequestedBy", "IsDirty", "Validation", "Error" , "Valid" }; | |
/// <summary> | |
/// Creates properties for the given <see cref="T:Newtonsoft.Json.Serialization.JsonContract"/>. | |
/// if the object has any properties or fields marked with IsDirtyMonitoringAttribute only these properties will be included in the contract or the filters are applied to the complete base collection. | |
/// </summary> | |
/// <param name="type">The type to create properties for.</param>/// <param name="memberSerialization">The member serialization mode for the type.</param> | |
/// <returns> | |
/// Properties for the given <see cref="T:Newtonsoft.Json.Serialization.JsonContract"/>. | |
/// </returns> | |
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) | |
{ | |
var properties = type.GetProperties().Where(p => p.GetCustomAttribute(typeof (IsDirtyMonitoringAttribute)) != null).ToList(); | |
var fields = type.GetFields().Where(f => f.GetCustomAttribute(typeof (IsDirtyMonitoringAttribute)) != null).ToList(); | |
if (properties.Count > 0 || fields.Count > 0) | |
{ | |
List<JsonProperty> props = properties.Select(propInfo => CreateProperty(propInfo, memberSerialization)).ToList(); | |
props.AddRange(fields.Select(fieldInfo => CreateProperty(fieldInfo, memberSerialization))); | |
return props; | |
} | |
var filterprops = base.CreateProperties(type, memberSerialization) | |
.Where(p => | |
!PropertyNameFilterList.Any(f => p.PropertyName.Contains(f)) && | |
!TypeFilterList.Any(f => p.PropertyType.IsAssignableFrom(f)) | |
).ToList(); | |
return filterprops; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can read more here