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

Commit 7d25f18

Browse files
jfversluisPureWeen
authored andcommitted
[Android] 28+ Make non-visible pickers work again (#7289)
* Repro + fix * Final fix * Update Issue5159.cs * Fixes #7311 * Test tweaks * Update TimePickerRenderer.cs * Revert "Update TimePickerRenderer.cs" This reverts commit 06c1172. * Update TimePickerRenderer.cs * Update TimePickerRenderer.cs * - added instructions
1 parent 4b60149 commit 7d25f18

File tree

8 files changed

+278
-8
lines changed

8 files changed

+278
-8
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using System;
2+
using Xamarin.Forms.Internals;
3+
using Xamarin.Forms.CustomAttributes;
4+
#if UITEST
5+
using Xamarin.Forms.Core.UITests;
6+
using Xamarin.UITest;
7+
using NUnit.Framework;
8+
#endif
9+
10+
namespace Xamarin.Forms.Controls.Issues
11+
{
12+
#if UITEST
13+
[Category(UITestCategories.Picker)]
14+
[Category(UITestCategories.DatePicker)]
15+
[Category(UITestCategories.TimePicker)]
16+
#endif
17+
[Preserve(AllMembers = true)]
18+
[Issue(IssueTracker.Github, 5159, "[Android] Calling Focus on all Pickers running an API 28 devices no longer opens Picker", PlatformAffected.Android)]
19+
public class Issue5159 : TestContentPage
20+
{
21+
const string DatePickerButton = "DatePickerButton";
22+
const string TimePickerButton = "TimePickerButton";
23+
const string PickerButton = "PickerButton";
24+
readonly string[] _pickerValues = { "Foo", "Bar", "42", "1337" };
25+
26+
protected override void Init()
27+
{
28+
var stackLayout = new StackLayout
29+
{
30+
VerticalOptions = LayoutOptions.Center,
31+
HorizontalOptions = LayoutOptions.Center
32+
};
33+
34+
// DatePicker
35+
var datePickerButton = new Button
36+
{
37+
Text = "Show DatePicker",
38+
AutomationId = DatePickerButton
39+
};
40+
41+
var datePicker = new DatePicker
42+
{
43+
IsVisible = false
44+
};
45+
46+
datePickerButton.Clicked += (s, a) =>
47+
{
48+
Device.BeginInvokeOnMainThread(() =>
49+
{
50+
if (datePicker.IsFocused)
51+
datePicker.Unfocus();
52+
53+
datePicker.Focus();
54+
});
55+
};
56+
57+
// TimePicker
58+
var timePickerButton = new Button
59+
{
60+
Text = "Show TimePicker",
61+
AutomationId = TimePickerButton
62+
};
63+
64+
var timePicker = new TimePicker
65+
{
66+
IsVisible = false
67+
};
68+
69+
timePickerButton.Clicked += (s, a) =>
70+
{
71+
Device.BeginInvokeOnMainThread(() =>
72+
{
73+
if (timePicker.IsFocused)
74+
timePicker.Unfocus();
75+
76+
timePicker.Focus();
77+
});
78+
};
79+
80+
// Picker
81+
var pickerButton = new Button
82+
{
83+
Text = "Show Picker",
84+
AutomationId = PickerButton
85+
};
86+
87+
var picker = new Picker
88+
{
89+
IsVisible = false,
90+
ItemsSource = _pickerValues
91+
};
92+
93+
pickerButton.Clicked += (s, a) =>
94+
{
95+
Device.BeginInvokeOnMainThread(() =>
96+
{
97+
if (picker.IsFocused)
98+
picker.Unfocus();
99+
100+
picker.Focus();
101+
});
102+
};
103+
104+
stackLayout.Children.Add(datePickerButton);
105+
stackLayout.Children.Add(datePicker);
106+
107+
stackLayout.Children.Add(timePickerButton);
108+
stackLayout.Children.Add(timePicker);
109+
110+
stackLayout.Children.Add(pickerButton);
111+
stackLayout.Children.Add(picker);
112+
113+
Content = stackLayout;
114+
}
115+
116+
#if UITEST && __ANDROID__
117+
[Test]
118+
[UiTest(typeof(DatePicker))]
119+
public void InvisibleDatepickerShowsDialogOnFocus()
120+
{
121+
RunningApp.WaitForElement(DatePickerButton);
122+
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
123+
RunningApp.Tap(DatePickerButton);
124+
125+
RunningApp.WaitForElement(x => x.Class("DatePicker"));
126+
127+
RunningApp.Screenshot("DatePicker is shown");
128+
RunningApp.TapCoordinates(5, 100);
129+
}
130+
131+
[Test]
132+
[UiTest(typeof(TimePicker))]
133+
public void InvisibleTimepickerShowsDialogOnFocus()
134+
{
135+
RunningApp.WaitForElement(TimePickerButton);
136+
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
137+
RunningApp.Tap(TimePickerButton);
138+
139+
RunningApp.WaitForElement(x => x.Class("timePicker"));
140+
141+
RunningApp.Screenshot("TimePicker is shown");
142+
RunningApp.TapCoordinates(5, 100);
143+
}
144+
145+
[Test]
146+
[UiTest(typeof(Picker))]
147+
public void InvisiblePickerShowsDialogOnFocus()
148+
{
149+
RunningApp.WaitForElement(PickerButton);
150+
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
151+
RunningApp.Tap(PickerButton);
152+
153+
RunningApp.WaitForElement("Foo");
154+
155+
RunningApp.Screenshot("Picker is shown");
156+
157+
RunningApp.Tap("Foo");
158+
159+
RunningApp.WaitForNoElement("Foo");
160+
161+
}
162+
#endif
163+
}
164+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using Xamarin.Forms.CustomAttributes;
3+
using Xamarin.Forms.Internals;
4+
5+
#if UITEST
6+
using Xamarin.UITest;
7+
using NUnit.Framework;
8+
using Xamarin.UITest.iOS;
9+
#endif
10+
11+
namespace Xamarin.Forms.Controls.Issues
12+
{
13+
[Preserve(AllMembers = true)]
14+
[Issue(IssueTracker.Github, 7311, "[Bug] [Android] Error back hardware button with Picker", PlatformAffected.Android)]
15+
public class Issue7311 : TestContentPage
16+
{
17+
const string FirstPickerItem = "Uno";
18+
const string PickerId = "CaptainPickard";
19+
readonly string[] _items = { FirstPickerItem, "Dos", "Tres" };
20+
21+
protected override void Init()
22+
{
23+
var picker = new Picker
24+
{
25+
ItemsSource = _items,
26+
AutomationId = PickerId
27+
};
28+
29+
Content = new StackLayout()
30+
{
31+
Children =
32+
{
33+
new Label()
34+
{
35+
Text = "Open Picker. Click hardware back button to close picker. Click hardware button a second time and it should navigate back to gallery"
36+
},
37+
picker
38+
}
39+
};
40+
}
41+
42+
#if UITEST && __ANDROID__
43+
[Test]
44+
public void OpeningPickerPressingBackButtonTwiceShouldNotOpenPickerAgain()
45+
{
46+
RunningApp.WaitForElement(PickerId);
47+
RunningApp.Tap(PickerId);
48+
49+
RunningApp.WaitForElement(FirstPickerItem);
50+
51+
RunningApp.Back();
52+
53+
RunningApp.WaitForNoElement(FirstPickerItem);
54+
55+
RunningApp.Back();
56+
57+
RunningApp.WaitForNoElement(FirstPickerItem, "Picker is again visible after second back button press", TimeSpan.FromSeconds(10));
58+
59+
RunningApp.Screenshot("Back at the previous page, not showing the picker again");
60+
}
61+
#endif
62+
}
63+
}

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,8 @@
10191019
<Compile Include="$(MSBuildThisFileDirectory)GitHub6926.cs" />
10201020
<Compile Include="$(MSBuildThisFileDirectory)Issue5503.cs" />
10211021
<Compile Include="$(MSBuildThisFileDirectory)ShellTitleView.cs" />
1022+
<Compile Include="$(MSBuildThisFileDirectory)Issue5159.cs" />
1023+
<Compile Include="$(MSBuildThisFileDirectory)Issue7311.cs" />
10221024
<Compile Include="$(MSBuildThisFileDirectory)Issue7053.cs" />
10231025
<Compile Include="$(MSBuildThisFileDirectory)Issue6894.cs" />
10241026
<Compile Include="$(MSBuildThisFileDirectory)Issue6929.cs" />

Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
9090
base.OnFocusChangeRequested(sender, e);
9191

9292
if (e.Focus)
93-
CallOnClick();
93+
{
94+
if (Clickable)
95+
CallOnClick();
96+
else
97+
((IPickerRenderer)this)?.OnClick();
98+
}
9499
else if (_dialog != null)
95100
{
96101
_dialog.Hide();

Xamarin.Forms.Platform.Android/PickerManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ namespace Xamarin.Forms.Platform.Android
1010
{
1111
internal static class PickerManager
1212
{
13-
readonly static HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
14-
Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
13+
readonly static HashSet<Keycode> AvailableKeys = new HashSet<Keycode>(new[] {
14+
Keycode.Tab, Keycode.Forward, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
1515
});
1616

1717
public static void Init(EditText editText)
@@ -42,7 +42,7 @@ public static void OnFocusChanged(bool gainFocus, EditText sender, IPopupTrigger
4242

4343
static void OnKeyPress(object sender, AView.KeyEventArgs e)
4444
{
45-
if (!availableKeys.Contains(e.KeyCode))
45+
if (!AvailableKeys.Contains(e.KeyCode))
4646
{
4747
e.Handled = false;
4848
return;

Xamarin.Forms.Platform.Android/Renderers/DatePickerRenderer.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
9494
base.OnFocusChangeRequested(sender, e);
9595

9696
if (e.Focus)
97-
CallOnClick();
97+
{
98+
if (Clickable)
99+
CallOnClick();
100+
else
101+
((IPickerRenderer)this)?.OnClick();
102+
}
98103
else if (_dialog != null)
99104
{
100105
_dialog.Hide();

Xamarin.Forms.Platform.Android/Renderers/PickerRenderer.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
104104
base.OnFocusChangeRequested(sender, e);
105105

106106
if (e.Focus)
107-
CallOnClick();
107+
{
108+
if (Clickable)
109+
CallOnClick();
110+
else
111+
((IPickerRenderer)this)?.OnClick();
112+
}
108113
else if (_dialog != null)
109114
{
110115
_dialog.Hide();
@@ -119,7 +124,7 @@ void IPickerRenderer.OnClick()
119124

120125
if (_dialog != null)
121126
return;
122-
127+
123128
var picker = new NumberPicker(Context);
124129
if (model.Items != null && model.Items.Any())
125130
{

Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public abstract class TimePickerRendererBase<TControl> : ViewRenderer<TimePicker
1616
{
1717
int _originalHintTextColor;
1818
AlertDialog _dialog;
19+
bool _isDisposed;
1920

2021
bool Is24HourView
2122
{
@@ -86,7 +87,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
8687
base.OnFocusChangeRequested(sender, e);
8788

8889
if (e.Focus)
89-
CallOnClick();
90+
{
91+
if (Clickable)
92+
CallOnClick();
93+
else
94+
((IPickerRenderer)this)?.OnClick();
95+
}
9096
else if (_dialog != null)
9197
{
9298
_dialog.Hide();
@@ -95,6 +101,7 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
95101
if (Forms.IsLollipopOrNewer)
96102
_dialog.CancelEvent -= OnCancelButtonClicked;
97103

104+
_dialog?.Dispose();
98105
_dialog = null;
99106
}
100107
}
@@ -109,6 +116,25 @@ protected virtual TimePickerDialog CreateTimePickerDialog(int hours, int minutes
109116
return dialog;
110117
}
111118

119+
protected override void Dispose(bool disposing)
120+
{
121+
if (_isDisposed)
122+
return;
123+
124+
_isDisposed = true;
125+
126+
if (disposing)
127+
{
128+
if (Forms.IsLollipopOrNewer && _dialog.IsAlive())
129+
_dialog.CancelEvent -= OnCancelButtonClicked;
130+
131+
_dialog?.Dispose();
132+
_dialog = null;
133+
}
134+
135+
base.Dispose(disposing);
136+
}
137+
112138
void IPickerRenderer.OnClick()
113139
{
114140
if (_dialog != null && _dialog.IsShowing)

0 commit comments

Comments
 (0)