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

[Android] Calling Focus on all Pickers running an API 28 devices no longer opens Picker #5159

Closed
varyamereon opened this issue Feb 7, 2019 · 33 comments

Comments

@varyamereon
Copy link

varyamereon commented Feb 7, 2019

Description

When using API Level 28, calling Focus on a DatePicker with Visible set to false does not show the date picker dialog.

Prior to this PR https://github.com/xamarin/Xamarin.Forms/pull/4344/files#diff-446294d29d78ca8d84e4c0ddc20bbc07L132 the Picker was opened as part of the FocusRequestedEvent. PR #4344 brought DatePicker inline with the other pickers by changing it to trigger a Click on the control which then would trigger the click listener to open the picker.

On API 28 FocusRequestedEvent is still called so there is still an opportunity to react to this. The difference on API 28 is that the Focus request fails so the Click Listener never actually fires.

Possible fix ideas

Steps to Reproduce

  1. Add a date picker and a button on a Forms page. Set the visibility of the date picker to false
  2. Use the button to call Focus on the date picker.
  3. On API 27 and lower the dialog is shown. Not on API 28.

Expected Behavior

DatePicker dialog is shown.

Actual Behavior

Nothing happens.

Basic Information

  • Version with issue: 4.0.0.135214-pre4
  • Last known good version: Unknown
  • IDE: Visual Studio 2019 Windows
  • Platform Target Frameworks:
    • Android: API 28
  • Android Support Library Version:
  • Nuget Packages:
  • Affected Devices: Android

Screenshots

n/a

Reproduction Link

Reproduction

@PureWeen PureWeen self-assigned this Feb 7, 2019
@samhouts
Copy link
Contributor

@PureWeen Are you looking at this one?

@PureWeen
Copy link
Contributor

@varyamereon
I couldn't get your sample to run.

I've attached a quick sample based on the one you linked that works for me.

Can you try it?

DatePickerPopup.zip

@PureWeen PureWeen added the s/needs-info ❓ A question has been asked that requires an answer before work can continue on this issue. label Feb 12, 2019
@varyamereon
Copy link
Author

varyamereon commented Feb 13, 2019

@PureWeen
Interesting, I have downloaded your sample and find the same problem on my end. Building against API 28 clicking the button will not show the DatePicker, building against API 27 it works. I am running the sample on an Android Emulator running Android Pie.

EDIT
Same happens when applying to a real device running Android Pie.

@NickCullen
Copy link

Anyone found a solution to this? Currently experiencing the exact same issue when compiling against Android 28 (Pie).

On devices that are 8.1 and lower (using the same APK built against Android 28), the date time pickers work. Only happens when running on an Android 9.0 device.

@NickCullen
Copy link

I just did a bit of testing.

In the start, our xaml looks like this (notice, they start off invisible):

<DatePicker x:Name="accidentDatePicker" Date="{Binding ClaimDate}" IsVisible="False" />
<TimePicker x:Name="accidentTimePicker" Time="{Binding ClaimTime}" IsVisible="False" />

Our code was just calling ".Focus()" on one of these elements (depending on the button you click, of course).

       private void EditDateButton_Clicked(object sender, EventArgs e)
       {
           this.accidentDatePicker.Focus();
       }

If I change my code to the following, the date picker input box appears (we don't want this to happen - all we want to happen is to bring up the date/time picker dialog). BUT along with the input field appearing, the date/timer picker dialog DOES appear.

        private void EditDateButton_Clicked(object sender, EventArgs e)
        {
            accidentDatePicker.IsVisible = true;
            this.accidentDatePicker.Focus();
        }

@PureWeen PureWeen added e/3 🕒 3 and removed s/needs-info ❓ A question has been asked that requires an answer before work can continue on this issue. labels Feb 28, 2019
@filmar25
Copy link

The picker not working too, not only DatePicker.
The solution .IsVisible = true not working for me.

@PureWeen
Copy link
Contributor

https://developer.android.com/about/versions/pie/android-9.0-changes-28

Views with 0 area (either a width or a height is 0) are no longer focusable.

Pickers probably should have a ShowDialog method instead of just having to go via focus
Either way we'll have to figure out a way to make Focus() work on API 28 to trigger dialoge

@TobiasRoeddiger
Copy link

Facing the same issue. Any quick workarounds for this at the moment?

@jmbowman1107
Copy link

jmbowman1107 commented Apr 18, 2019

Facing the same issue. Any quick workarounds for this at the moment?

I put the button I am using to activate it, and the picker itself in a grid in the same column and did:

HeightRequest="1" WidthRequest="1" Margin="0" HorizontalOptions="Center" on the picker.

So basically hide the picker behind the button, and it seems to work.

@jmjohnson05
Copy link

Hopefully this can help somebody else. Full disclosure -- it's a hack, but it works. I was able to work around this issue with a custom picker renderer bypassing the default implementation of the OnFocusChangeRequested method. I basically just copied the code that is used to show the dialog from the XF picker renderer. It's not exactly a quick fix if you don't already have a custom renderer for the picker, but it works with API Level 28 and 27.

For reference, take a look at the IPickerRenderer.OnClick method for the Android PickerRenderer:

Here is the relevant code:

IElementController ElementController => Element as IElementController;

protected override void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
{
	if (e.Focus)
	{
		SetupPickerDialog();
	}
	else if (pickerDialog != null)
	{
		pickerDialog.Hide();
		ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
		pickerDialog = null;
	}
}

void SetupPickerDialog()
{
	Picker model = Element;

	if (pickerDialog != null)
	{
		return;
	}

	var picker = new NumberPicker(Context);

	if (model.Items != null && model.Items.Any())
	{
		picker.MaxValue = model.Items.Count - 1;
		picker.MinValue = 0;
		picker.SetDisplayedValues(model.Items.ToArray());
		picker.WrapSelectorWheel = false;
		picker.DescendantFocusability = DescendantFocusability.BlockDescendants;
		picker.Value = model.SelectedIndex;
	}

	var layout = new LinearLayout(Context)
	{
		Orientation = Orientation.Vertical
	};

	layout.AddView(picker);

	ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);

	var builder = new AlertDialog.Builder(Context);
	builder.SetView(layout);

	if (!Element.IsSet(Picker.TitleColorProperty))
	{
		builder.SetTitle(model.Title ?? "");
	}

	builder.SetNegativeButton(global::Android.Resource.String.Cancel, (s, a) =>
	{
		ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
		pickerDialog = null;
	});

	builder.SetPositiveButton(global::Android.Resource.String.Ok, (s, a) =>
	{
		ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);

		if (Element != null)
		{
			if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
			{
				Control.Text = model.Items[Element.SelectedIndex];
			}

			ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
		}

		pickerDialog = null;
	});

	pickerDialog = builder.Create();

	pickerDialog.DismissEvent += (sender, args) =>
	{
		ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
	};

	pickerDialog.Show();
}

@Ws16
Copy link

Ws16 commented May 14, 2019

check if the IsEnable property is set to false then the focus() event won't work on api 28, setting it to true or removing it will fix the issue and make the datepicker focus works.

@chrisfoulds
Copy link

chrisfoulds commented Jun 3, 2019

Tried all the workarounds, some work but only once and the custom renderer is overkill for my purposes.
So I basically removed the datepicker from my xaml and make a new one on each button press now. Works on Droid, iOS and Windows on all versions tested and is simpler than making new renderer.


 private void Date_Clicked(object sender, EventArgs e)
        {
            currentPicker = new DatePicker
            {
                IsEnabled=true,
                HeightRequest=1,
                BackgroundColor=Color.Transparent,
                TextColor =Color.White,
                FontSize=26,
                Margin = new Thickness(5)
            };
            currentPicker.Date = selectedDate;

            currentPicker.DateSelected += DatePicker_DateSelected;
            mainGrid.Children.Insert(0, currentPicker);
            currentPicker.Focus();
        }

        private void DatePicker_DateSelected(object sender, DateChangedEventArgs e)
        {
            selectedDate = currentPicker.Date;
            mainGrid.Children.Remove(currentPicker);
            currentPicker = null;
            UpdateDisplay();
        }

@jonathanantoine
Copy link

@chrisfoulds sorry it did not work for me either. The only workaround I've found is using a custom renderer (quite easy to write in fact) following these instructions
: #5433 (comment)

@chrisfoulds
Copy link

The solution I posted above works great for me, in a product app now with 20k DAU's and no complaints so surprised you had to go down the custom renderer route.
Thanks for sharing though.

@jonathanantoine
Copy link

@chrisfoulds I am using it in a "modal page", that's maybe the issue ?

@chrisfoulds
Copy link

@chrisfoulds I am using it in a "modal page", that's maybe the issue ?

@jonathanantoine maybe , I just checked I am using it on a standard navigation page which may make all the difference.

@NickCullen
Copy link

Hi folks,

I see a lot of replies here looking for solutions and I realized I never posted my work around!!

Just make the date picker / time picker visible but translate it on the Y axis by a huge number.

Then when you call .Focus() on it, it will work!

Here is an example of what we have:

xaml:
<DatePicker x:Name="datePicker" Date="{Binding MyDate}" IsVisible="True" TranslationY="-40000"/> <TimePicker Time="{Binding MyTime}" IsVisible="True" TranslationY="-40000"/>

Code behind:
this.datePicker.Focus();

If it means anything (I doubt it) these elements are direct children of a StackLayout, which is the root child of my content page.

This content page itself is apart of the out-of-the-back navigation page.

@truXton222
Copy link

truXton222 commented Jun 18, 2019

Just set IsVisible TRUE, and set Opacity to 0 :)

now you can reuse the datepicker.Focus

@varyamereon
Copy link
Author

Another hack, set the Height and Width requests to 1 and make the text transparent. Set IsVisible to true. It ain't pretty but it works for now. Still think this is something that needs a proper implementation with regards to #5159 (comment).

@samhouts
Copy link
Contributor

samhouts commented Jul 30, 2019

Regression between 3.4.0.1008975 and 3.4.0.1009999

Requires targeting Android 9.0 to reproduce.

@PureWeen PureWeen removed their assignment Jul 30, 2019
@PureWeen
Copy link
Contributor

Possible causes

#4725
#4344

@JimmyPun610
Copy link

Same issue,
for my work around

 <ContentView Grid.Row="1" Grid.Column="0">
                            <ContentView.GestureRecognizers>
                                <TapGestureRecognizer Tapped="DocDatePickerTapGestureRecognizer_Tapped"/>
                            </ContentView.GestureRecognizers>
                            <Grid>
                                <material:MaterialDatePicker x:Name="DocDate_Entry" AccentColor="{StaticResource Key=AccentColor}" InputTransparent="True"
                                                Placeholder="{language:TranslateExtension Text=DocDateTitle_str}" PropertyChanged="DocDate_Entry_PropertyChanged"/>
                                <DatePicker x:Name="DocDatePicker" HeightRequest="1" WidthRequest="1" Margin="0" HorizontalOptions="Center" PropertyChanged="DocDatePicker_PropertyChanged"
                                            TextColor="Transparent"/>
                            </Grid>
                        </ContentView>

Put the control in the same grid. set datepicker textcolor to transparent

@kingces95
Copy link
Contributor

kingces95 commented Aug 22, 2019

So in 9.0 it seems Android started enforcing (or added?) a condition for being focusable is first being visible. If we make the view visible before requesting it gain focus then the the date picker appears.

It seems people want the picker dialog without the EditText view and that's simply not the way the DatePicker view is designed... So essentially, this bug is a feature request. People would like a way to display the DatePicker dialog independent of the accompanying EditText view which displays the picked date/time.

A number of workaround/hacks have been proposed (a translation off the visible screen, reducing size to a pixel, hiding behind another view) and while any may work we not in a position to endorse one over the other.

https://developer.android.com/reference/android/view/View.html#requestFocus(int,%20android.graphics.Rect)

image

Here is where I added IsVisible to @PureWeen's most excellent reproduction.

image

@PureWeen
Copy link
Contributor

Here's the line of code that changed that broke it between 3.4 releases

https://github.com/xamarin/Xamarin.Forms/pull/4344/files#diff-446294d29d78ca8d84e4c0ddc20bbc07R100

@kingces95
Copy link
Contributor

#4344

@kingces95 kingces95 assigned paymicro and unassigned kingces95 Aug 22, 2019
@PureWeen PureWeen assigned jfversluis and unassigned paymicro Aug 23, 2019
@PureWeen PureWeen changed the title [Android] DatePicker Focus event broken when targeting API Level 28 [Android] Calling Focus on all Pickers running an API 28 device no longer opens Picker Aug 23, 2019
@PureWeen PureWeen changed the title [Android] Calling Focus on all Pickers running an API 28 device no longer opens Picker [Android] Calling Focus on all Pickers running an API 28 devices no longer opens Picker Aug 23, 2019
@jfversluis
Copy link
Member

jfversluis commented Aug 27, 2019

So, if I have reproduced this correctly in the Gallery app, it's not so much a problem of receiving focus. That seems to still work, but the TextView is no longer clickable when it's not visible.

I have tried both CallOnClick and PerformClick but both do not seem to execute when the control is not visible. I have created a PR that now checks if the control is Clickable and if not, it will not call the click method, but it will go directly into the logic to show the dialog. Basically the change @PureWeen highlighted with this link a few comments earlier.

This seems to work in all cases I have tried so far, at the time of writing the UI tests are still running.

In any case, if this works, I don't really see the need to keep the IPickerRenderer.OnClick() implementation that was implemented as part of #4344. I'm assuming that was done for a reason. With the logic implemented in #7289 it should now respond the same way as it does today when the control is visible and if the control is not visible, we skip the click and show the dialog directly. The exact same logic is executed for both paths.

I'm curious to hear any of your thoughts about this.

Edit: this fix does not seem to work for Picker for some reason...

@svaldetero-envoc
Copy link

My picker was hidden behind another view so setting the IsVisible workaround didn't work for me. This line in constructor of the Page.xaml.cs worked for me:

if (Device.RuntimePlatform == Device.Android && 
    Xamarin.Essentials.DeviceInfo.Version.Major >= 9.0)
{ 
    picker.HeightRequest = picker.WidthRequest = 1; 
}

@Pastajello
Copy link

I've used the workaround from @truXton222 but now when I press backbutton on the device the datepicker dialog shown again instead of view going back :|

@jfversluis
Copy link
Member

jfversluis commented Sep 17, 2019

@Miksier that is something that is something described in #7289 and also fixed with #7311. I think we're close to merging, thank you for your patience!

@larsduewel
Copy link

@jfversluis would be great!

@samhouts
Copy link
Contributor

samhouts commented Oct 7, 2019

closed by #7289

@samhouts samhouts closed this as completed Oct 7, 2019
@gilles-leblanc
Copy link

Same issue,
for my work around

 <ContentView Grid.Row="1" Grid.Column="0">
                            <ContentView.GestureRecognizers>
                                <TapGestureRecognizer Tapped="DocDatePickerTapGestureRecognizer_Tapped"/>
                            </ContentView.GestureRecognizers>
                            <Grid>
                                <material:MaterialDatePicker x:Name="DocDate_Entry" AccentColor="{StaticResource Key=AccentColor}" InputTransparent="True"
                                                Placeholder="{language:TranslateExtension Text=DocDateTitle_str}" PropertyChanged="DocDate_Entry_PropertyChanged"/>
                                <DatePicker x:Name="DocDatePicker" HeightRequest="1" WidthRequest="1" Margin="0" HorizontalOptions="Center" PropertyChanged="DocDatePicker_PropertyChanged"
                                            TextColor="Transparent"/>
                            </Grid>
                        </ContentView>

Put the control in the same grid. set datepicker textcolor to transparent

This was the only work around which works for me sadly it only works the first time. After dismissing the picker I can't seem to bring it up anymore unless I switch to another views and later come back to the view with the picker.

@jfversluis
Copy link
Member

jfversluis commented Oct 14, 2019

@gilles-leblanc did you try the latest 4.2 version (or 4.3-pre) the fix for this should be incorporated in there? If you still find that it's not working, please open a new issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests