Xamarin.forms: [Bug] Xamarin.Forms.Device.Idiom return Desktop instead of Phone while Xamarin.Essentials.DeviceInfo.Idiom return idiom correct

Created on 22 Jun 2020  路  11Comments  路  Source: xamarin/Xamarin.Forms

Description

Returns inconsistent values from Forms and Essentials.

Steps to Reproduce

  1. Compare returns values from Xamarin.Essentials.DeviceInfo.Idiom and Xamarin.Forms.Device.Idiom on Xiaomi Mi9 SE

Expected Behavior

Xamarin.Forms.Device.Idiom and Xamarin.Essentials.DeviceInfo.Idiom behave in same way.

Actual Behavior

Xamarin.Forms.Device.Idiom returns TargetIdiom.Desktop
Xamarin.Essentials.DeviceInfo.Idiom returns DeviceIdiom.Phone

Basic Information

  • Versions with issue: XF 4.5, 4.6, 4.7
  • Last known good version: XF 4.4
  • Nuget Packages: Xamarin.Essentials 1.5.3.2 and tested on XF 4.4-4.7
  • Affected Devices: Xiaomi Mi9 SE with MIUI 12 - Android 10

Screenshots

XF 4.7
Screenshot 2020-06-22 at 16 19 42
Screenshot_2020-06-22-16-18-42-189_com companyname idiomtest

XF 4.4
Screenshot_2020-06-22-16-16-39-629_com companyname idiomtest

Additional Info

It's a similar issue to https://github.com/xamarin/Essentials/issues/1027

4.5.0 2 regression in-progress Android bug

Most helpful comment

i found cleaner workaround for this issue. Simply set Device.Idiom property via reflection on app start

 public static class FixXamarin
    {
        public static void FixDevice()
        {
            var map = new Dictionary<DeviceIdiom, TargetIdiom>()
            {
                [DeviceIdiom.Desktop] = TargetIdiom.Desktop,
                [DeviceIdiom.Phone] = TargetIdiom.Phone,
                [DeviceIdiom.Tablet] = TargetIdiom.Tablet,
                [DeviceIdiom.Unknown] = TargetIdiom.Unsupported,
                [DeviceIdiom.Watch] = TargetIdiom.Watch,
                [DeviceIdiom.TV] = TargetIdiom.TV,
            };

            var deviceIdiomProperty = typeof(Device).GetProperty("Idiom", BindingFlags.Public | BindingFlags.Static);
            var mappedIdiom = map[Xamarin.Essentials.DeviceInfo.Idiom];
            deviceIdiomProperty?.SetValue(null, mappedIdiom);
        }
    }
protected override void OnCreate(Bundle bundle)
{
      FixXamarin.FixDevice(); // before init if forms using Incorrect values from Device.Idiom
      Forms.Init(this, bundle);
      FixXamarin.FixDevice(); // and after init for extra safety if Forms reseted value Device.Idiom 
}

All 11 comments

Great! Another Xiaomi device and another results... This destroys my app completely :(
Xamarin.Froms.Device.Idiom == "Desktop"
Xamarin.Essentials.DeviceInfo.Idiom == "Phone"

image

Code used to render page:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using Xamarin.Forms;

namespace Pages
{
    public partial class DiagnosticPage : ContentPage
    {
        public List<string> List = new List<string>(); 
        public DiagnosticPage()
        {
            this.List.Add($"XF.Device.Info.CurrentOrientation: {Device.Info.CurrentOrientation}");
            this.List.Add($"XF.Device.Info.ScalingFactor: {Device.Info.ScalingFactor}");
            this.List.Add($"XF.Device.Info.PixelScreenSize: {Device.Info.PixelScreenSize}");
            this.List.Add($"XF.Device.Info.ScaledScreenSize: {Device.Info.ScaledScreenSize}");
            this.List.Add($"XF.Device.Idiom: {Device.Idiom}");
            this.List.Add($"XF.Device.RuntimePlatform: {Device.RuntimePlatform}");
            this.List.Add($"Essentials.DeviceInfo.Idiom: {Xamarin.Essentials.DeviceInfo.Idiom}");
            this.List.Add($"Essentials.DeviceInfo.Manufacturer: {Xamarin.Essentials.DeviceInfo.Manufacturer}");
            this.List.Add($"Essentials.DeviceInfo.Model: {Xamarin.Essentials.DeviceInfo.Model}");
            this.List.Add($"Essentials.DeviceInfo.Name: {Xamarin.Essentials.DeviceInfo.Name}");
            this.List.Add($"Essentials.DeviceInfo.Platform: {Xamarin.Essentials.DeviceInfo.Platform}");
            this.List.Add($"Essentials.DeviceInfo.Version: {Xamarin.Essentials.DeviceInfo.Version}");
            this.List.Add($"Essentials.DeviceInfo.DeviceType: {Xamarin.Essentials.DeviceInfo.DeviceType}");
            this.List.Add($"Essentials.DeviceInfo.VersionString: {Xamarin.Essentials.DeviceInfo.VersionString}");
            this.InitializeComponent();
            BindableLayout.SetItemsSource(this.Layout, this.List);
        }
    }
}


@@ -0,0 +1,17 @@
<ContentPage x:Class="Pages.DiagnosticPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    BackgroundColor="Black">

    <Grid AutomationId="LoadingPageId" CompressedLayout.IsHeadless="True" BackgroundColor="Black">
        <StackLayout x:Name="Layout">
            <BindableLayout.ItemTemplate>
                <DataTemplate>
                    <Label Text="{Binding .}" TextColor="White"/>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
        </StackLayout>
    </Grid>
</ContentPage>

Can you fix this in Xamarin Forms 4.5?

Does it only occur for Xaomi devices?

Interesting, I suffer from the same issue, it was working just fine on Xamarin Forms 4.4.0.991640, and when we updated to 4.6.0.800 that happened.

Here's the list of affected devices by this bug on my side

  1. Xiaomi Redmi Note 8T running Android 9
  2. Xiaomi Redmi Note 8 Pro running Android 10
  3. Xiaomi Redmi Note 8 running Android 9
  4. Xiaomi Redmi Note 7 running Android 9 or Android 10
  5. Xiaomi POCOPHONE F1 running Android 10
  6. Xiaomi Mi Note 10 running Android 10
  7. Xiaomi MI 8 Lite running Android 10
  8. Samsung SM-T865 running Android 10

and it's weird that it only affects some specific devices, like a user X with a Redmi Note 8 returns the Phone value while the user Y with a Redmi Note 8 returns a value that's not the one defined on the Phone idiom.

Sadly for me, because it's returning a non defined value it's crashing my app, here's an attached list showing that for some users the app is working just fine (receiving the Phone value) while others crash the app (by not receiving a Phone value)
image

Sadly, we have a Redmi Note 8 and a MI 8 Lite and both are returning the correct Phone values

@jsuarezruiz, Can you speed up the merge of your fixes? This bug causes a lot of crashes in my recently released project.

Devices where bug occurs, just for now more than 1 thousand crashes, and rising (2 days old app) :/
Application is bricked completely on those devices as we use Device.Idom in our core sdk.

Xiaomi Redmi Note 8 Pro
Xiaomi Redmi Note 4
Xiaomi Redmi Note 8T
Xiaomi Redmi Note 7
Xiaomi Redmi 6
Xiaomi Redmi Note 6 Pro
Xiaomi Redmi Note 5
Xiaomi Redmi 4A
Xiaomi MI 8
Xiaomi Redmi Note 8
Xiaomi Redmi 7
Xiaomi Redmi 5 Plus
rockchip X96 plus 4s.01.d4
Xiaomi Redmi 7A
Xiaomi POCOPHONE F1
Xiaomi Mi Note 10 Lite
Xiaomi Mi Note 10
Xiaomi Redmi 8
Xiaomi MI 8 Lite
Xiaomi Redmi Note 9 Pro
Xiaomi M2003J15SC
Xiaomi Redmi 8A
Xiaomi Mi MIX 2S
rockchip MXR PRO
Xiaomi Redmi Note 9S
Xiaomi Redmi 5
Xiaomi Mi 9 Lite
Xiaomi Redmi 4X
Xiaomi Redmi S2
rockchip MX10.o.00.d4
Xiaomi MI MAX 3
rockchip MBOX
rockchip MX10.11.p2.0.00.d4
rockchip H96Max RK3318
Xiaomi Redmi Note 5A Prime
Xiaomi Mi MIX 2
rockchip N5NOVA
Xiaomi Redmi Note 3
rockchip MiniA5X_Plus.hxj.p2.0.00.d4
rockchip H96 Max RK3318
Xiaomi Mi 9T Pro
rockchip A5X MAX00
rockchip X96 plus 4s.00.d4
rockchip MX9
Xiaomi Redmi 4
Xiaomi Redmi 5A
Xiaomi MI MAX
Xiaomi MI 5s
rockchip V4.SY.01.d4
Xiaomi MI MAX 2
Xiaomi Redmi 6 Pro
Xiaomi MI 9
Xiaomi Mi 9T
rockchip X88MAX.p2.0.01.d4

Workaround: Set the Default value for OnIdiom

For now i found solution to override OnIdiom for every occurence in my code...

 public class OnIdiomOverride<T>
    {
        T _phone;
        T _tablet;
        T _desktop;
        T _tV;
        T _watch;
        T _default;
        bool _isPhoneSet;
        bool _isTabletSet;
        bool _isDesktopSet;
        bool _isTVSet;
        bool _isWatchSet;
        bool _isDefaultSet;

        public T Phone
        {
            get => _phone;
            set
            {
                _phone = value;
                _isPhoneSet = true;
            }
        }
        public T Tablet
        {
            get => _tablet;
            set
            {
                _tablet = value;
                _isTabletSet = true;
            }
        }
        public T Desktop
        {
            get => _desktop;
            set
            {
                _desktop = value;
                _isDesktopSet = true;
            }
        }
        public T TV
        {
            get => _tV;
            set
            {
                _tV = value;
                _isTVSet = true;
            }
        }
        public T Watch
        {
            get => _watch;
            set
            {
                _watch = value;
                _isWatchSet = true;
            }
        }
        public T Default
        {
            get => _default;
            set
            {
                _default = value;
                _isDefaultSet = true;
            }
        }

        public static implicit operator T(OnIdiomOverride<T> onIdiom)
        {
            // LOOK AT Xamarin.Essentials.DeviceInfo, because Xamarin.Forms.Device.Idiom is buggy
            if (DeviceInfo.Idiom == DeviceIdiom.Phone)
            {
                return onIdiom._isPhoneSet ? onIdiom.Phone : (onIdiom._isDefaultSet ? onIdiom.Default : default(T));
            }
            if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
            {
                return onIdiom._isTabletSet ? onIdiom.Tablet : (onIdiom._isDefaultSet ? onIdiom.Default : default(T));
            }
            if (DeviceInfo.Idiom == DeviceIdiom.Desktop)
            {
                return onIdiom._isDesktopSet ? onIdiom.Desktop : (onIdiom._isDefaultSet ? onIdiom.Default : default(T));
            }
            if (DeviceInfo.Idiom == DeviceIdiom.TV)
            {
                return onIdiom._isTVSet ? onIdiom.TV : (onIdiom._isDefaultSet ? onIdiom.Default : default(T));
            }
            if (DeviceInfo.Idiom == DeviceIdiom.Watch)
            {
                return onIdiom._isWatchSet ? onIdiom.Watch : (onIdiom._isDefaultSet ? onIdiom.Default : default(T));
            }
            return default;
        }
    }

And use this like that:

<xaml 
xmlns:infrastructure="clr-namespace:hot-fix.Infrastructure;assembly=hotfix">
    <Grid.IsVisible>
            <infrastructure:OnIdiomOverride
                x:TypeArguments="x:Boolean"
                Phone="True"
                Tablet="False" />
        </Grid.IsVisible>

Its lame but for me its working, On c# usage is like this:

 if (Application.Current.Resources.TryGetValue(maxItemsPerRowKey, out var maxItemsPerRowObject) && maxItemsPerRowObject is Hot.Fix.Namespace.OnIdiomOverride<int> maxItemsPerRowOnIdiom)
            {
                maxItemsPerRow = (int)maxItemsPerRowOnIdiom;
            }

@lucas-zimerman
That would work, but my app would still crash as DeviceIdom is sometimes Uknown, Watch or Destkop. And my SDK would simply do other things that it supposed to do. Best way is to override OnIdom, so devices can use Xamarin.Essentials implementation

@samhouts i think i found problem. here you are cheking for https://github.com/xamarin/Xamarin.Forms/blob/719fc7a604ff0cce8922d717c99bfb0fa17e35e0/Xamarin.Forms.Platform.Android/Forms.cs#L399-L414 with .HasFlag() method on enum that definietly should return just single value. this: https://developer.android.com/reference/android/app/UiModeManager#getCurrentModeType() isn't flagable.

Essentials do right thing. Forms don't.
https://github.com/xamarin/Essentials/blob/3ab48b96240e7844a4abd68d5801b181151dc796/Xamarin.Essentials/DeviceInfo/DeviceInfo.android.cs#L73-L85

EDIT: I've just saw that you have opened PR that fixes this. Its a over month and still not merged ... :(

Bump, do you plan to merge this PR?
I'm just leaving this issue that ideally sums, that you don't care about community bugs as much as you could care. https://github.com/dotnet/maui/issues/109

i found cleaner workaround for this issue. Simply set Device.Idiom property via reflection on app start

 public static class FixXamarin
    {
        public static void FixDevice()
        {
            var map = new Dictionary<DeviceIdiom, TargetIdiom>()
            {
                [DeviceIdiom.Desktop] = TargetIdiom.Desktop,
                [DeviceIdiom.Phone] = TargetIdiom.Phone,
                [DeviceIdiom.Tablet] = TargetIdiom.Tablet,
                [DeviceIdiom.Unknown] = TargetIdiom.Unsupported,
                [DeviceIdiom.Watch] = TargetIdiom.Watch,
                [DeviceIdiom.TV] = TargetIdiom.TV,
            };

            var deviceIdiomProperty = typeof(Device).GetProperty("Idiom", BindingFlags.Public | BindingFlags.Static);
            var mappedIdiom = map[Xamarin.Essentials.DeviceInfo.Idiom];
            deviceIdiomProperty?.SetValue(null, mappedIdiom);
        }
    }
protected override void OnCreate(Bundle bundle)
{
      FixXamarin.FixDevice(); // before init if forms using Incorrect values from Device.Idiom
      Forms.Init(this, bundle);
      FixXamarin.FixDevice(); // and after init for extra safety if Forms reseted value Device.Idiom 
}
Was this page helpful?
0 / 5 - 0 ratings