Xamarin.forms: [Android] Calling Focus on all Pickers running an API 28 devices no longer opens Picker

Created on 7 Feb 2019  路  33Comments  路  Source: xamarin/Xamarin.Forms

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

  • force it to focus anyway. Maybe this is a bad idea? On iOS if the control isn't visible it's still able to be set as a First Responder so this would bring it inline with iOS
  • change all the pickers to open the dialog from the FocusRequestedEvent. If this is done then make sure to test what #4344 fixed

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

3 regression mobcat Android bug

Most helpful comment

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

now you can reuse the datepicker.Focus

All 33 comments

@PureWeen Are you looking at this one?

@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
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.

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.

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();
    }

```

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

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

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

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.

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();
}

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.

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();
        }

@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
: https://github.com/xamarin/Xamarin.Forms/issues/5433#issuecomment-469142933

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.

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

@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.

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.

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

now you can reuse the datepicker.Focus

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 https://github.com/xamarin/Xamarin.Forms/issues/5159#issuecomment-474148829.

Regression between 3.4.0.1008975 and 3.4.0.1009999

Requires targeting Android 9.0 to reproduce.

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

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

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

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...

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; 
}

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 :|

@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!

@jfversluis would be great!

closed by #7289

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.

@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.

Was this page helpful?
0 / 5 - 0 ratings