Skip to content

Instantly share code, notes, and snippets.

@jamsoft
Last active October 21, 2017 17:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamsoft/ca0e6851d787cbaed963 to your computer and use it in GitHub Desktop.
Save jamsoft/ca0e6851d787cbaed963 to your computer and use it in GitHub Desktop.
View Model Value Change Tracking in MvvmCross
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
}
}
public class PersonViewModel : MvxViewModel
{
public string Name { get; set; }
[IsDirtyMonitoring]
public string DisplayName { get; set; }
}
public async void Init(int id)
{
var data = await _myapiClient.GetPersonAsync(id);
Mapper.Map(data, this);
IsMonitoring = true;
}
using System;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class IsDirtyMonitoringAttribute : Attribute
{
}
/// <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;
}
}
@jamsoft
Copy link
Author

jamsoft commented Dec 19, 2015

You can read more here

@sergio11
Copy link

you are an artist!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment