Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 94cc662

Browse files
authored
LifeCycleEvents (#674)
* Created LifeCycleEffect and platform implementations * Added sample to LifeCycle effect * refactor UnLoaded logic * clean up csproj * Fixed NRE for shell * Code Review fixes * code style * improved the sample * fixed tizen path * Fixed iOS support * Implemented WeakEventManager * Added inline docs/comments * changed to nameof
1 parent 3d439c8 commit 94cc662

File tree

8 files changed

+301
-1
lines changed

8 files changed

+301
-1
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<pages:BasePage
3+
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Effects.LifeCycleEffectPage"
4+
xmlns="http://xamarin.com/schemas/2014/forms"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6+
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
7+
xmlns:xct="http://xamarin.com/schemas/2020/toolkit">
8+
<ContentPage.Content>
9+
<StackLayout x:Name="stack">
10+
<StackLayout.Effects>
11+
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" Unloaded="LifeCycleEffect_Unloaded" />
12+
</StackLayout.Effects>
13+
<Label
14+
HorizontalOptions="CenterAndExpand"
15+
Text="When you press the button, the Image will appear and after 3 seconds will be removed!"
16+
VerticalOptions="CenterAndExpand">
17+
<Label.Effects>
18+
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" Unloaded="LifeCycleEffect_Unloaded" />
19+
</Label.Effects>
20+
</Label>
21+
<Image
22+
x:Name="img"
23+
IsVisible="false"
24+
Source="https://raw.githubusercontent.com/xamarin/XamarinCommunityToolkit/main/assets/XamarinCommunityToolkit_128x128.png">
25+
<Image.Effects>
26+
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" Unloaded="LifeCycleEffect_Unloaded" />
27+
</Image.Effects>
28+
</Image>
29+
<Button Clicked="Button_Clicked" Text="Present Image and Remove it">
30+
<Button.Effects>
31+
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" Unloaded="LifeCycleEffect_Unloaded" />
32+
</Button.Effects>
33+
</Button>
34+
<Label Text="Log:" />
35+
<Label x:Name="lbl" TextColor="Red" />
36+
</StackLayout>
37+
</ContentPage.Content>
38+
</pages:BasePage>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
using Xamarin.Forms;
9+
using Xamarin.Forms.Xaml;
10+
11+
namespace Xamarin.CommunityToolkit.Sample.Pages.Effects
12+
{
13+
public partial class LifeCycleEffectPage
14+
{
15+
public LifeCycleEffectPage()
16+
{
17+
InitializeComponent();
18+
}
19+
20+
void LifeCycleEffect_Loaded(object sender, EventArgs e)
21+
{
22+
if (sender is Button)
23+
lbl.Text += "Button loaded \n";
24+
if (sender is Image)
25+
lbl.Text += "Image loaded \n";
26+
if (sender is Label)
27+
lbl.Text += "Label loaded \n";
28+
if (sender is StackLayout)
29+
lbl.Text += "StackLayout loaded \n";
30+
}
31+
32+
void LifeCycleEffect_Unloaded(object sender, EventArgs e)
33+
{
34+
if (sender is Button)
35+
lbl.Text += "Button unloaded \n";
36+
if (sender is Image)
37+
lbl.Text += "Image unloaded \n";
38+
if (sender is Label)
39+
lbl.Text += "Label unloaded \n";
40+
if (sender is StackLayout)
41+
lbl.Text += "StackLayout unloaded \n";
42+
}
43+
44+
void Button_Clicked(object sender, EventArgs e)
45+
{
46+
img.IsVisible = !img.IsVisible;
47+
Device.StartTimer(TimeSpan.FromSeconds(3), () =>
48+
{
49+
stack.Children.Remove(img);
50+
return false;
51+
});
52+
}
53+
}
54+
}

samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,20 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
3232
new SectionModel(
3333
typeof(TouchEffectPage),
3434
nameof(TouchEffect),
35-
"The TouchEffect is an effect that allows changing the view's appearance depending on the touch state (normal, pressed, hovered). Also, it allows to handle long presses."),
35+
"The TouchEffect is an effect that allows changing the view's appearance depending on the touch state (normal, pressed, hovered). Also, it allows to handle long presses."
36+
),
3637

