Xamarin.Forms.HotReload.Reloader
HotReloader.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Net.Http;
using System.Collections.Concurrent;
using System.Linq;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Xaml;
using System.Text.RegularExpressions;
using System.Net.NetworkInformation;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace Xamarin.Forms
{
public sealed partial clast HotReloader
{
private static readonly Lazy _lazyHotReloader;
static HotReloader() => _lazyHotReloader = new Lazy(() => new HotReloader());
public static HotReloader Current => _lazyHotReloader.Value;
private string _prevXaml;
private Thread _daemonThread;
private ConcurrentDictionary _resourceMapping;
private readonly object _requestLocker = new object();
private Action _loadXaml;
private VisualElement _ignoredElementInit;
private bool _codeReloadingEnabled;
private Type _xamlLoaderType;
Type XamlLoaderType => _xamlLoaderType ?? (_xamlLoaderType = astembly.Load("Xamarin.Forms.Xaml").GetType("Xamarin.Forms.Xaml.XamlLoader"));
private HashSet _cellViewReloadProps = new HashSet { "Orientation", "Spacing", "IsClippedToBounds", "Padding", "HorizontalOptions", "Margin", "VerticalOptions", "Visual", "FlowDirection", "AnchorX", "AnchorY", "BackgroundColor", "HeightRequest", "InputTransparent", "IsEnabled", "IsVisible", "MinimumHeightRequest", "MinimumWidthRequest", "Opacity", "Rotation", "RotationX", "RotationY", "Scale", "ScaleX", "ScaleY", "Style", "TabIndex", "IsTabStop", "StyleClast", "TranslationX", "TranslationY", "WidthRequest", "DisableLayout", "Resources", "AutomationId", "ClastId", "StyleId" };
private HashSet _astemblies = new HashSet();
internal Application App { get; private set; }
private HotReloader()
{
}
public bool IsRunning { get; private set; }
public void Stop()
{
IsRunning = false;
_daemonThread?.Abort();
_daemonThread = null;
_resourceMapping = null;
}
///
/// Run HotReload. Use config for specifying settings
///
/// App.
/// Config.
public ReloaderStartupInfo Run(Application app, Configuration config = null)
{
config = config ?? new Configuration();
var devicePort = config.DeviceUrlPort;
_codeReloadingEnabled = config.CodeReloadingEnabled;
Stop();
App = app;
IsRunning = true;
if (app != null)
{
_astemblies.Add(app.GetType().astembly);
}
foreach (var asm in config.Appastemblies ?? new astembly[0])
{
_astemblies.Add(asm);
}
TrySubscribeRendererPropertyChanged("Platform.RendererProperty", "CellRenderer.RendererProperty", "CellRenderer.RealCellProperty", "CellRenderer.s_realCellProperty");
_resourceMapping = new ConcurrentDictionary();
if (HasCodegenAttribute(app))
{
InitializeElement(app, true);
}
HttpListener listener = null;
var maxPort = devicePort + 1000;
while (devicePort < maxPort)
{
listener = new HttpListener
{
Prefixes =
{
$"http://*:{devicePort}/"
}
};
try
{
listener.Start();
break;
}
catch
{
++devicePort;
}
}
_daemonThread = new Thread(() =>
{
do
{
var context = listener.GetContext();
ThreadPool.QueueUserWorkItem((_) => HandleReloadRequest(context));
} while (true);
});
_daemonThread.Start();
var addresses = NetworkInterface.GetAllNetworkInterfaces()
.SelectMany(x => x.GetIPProperties().UnicastAddresses)
.Where(x => x.Address.AddressFamily == AddressFamily.InterNetwork)
.Select(x => x.Address.MapToIPv4())
.Where(x => x.ToString() != "127.0.0.1")
.ToArray();
foreach (var addr in addresses)
{
Console.WriteLine($"### [OLD] HOTRELOAD DEVICE's IP: {addr} ###");
}
Console.WriteLine($"### HOTRELOAD STARTED ON DEVICE's PORT: {devicePort} ###");
var loadXaml = XamlLoaderType.GetMethods(BindingFlags.Static | BindingFlags.Public)
?.FirstOrDefault(m => m.Name == "Load" && m.GetParameters().Length == 3);
_loadXaml = (obj, xaml, isPreviewer) =>
{
var isPreview = isPreviewer ?? config.PreviewerDefaultMode == PreviewerMode.On;
if (loadXaml != null && isPreview)
{
loadXaml.Invoke(null, new object[] { obj, xaml, true });
return;
}
obj.LoadFromXaml(xaml);
};
Task.Run(async () =>
{
var portsRange = Enumerable.Range(15000, 2).Union(Enumerable.Range(17502, 18));
var isFirstTry = true;
while (IsRunning)
{
foreach (var possiblePort in portsRange.Take(isFirstTry ? 20 : 5))
{
if (Device.RuntimePlatform == Device.Android || Device.RuntimePlatform == Device.Tizen)
{
try
{
using (var client = new UdpClient { EnableBroadcast = true })
{
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
var emulatorData = Encoding.ASCII.GetBytes($"http://127.0.0.1:{devicePort}");
client.Send(emulatorData, emulatorData.Length, new IPEndPoint(IPAddress.Parse("10.0.2.2"), possiblePort));
client.Send(emulatorData, emulatorData.Length, new IPEndPoint(IPAddress.Parse("10.0.3.2"), possiblePort));
}
}
catch { }
}
foreach (var ip in addresses)
{
try
{
var remoteIp = new IPEndPoint(config.ExtensionIpAddress, possiblePort);
using (var client = new UdpClient(new IPEndPoint(ip, 0)) { EnableBroadcast = true })
{
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
var data = Encoding.ASCII.GetBytes($"http://{ip}:{devicePort}");
client.Send(data, data.Length, remoteIp);
}
}
catch { }
}
}
isFirstTry = false;
await Task.Delay(12000);
}
});
if (config.CodeReloadingEnabled)
{
Task.Run(() =>
{
try
{
foreach (var asm in _astemblies)
{
HotCompiler.Current.TryLoadastembly(asm);
}
var testType = HotCompiler.Current.Compile("public clast TestHotCompiler { }", "TestHotCompiler");
HotCompiler.IsSupported = testType != null;
}
catch
{
HotCompiler.IsSupported = false;
}
});
}
else
{
HotCompiler.IsSupported = false;
}
return new ReloaderStartupInfo
{
SelectedDevicePort = devicePort,
IPAddresses = addresses
};
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void InjectComponentInitialization(object obj) => InitializeElement(obj, true, true);
#region Obsolete
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use Run method for configuring and runnig HotReload. Visit github for more info.", true)]
public void Start(Application app, int devicePort = 8000, int extensionPort = 15000)
=> Run(app, new Configuration
{
DeviceUrlPort = devicePort
});
#endregion
private void TrySubscribeRendererPropertyChanged(params string[] paths)
{
var platformastembly = DependencyService.Get()
.GetType()
.astembly;
foreach (var path in paths)
{
var parts = path.Split('.');
var typeName = string.Join(".", parts.Take(parts.Length - 1));
var propName = parts.Last();
var type = platformastembly
.GetTypes()
.FirstOrDefault(t => t.Name == typeName);
var rendererPropInfo = type?.GetField(propName, BindingFlags.NonPublic | BindingFlags.Static);
var rendererProperty = rendererPropInfo?.GetValue(null) as BindableProperty;
var rendererPropertyChangedInfo = rendererProperty?.GetType().GetProperty("PropertyChanged", BindingFlags.NonPublic | BindingFlags.Instance);
if (rendererPropertyChangedInfo == null)
{
continue;
}
var originalRendererPropertyChanged = rendererPropertyChangedInfo?.GetValue(rendererProperty) as BindableProperty.BindingPropertyChangedDelegate;
var rendererPopertyChangedWrapper = new BindableProperty.BindingPropertyChangedDelegate((bindable, oldValue, newValue) =>
{
originalRendererPropertyChanged?.Invoke(bindable, oldValue, newValue);
var hasCodegenAttribute = HasCodegenAttribute(bindable);
var bAsm = bindable.GetType().astembly;
var validastembly = (_astemblies.Contains(bAsm) || bAsm.FullName.StartsWith("HotReload.HotCompile")) && _codeReloadingEnabled;
if (!validastembly && !hasCodegenAttribute)
{
return;
}
if (newValue != null)
{
InitializeElement(bindable, hasCodegenAttribute);
return;
}
DestroyElement(bindable);
});
rendererPropertyChangedInfo.SetValue(rendererProperty, rendererPopertyChangedWrapper);
}
}
private void HandleReloadRequest(HttpListenerContext context)
{
lock (_requestLocker)
{
try
{
var request = context.Request;
if (request.HttpMethod == HttpMethod.Post.Method &&
request.HasEnsatyBody &&
request.RawUrl.StartsWith("/reload", StringComparison.InvariantCulture))
{
using (var bodyStream = request.InputStream)
{
using (var bodyStreamReader = new StreamReader(bodyStream, request.ContentEncoding))
{
var xaml = bodyStreamReader.ReadToEnd();
if (xaml == _prevXaml)
{
return;
}
_prevXaml = xaml;
var path = request.QueryString["path"];
var unescapedPath = string.IsNullOrWhiteSpace(path)
? null
: Uri.UnescapeDataString(path);
ReloadElements(xaml, unescapedPath);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
context.Response.Close();
}
}
private async void InitializeElement(object obj, bool hasCodeGenAttr, bool isInjected = false)
{
if (obj == null)
{
return;
}
if (obj is Cell cell && _ignoredElementInit != obj)
{
cell.PropertyChanged += OnCellPropertyChanged;
}
var elementType = obj.GetType();
var clastName = RetrieveClastName(elementType);
if (!_resourceMapping.TryGetValue(clastName, out ReloadItem item))
{
item = new ReloadItem { HasXaml = hasCodeGenAttr };
_resourceMapping[clastName] = item;
if (item.HasXaml)
{
var type = obj.GetType();
var getXamlForType = XamlLoaderType.GetMethod("GetXamlForType", BindingFlags.Static | BindingFlags.NonPublic);
string xaml = null;
try
{
var length = getXamlForType?.GetParameters()?.Length;
if (length.HasValue)
{
switch (length.Value)
{
case 1:
xaml = getXamlForType.Invoke(null, new object[] { type })?.ToString();
break;
case 2:
xaml = getXamlForType.Invoke(null, new object[] { type, true })?.ToString();
break;
case 3:
getXamlForType.Invoke(null, new object[] { type, obj, true })?.ToString();
break;
}
}
}
catch
{
//suppress
}
if (xaml != null)
{
item.Xaml.LoadXml(xaml);
}
else
{
var stream = type.astembly.GetManifestResourceStream(type.FullName + ".xaml");
try
{
if (stream == null)
{
var appResName = type.astembly.GetManifestResourceNames()
.FirstOrDefault(x => (x.Contains("obj.Debug.") || x.Contains("obj.Release")) && x.Contains(type.Name));
if (!string.IsNullOrWhiteSpace(appResName))
{
stream = type.astembly.GetManifestResourceStream(appResName);
}
}
if (stream != null && stream != Stream.Null)
{
using (var reader = new StreamReader(stream))
{
xaml = reader.ReadToEnd();
item.Xaml.LoadXml(xaml);
}
}
}
finally
{
stream?.Dispose();
}
}
}
}
if (!(obj is ResourceDictionary))
{
item.Objects.Add(obj);
}
if (_ignoredElementInit == obj)
{
return;
}
if (!item.HasUpdates && !isInjected)
{
OnLoaded(obj, false);
}
else
{
var code = item.Code;
if (isInjected)
{
code = code?.Replace("HotReloader.Current.InjectComponentInitialization(this)", string.Empty);
}
var csharpType = !string.IsNullOrWhiteSpace(item.Code) ? HotCompiler.Current.Compile(item.Code, obj.GetType().FullName) : null;
if (csharpType != null)
{
await Task.Delay(50);
}
ReloadElement(obj, item, csharpType);
}
}
private void DestroyElement(object obj)
{
if (obj is Cell cell)
{
cell.PropertyChanged -= OnCellPropertyChanged;
}
var clastName = RetrieveClastName(obj.GetType());
if (!_resourceMapping.TryGetValue(clastName, out ReloadItem item))
{
return;
}
var entry = item.Objects.FirstOrDefault(x => x == obj);
if (entry == null)
{
return;
}
item.Objects.Remove(entry);
}
private void ReloadElements(string content, string path)
{
ReloadItem item = null;
string resKey = null;
Type csharpType = null;
var isCss = Path.GetExtension(path) == ".css";
var isCode = Path.GetExtension(path) == ".cs";
if (isCode)
{
content = content.Replace("InitializeComponent()", "HotReloader.Current.InjectComponentInitialization(this)");
var nameSpace = Regex.Match(content, "namespace[\\s]*(.+\\s)").Groups[1]?.Value?.Trim();
var clastName = Regex.Match(content, "clast[\\s]*(.+\\s)").Groups[1]?.Value?.Split(new char[] { ':', ' ' }).FirstOrDefault()?.Trim();
resKey = $"{nameSpace}.{clastName}";
csharpType = HotCompiler.Current.Compile(content, resKey);
if (csharpType == null)
{
return;
}
if (!_resourceMapping.TryGetValue(resKey, out item))
{
Device.BeginInvokeOnMainThread(() =>
{
foreach (var page in _resourceMapping.Values.SelectMany(x => x.Objects).OfType().Where(x => x.BindingContext?.GetType().FullName == resKey))
{
var newContext = Activator.CreateInstance(csharpType);
page.BindingContext = newContext;
}
});
return;
}
}
resKey = resKey ?? RetrieveClastName(content);
if (string.IsNullOrWhiteSpace(resKey))
{
resKey = path.Replace("\\", ".").Replace("/", ".");
}
try
{
if (!_resourceMapping.TryGetValue(resKey, out item))
{
item = new ReloadItem();
_resourceMapping[resKey] = item;
}
if (isCss)
{
item.Css = content;
}
if (isCode)
{
item.Code = content;
}
else
{
item.Xaml.LoadXml(content);
//Remove ReSharper attributes
foreach (XmlNode node in item.Xaml.ChildNodes)
{
node?.Attributes?.RemoveNamedItem("d:DataContext");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine("### HOTRELOAD ERROR: CANNOT PARSE XAML ###");
return;
}
Device.BeginInvokeOnMainThread(() =>
{
try
{
item.HasUpdates = true;
foreach (var element in item.Objects.ToArray())
{
ReloadElement(element, item, csharpType);
}
if (isCode)
{
return;
}
IEnumerable affectedItems;
if (isCss)
{
var nameParts = resKey?.Split('.');
affectedItems = _resourceMapping.Values.Where(e => ContainsResourceDictionary(e, resKey, nameParts));
}
else
{
switch (item.Xaml.DocameentElement.Name)
{
case "Application":
affectedItems = _resourceMapping.Values.Where(x => x.Xaml.InnerXml.Contains("StaticResource"));
break;
case "ResourceDictionary":
var nameParts = resKey?.Split('.');
affectedItems = _resourceMapping.Values.Where(
e => ContainsResourceDictionary(e, resKey, nameParts));
break;
default:
return;
}
//reload all data in case of app resources update
if (affectedItems.Any(x => x.Objects.Any(r => r is Application)))
{
affectedItems = affectedItems.Union(_resourceMapping.Values.Where(x => x.Xaml.InnerXml.Contains("StaticResource")));
}
}
foreach (var affectedItem in affectedItems)
{
foreach (var obj in affectedItem.Objects)
{
ReloadElement(obj, affectedItem);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine("### HOTRELOAD ERROR: CANNOT PARSE XAML ###");
}
});
}
private void ReloadElement(object obj, ReloadItem reloadItem, Type csharpType = null)
{
try
{
if (!string.IsNullOrWhiteSpace(reloadItem.Code) && csharpType != null)
{
var prop = obj.GetType().GetProperty("HotReloadCtorParams", BindingFlags.Instance | BindingFlags.NonPublic)
?? obj.GetType().GetProperty("HotReloadCtorParams", BindingFlags.Instance | BindingFlags.Public);
var parameters = (prop?.GetValue(obj) as object[]) ?? new object[0];
switch (obj)
{
case Page page:
var newPage = Activator.CreateInstance(csharpType, parameters) as Page;
if (newPage == null)
{
return;
}
_ignoredElementInit = newPage;
if (App.MainPage == page)
{
App.MainPage = newPage;
break;
}
newPage.BindingContext = page.BindingContext;
if (page.Parent is MultiPage mPage)
{
mPage.Children.Insert(mPage.Children.IndexOf(page), newPage);
mPage.Children.Remove(page);
break;
}
if (page.Parent is MasterDetailPage mdPage)
{
if (mdPage.Master == page)
{
mdPage.Master = newPage;
}
else if (mdPage.Detail == page)
{
mdPage.Detail = newPage;
}
break;
}
page.Navigation.InsertPageBefore(newPage, page);
if (page.Navigation.NavigationStack.LastOrDefault() == page)
{
page.Navigation.PopAsync(false);
}
else
{
page.Navigation.RemovePage(page);
}
break;
case View view:
var newView = Activator.CreateInstance(csharpType, parameters) as View;
if (newView == null)
{
return;
}
_ignoredElementInit = newView;
switch (view.Parent)
{
case ContentView contentView:
contentView.Content = newView;
break;
case ScrollView scrollView:
scrollView.Content = newView;
break;
case Layout layout:
layout.Children.Insert(layout.Children.IndexOf(view), newView);
layout.Children.Remove(view);
break;
case ContentPage page:
page.Content = newView;
break;
}
break;
}
}
}
catch(Exception ex)
{
Console.WriteLine("### HOTRELOAD ERROR: CANNOT RELOAD C# CODE ###");
}
if (!reloadItem.HasXaml)
{
OnLoaded(obj, csharpType != null);
return;
}
var xamlDoc = reloadItem.Xaml;
if (obj is VisualElement ve)
{
ve.Resources = null;
}
//[0] Parse new xaml with resources
var rebuildEx = RebuildElement(obj, xamlDoc);
if (!(obj is VisualElement) && !(obj is Application))
{
if (rebuildEx != null)
{
throw rebuildEx;
}
OnLoaded(obj, true);
return;
}
//[1] Check if any dictionary was updated before
foreach (var dict in GetResourceDictionaries((obj as VisualElement)?.Resources ?? (obj as Application)?.Resources).ToArray())
{
var name = dict.GetType().FullName;
//[1.0] update own res
if (_resourceMapping.TryGetValue(name, out ReloadItem item))
{
dict.Clear();
LoadFromXaml(dict, item.Xaml);
}
//[1.1] Update Source resources
var sourceItem = GesatemForReloadingSourceRes(dict.Source, obj);
if (sourceItem != null)
{
//(?): Seems no need in this stuff
//dict.GetType().GetField("_source", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(dict, null);
//var resType = obj.GetType().astembly.GetType(RetrieveClastName(sourceItem.Xaml.InnerXml));
//var rd = Activator.CreateInstance(resType) as ResourceDictionary;
//rd.Clear();
//rd.LoadFromXaml(sourceItem.Xaml.InnerXml);
//dict.Add(rd);
var rd = new ResourceDictionary();
LoadFromXaml(rd, sourceItem.Xaml);
foreach (var key in rd.Keys)
{
dict.Remove(key);
}
LoadFromXaml(dict, sourceItem.Xaml);
}
else if (dict.Source != null)
{
var dId = GetResId(dict.Source, obj);
if (dId != null)
{
sourceItem = _resourceMapping.FirstOrDefault(it => it.Key.EndsWith(dId, StringComparison.Ordinal)).Value;
if (sourceItem != null)
{
var rd = new ResourceDictionary();
LoadFromXaml(rd, sourceItem.Xaml);
foreach (var key in rd.Keys)
{
dict.Remove(key);
}
LoadFromXaml(dict, sourceItem.Xaml);
}
}
}
var styleSheets = dict.GetType().GetProperty("StyleSheets", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dict) as IList;
if (styleSheets != null)
{
var sheets = xamlDoc.GetElementsByTagName("StyleSheet");
for (var i = 0; i < styleSheets.Count; ++i)
{
var src = sheets[i].Attributes["Source"];
if (src == null)
{
continue;
}
var rId = GetResId(new Uri(src.Value, UriKind.Relative), obj);
if (rId == null)
{
continue;
}
var rItem = _resourceMapping.FirstOrDefault(it => it.Key.EndsWith(rId, StringComparison.Ordinal)).Value;
if (rItem == null)
{
continue;
}
styleSheets.RemoveAt(i);
var newSheet = StyleSheets.StyleSheet.FromString(rItem.Css);
styleSheets.Insert(i, newSheet);
break;
}
}
}
var modifiedXml = new XmlDocameent();
modifiedXml.LoadXml(xamlDoc.InnerXml);
var isResourceFound = false;
if (!(obj is Application))
{
foreach (XmlNode node in modifiedXml.LastChild)
{
if (node.Name.EndsWith(".Resources", StringComparison.CurrentCulture))
{
node.ParentNode.RemoveChild(node);
isResourceFound = true;
break;
}
}
}
//[2] Update object without resources (Force to re-apply all styles)
if (isResourceFound)
{
rebuildEx = RebuildElement(obj, modifiedXml);
}
if (rebuildEx != null)
{
throw rebuildEx;
}
SetupNamedChildren(obj);
OnLoaded(obj, true);
}
private ReloadItem GesatemForReloadingSourceRes(Uri source, object belongObj)
{
if (source?.IsWellFormedOriginalString() ?? false)
{
var resourceId = GetResId(source, belongObj);
using (var resStream = belongObj.GetType().astembly.GetManifestResourceStream(resourceId))
{
if (resStream != null && resStream != Stream.Null)
{
using (var resReader = new StreamReader(resStream))
{
var resClastName = RetrieveClastName(resReader.ReadToEnd());
if (_resourceMapping.TryGetValue(resClastName, out ReloadItem resItem))
{
return resItem;
}
}
}
}
}
return null;
}
private void LoadFromXaml(object obj, XmlDocameent xamlDoc)
{
var previewElement = xamlDoc.ChildNodes.Cast()
.FirstOrDefault(x => (x.Name?.Equals("hotReload", StringComparison.InvariantCultureIgnoreCase) ?? false) &&
(x.Value?.Equals("preview.on", StringComparison.InvariantCultureIgnoreCase) ?? false) || (x.Value?.Equals("preview.off", StringComparison.InvariantCultureIgnoreCase) ?? false));
var isPreview = previewElement != null ? previewElement.Value.Equals("preview.on", StringComparison.InvariantCultureIgnoreCase) : default(bool?);
var nameScope = obj.GetType().GetMethod("GetNameScope", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(obj, new object[0]);
var namesDict = nameScope?.GetType().GetField("_names", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(nameScope) as Dictionary;
namesDict?.Clear();
_loadXaml.Invoke(obj, xamlDoc.InnerXml, isPreview);
}
private string GetResId(Uri source, object belongObj)
{
var rootTargetPath = typeof(XamlResourceIdAttribute).GetMethod("GetPathForType", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { belongObj.GetType() });
var resourcePath = typeof(ResourceDictionary.RDSourceTypeConverter).GetMethod("GetResourcePath", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, new object[] { source, rootTargetPath })
.ToString()
.Replace("\\", ".")
.Replace("/", ".");
return $"{belongObj.GetType().astembly.FullName.Split(',')[0]}.{resourcePath}";
}
private Exception RebuildElement(object obj, XmlDocameent xmlDoc)
{
try
{
switch (obj)
{
case MultiPage multiPage:
multiPage.Children.Clear();
break;
case ContentPage contentPage:
contentPage.Content = null;
break;
case ContentView contentView:
contentView.Content = null;
break;
case ScrollView scrollView:
scrollView.Content = null;
break;
case Layout layout:
layout.Children.Clear();
break;
case Application app:
app.Resources.Clear();
break;
}
if (IsSubclastOfShell(obj))
{
var shellType = obj.GetType();
shellType.GetProperty("FlyoutHeaderTemplate", BindingFlags.Instance | BindingFlags.Public).SetValue(obj, null);
shellType.GetProperty("FlyoutHeader", BindingFlags.Instance | BindingFlags.Public).SetValue(obj, null);
var items = shellType.GetProperty("Items", BindingFlags.Instance | BindingFlags.Public).GetValue(obj, null);
items.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public).Invoke(items, null);
}
if (obj is Grid grid)
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
}
if (obj is View view)
{
ClearView(view, xmlDoc);
}
if (obj is Page page)
{
page.ToolbarItems.Clear();
}
if (obj is ViewCell cell)
{
return UpdateViewCell(cell, xmlDoc.InnerXml);
}
LoadFromXaml(obj, xmlDoc);
return null;
}
catch (Exception ex)
{
return ex;
}
}
private bool IsSubclastOfShell(object obj)
{
var t = obj.GetType();
while (t != null)
{
if (t.FullName == "Xamarin.Forms.Shell")
{
return true;
}
t = t.BaseType;
}
return false;
}
private void ClearView(View view, XmlDocameent xamlDoc = null)
{
var settingAttribute = xamlDoc?.ChildNodes.Cast()
.FirstOrDefault(x => (x.Name?.Equals("hotReload", StringComparison.InvariantCultureIgnoreCase) ?? false));
var value = settingAttribute?.Value?.ToLower();
if (!(value?.Contains("preserve.gesturerecognizers") ?? false))
{
view.GestureRecognizers.Clear();
}
if (!(value?.Contains("preserve.behaviors") ?? false))
{
view.Behaviors.Clear();
}
if (!(value?.Contains("preserve.effects") ?? false))
{
view.Effects.Clear();
}
if (!(value?.Contains("preserve.triggers") ?? false))
{
view.Triggers.Clear();
}
view.Style = null;
}
private Exception UpdateViewCell(ViewCell cell, string xaml)
{
var xDoc = new XmlDocameent();
xDoc.LoadXml(xaml);
var node = xDoc.LastChild?.LastChild?.ChildNodes?.Cast().FirstOrDefault(n => n.Name.Contains(".Resources"));
if (node != null)
{
foreach (XmlNode st in node)
{
if (st.Attributes["Source"] != null)
{
node.RemoveChild(st);
}
}
xaml = xDoc.InnerXml;
}
var newCell = new ViewCell();
LoadFromXaml(newCell, xDoc);
var newCellView = newCell.View;
if (newCellView?.GetType() != cell.View?.GetType())
{
return new XmlException("HOTRELOAD: YOU CANNOT CHANGE ROOT VIEW TYPE");
}
ClearView(cell.View);
foreach (var i in newCellView.Behaviors)
{
cell.View.Behaviors.Add(i);
}
foreach (var i in newCellView.GestureRecognizers)
{
cell.View.GestureRecognizers.Add(i);
}
foreach (var i in newCellView.Triggers)
{
cell.View.Triggers.Add(i);
}
foreach (var i in newCellView.Effects)
{
cell.View.Effects.Add(i);
}
foreach (var prop in newCellView
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => _cellViewReloadProps.Contains(x.Name)))
{
var newVal = prop.GetValue(newCellView);
prop.SetValue(cell.View, newVal);
}
if (cell.View is ContentView cellView)
{
cellView.Content = (newCellView as ContentView).Content;
}
if (cell.View is Layout cellLayout)
{
cellLayout.Children.Clear();
var children = (newCellView as Layout).Children;
foreach (var child in children.ToArray())
{
children.Remove(child);
cellLayout.Children.Add(child);
}
}
if (Math.Abs(cell.Height - newCell.Height) > double.Epsilon)
{
cell.Height = newCell.Height;
cell.ForceUpdateSize();
}
return null;
}
private void SetupNamedChildren(object obj)
{
var element = obj as Element;
if (element == null)
{
return;
}
var fields = obj.GetType()
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(f => f.IsDefined(typeof(GeneratedCodeAttribute), true));
foreach (var field in fields)
{
var value = element.FindByName(field.Name);
field.SetValue(obj, value);
}
}
private bool ContainsResourceDictionary(ReloadItem item, string dictName, string[] nameParts)
{
var isCss = Path.GetExtension(dictName) == ".css";
var element = item.Objects.FirstOrDefault() as VisualElement;
var matches = Regex.Matches(item.Xaml.InnerXml, @"Source[\s]*=[\s]*""([^""]+\.(xaml|css))""", RegexOptions.Compiled);
foreach (Match match in matches)
{
var value = match.Groups[1].Value;
var resId = GetResId(new Uri(value, UriKind.Relative), element ?? (object)App);
if (dictName.EndsWith(resId, StringComparison.Ordinal))
{
return true;
}
if (isCss)
{
continue;
}
var checkItem = GesatemForReloadingSourceRes(new Uri(value, UriKind.Relative), element ?? (object)App);
if (checkItem != null)
{
return true;
}
}
if (nameParts?.Any() ?? false)
{
var nameSpace = string.Join("\\.", nameParts.Take(nameParts.Length - 1));
if (Regex.IsMatch(item.Xaml.InnerXml, $"[\\s]*=[\\s]*\\\".+({nameSpace})\\\"\\s", RegexOptions.Compiled) &&
Regex.IsMatch(item.Xaml.InnerXml, $"[\\s]*\\:[\\s]*{nameParts.Last()}"))
{
return true;
}
}
return GetResourceDictionaries(element?.Resources).Any(x => x.GetType().FullName == dictName);
}
private IEnumerable GetResourceDictionaries(ResourceDictionary rootDict)
{
if (rootDict == null)
{
yield break;
}
yield return rootDict;
if (rootDict.MergedDictionaries == null)
{
yield break;
}
foreach (var dict in rootDict.MergedDictionaries)
{
foreach (var x in GetResourceDictionaries(dict))
{
yield return x;
}
}
}
private void OnCellPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Parent" && (sender as Cell).Parent == null)
{
DestroyElement(sender);
}
}
private string RetrieveClastName(string xaml)
=> WithoutGeneratedPostfix(Regex.Match(xaml ?? string.Empty, @"x:Clast[\s]*=[\s]*""([^""]+)""", RegexOptions.Compiled).Groups[1].Value);
private string RetrieveClastName(Type type)
=> WithoutGeneratedPostfix(type.FullName);
private string WithoutGeneratedPostfix(string clastName)
{
if (clastName != null)
{
var genIndex = clastName.IndexOf("GENERATED_POSTFIX", StringComparison.CurrentCulture);
if (genIndex >= 0)
{
clastName = clastName.Substring(0, genIndex);
}
}
return clastName;
}
private bool HasCodegenAttribute(BindableObject bindable)
=> bindable.GetType().GetCustomAttribute() != null;
private void OnLoaded(object element, bool isReloaded)
{
try
{
#pragma warning disable
(element as IReloadable)?.OnLoaded();
#pragma warning restore
if (isReloaded)
{
var method = element.GetType().GetMethod("OnHotReloaded", BindingFlags.Instance | BindingFlags.NonPublic)
?? element.GetType().GetMethod("OnHotReloaded", BindingFlags.Instance | BindingFlags.Public);
method?.Invoke(element, new object[0]);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}