38+
new SectionModel(
39+
typeof(LifeCycleEffectPage),
40+
nameof(LifecycleEffect),
41+
"The LifeCycle is an effect that allows you to know when a control or layout is loaded or/and unloaded in the screen and perform actions based on that."
42+
),
43+
3744
new SectionModel(
3845
typeof(ShadowEffectPage),
3946
nameof(ShadowEffect),
4047
"The ShadowEffect allows all views to display shadow."),
48+
4149
};
4250
}
4351
}

src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/EffectIds.shared.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ sealed class EffectIds
4141
/// </summary>
4242
public static string TouchEffect => $"{effectResolutionGroupName}.{nameof(TouchEffect)}";
4343

44+
45+
public static string LifeCycleEffect => $"{effectResolutionGroupName}.{nameof(LifecycleEffect)}";
46+
4447
/// <summary>
4548
/// Effect Id for <see cref="ShadowEffect"/>
4649
/// </summary>
4750
public static string ShadowEffect => $"{effectResolutionGroupName}.{nameof(ShadowEffect)}";
51+
4852
}
4953
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using Xamarin.CommunityToolkit.Helpers;
3+
using Xamarin.Forms;
4+
5+
namespace Xamarin.CommunityToolkit.Effects
6+
{
7+
/// <summary>
8+
/// An effect to subscribe to the View's lifecycle events.
9+
/// </summary>
10+
public class LifecycleEffect : RoutingEffect
11+
{
12+
readonly WeakEventManager eventManager = new WeakEventManager();
13+
14+
/// <summary>
15+
/// Event that is triggered when the <see cref="View" /> is loaded and is ready for use.
16+
/// </summary>
17+
public event EventHandler Loaded
18+
{
19+
add => eventManager.AddEventHandler(value);
20+
remove => eventManager.RemoveEventHandler(value);
21+
}
22+
23+
/// <summary>
24+
/// Event that is triggered when the <see cref="View" /> is unloaded and isn't ready for use.
25+
/// </summary>
26+
public event EventHandler Unloaded
27+
{
28+
add => eventManager.AddEventHandler(value);
29+
remove => eventManager.RemoveEventHandler(value);
30+
}
31+
32+
/// <summary>
33+
/// Constructor for the <see cref="LifecycleEffect" />
34+
/// </summary>
35+
public LifecycleEffect()
36+
: base(EffectIds.LifeCycleEffect)
37+
{
38+
#if __ANDROID__
39+
if (System.DateTime.Now.Ticks < 0)
40+
_ = new Xamarin.CommunityToolkit.Android.Effects.LifeCycleEffectRouter();
41+
#elif __IOS__
42+
if (System.DateTime.Now.Ticks < 0)
43+
_ = new Xamarin.CommunityToolkit.iOS.Effects.LifeCycleEffectRouter();
44+
#elif UWP
45+
if (System.DateTime.Now.Ticks < 0)
46+
_ = new Xamarin.CommunityToolkit.UWP.Effects.LifeCycleEffectRouter();
47+
#endif
48+
}
49+
50+
internal void RaiseLoadedEvent(Element element) => eventManager.RaiseEvent(element, EventArgs.Empty, nameof(Loaded));
51+
52+
internal void RaiseUnloadedEvent(Element element) => eventManager.RaiseEvent(element, EventArgs.Empty, nameof(Unloaded));
53+
}
54+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Linq;
3+
using Xamarin.CommunityToolkit.Android.Effects;
4+
using Xamarin.CommunityToolkit.Effects;
5+
using Xamarin.Forms;
6+
using Xamarin.Forms.Platform.Android;
7+
using View = Android.Views.View;
8+
9+
[assembly: ExportEffect(typeof(LifeCycleEffectRouter), nameof(LifecycleEffect))]
10+
11+
namespace Xamarin.CommunityToolkit.Android.Effects
12+
{
13+
/// <summary>
14+
/// Android implementation of the <see cref="LifecycleEffect" />
15+
/// </summary>
16+
public class LifeCycleEffectRouter : PlatformEffect
17+
{
18+
View nativeView;
19+
LifecycleEffect lifeCycleEffect;
20+
21+
protected override void OnAttached()
22+
{
23+
lifeCycleEffect = Element.Effects.OfType<LifecycleEffect>().FirstOrDefault() ??
24+
throw new ArgumentNullException($"The effect {nameof(LifecycleEffect)} can't be null.");
25+
26+
nativeView = Control ?? Container;
27+
28+
nativeView.ViewAttachedToWindow += OnNativeViewViewAttachedToWindow;
29+
nativeView.ViewDetachedFromWindow += OnNativeViewViewDetachedFromWindow;
30+
}
31+
32+
void OnNativeViewViewAttachedToWindow(object sender, View.ViewAttachedToWindowEventArgs e) => lifeCycleEffect.RaiseLoadedEvent(Element);
33+
34+
void OnNativeViewViewDetachedFromWindow(object sender, View.ViewDetachedFromWindowEventArgs e)
35+
{
36+
lifeCycleEffect.RaiseUnloadedEvent(Element);
37+
nativeView.ViewDetachedFromWindow -= OnNativeViewViewDetachedFromWindow;
38+
nativeView.ViewAttachedToWindow -= OnNativeViewViewAttachedToWindow;
39+
nativeView = null;
40+
lifeCycleEffect = null;
41+
}
42+
43+
protected override void OnDetached()
44+
{
45+
}
46+
}
47+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Linq;
3+
using Xamarin.CommunityToolkit.Effects;
4+
using Xamarin.CommunityToolkit.iOS.Effects;
5+
using Xamarin.Forms;
6+
using Xamarin.Forms.Platform.iOS;
7+
8+
[assembly: ExportEffect(typeof(LifeCycleEffectRouter), nameof(LifecycleEffect))]
9+
10+
namespace Xamarin.CommunityToolkit.iOS.Effects
11+
{
12+
/// <summary>
13+
/// iOS implementation of the <see cref="LifecycleEffect" />
14+
/// </summary>
15+
public class LifeCycleEffectRouter : PlatformEffect
16+
{
17+
LifecycleEffect lifeCycleEffect;
18+
19+
protected override void OnAttached()
20+
{
21+
lifeCycleEffect = Element.Effects.OfType<LifecycleEffect>().FirstOrDefault() ??
22+
throw new ArgumentNullException($"The effect {nameof(LifecycleEffect)} can't be null.");
23+
24+
Element.PropertyChanged += OnPropertyChanged;
25+
}
26+
27+
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
28+
{
29+
if (e.PropertyName == "Renderer")
30+
{
31+
var result = Platform.GetRenderer(Element as VisualElement);
32+
33+
if (result != null)
34+
lifeCycleEffect.RaiseLoadedEvent(Element);
35+
else
36+
{
37+
lifeCycleEffect.RaiseUnloadedEvent(Element);
38+
lifeCycleEffect = null;
39+
Element.PropertyChanged -= OnPropertyChanged;
40+
}
41+
}
42+
}
43+
44+
protected override void OnDetached()
45+
{
46+
}
47+
}
48+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Linq;
3+
using Windows.UI.Xaml;
4+
using Xamarin.CommunityToolkit.Effects;
5+
using Xamarin.CommunityToolkit.UWP.Effects;
6+
using Xamarin.Forms;
7+
using Xamarin.Forms.Platform.UWP;
8+
9+
[assembly: ExportEffect(typeof(LifeCycleEffectRouter), nameof(LifecycleEffect))]
10+
11+
namespace Xamarin.CommunityToolkit.UWP.Effects
12+
{
13+
/// <summary>
14+
/// UWP implementation of the <see cref="LifecycleEffect" />
15+
/// </summary>
16+
public class LifeCycleEffectRouter : PlatformEffect
17+
{
18+
FrameworkElement nativeView;
19+
LifecycleEffect lifeCycleEffect;
20+
21+
protected override void OnAttached()
22+
{
23+
lifeCycleEffect = Element.Effects.OfType<LifecycleEffect>().FirstOrDefault() ??
24+
throw new ArgumentNullException($"The effect {nameof(LifecycleEffect)} can't be null.");
25+
26+
nativeView = Control ?? Container;
27+
28+
nativeView.Loaded += OnNativeViewLoaded;
29+
nativeView.Unloaded += OnNativeViewUnloaded;
30+
}
31+
32+
void OnNativeViewLoaded(object sender, RoutedEventArgs e) => lifeCycleEffect.RaiseLoadedEvent(Element);
33+
34+
void OnNativeViewUnloaded(object sender, RoutedEventArgs e)
35+
{
36+
lifeCycleEffect.RaiseUnloadedEvent(Element);
37+
nativeView.Unloaded -= OnNativeViewUnloaded;
38+
nativeView.Loaded -= OnNativeViewLoaded;
39+
lifeCycleEffect = null;
40+
nativeView = null;
41+
}
42+
43+
protected override void OnDetached()
44+
{
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)