Skip to content

Commit 78741d6

Browse files
Merge pull request #211 from syncfusion/copilot/fix-209
Fix SfTextInputLayout crash on Android with very small container widths
2 parents 8c72682 + c7d78c5 commit 78741d6

File tree

3 files changed

+99
-22
lines changed

3 files changed

+99
-22
lines changed

maui/src/Core/Extensions/CanvasExtensions.Android.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Android.Graphics;
33
using Android.Text;
44
using Microsoft.Maui.Graphics.Platform;
5+
using System;
56
using Font = Microsoft.Maui.Font;
67
using Paint = Android.Graphics.Paint;
78
using Rect = Microsoft.Maui.Graphics.Rect;
@@ -106,7 +107,9 @@ public static void DrawText(this ICanvas canvas, string value, Rect rect, Horizo
106107
alignment = Android.Text.Layout.Alignment.AlignOpposite;
107108
}
108109

109-
StaticTextLayout layout = new StaticTextLayout(value, paint, (int)(rect.Width * nativeCanvas.DisplayScale), alignment, 1.0f, 0.0f, false);
110+
// Ensure width is positive to prevent Android StaticLayout crashes
111+
int layoutWidth = Math.Max(1, (int)(rect.Width * nativeCanvas.DisplayScale));
112+
StaticTextLayout layout = new StaticTextLayout(value, paint, layoutWidth, alignment, 1.0f, 0.0f, false);
110113
double rectDensityHeight = rect.Height * nativeCanvas.DisplayScale;
111114
//// Check the layout does not accommodate the text inside the specified rect then
112115
//// restrict the text rendering with line count.

maui/src/TextInputLayout/SfTextInputLayout.Methods.cs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Syncfusion.Maui.Toolkit.NumericUpDown;
55
using Syncfusion.Maui.Toolkit.NumericEntry;
66
using Syncfusion.Maui.Toolkit.Themes;
7+
using System;
78
using Path = Microsoft.Maui.Controls.Shapes.Path;
89

910
namespace Syncfusion.Maui.Toolkit.TextInputLayout
@@ -1044,7 +1045,7 @@ void UpdateContentPosition()
10441045
_viewBounds.X = (int)_leadViewWidth;
10451046
UpdatePosition();
10461047
_viewBounds.Y = 0;
1047-
_viewBounds.Width = (int)(Width - _leadViewWidth - _trailViewWidth);
1048+
_viewBounds.Width = (int)Math.Max(1, Width - _leadViewWidth - _trailViewWidth);
10481049
_viewBounds.Height = (int)Height;
10491050

10501051
if (EnablePasswordVisibilityToggle && !ShowUpDownButton)
@@ -1061,7 +1062,10 @@ void UpdateContentPosition()
10611062
_viewBounds.Width -= (float)(IconSize * (IsUpDownVerticalAlignment ? 1 : 2));
10621063
}
10631064

1064-
if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
1065+
// Ensure width doesn't become negative after all subtractions
1066+
_viewBounds.Width = (float)Math.Max(1, _viewBounds.Width);
1067+
1068+
if (_viewBounds.Height >= 0 && _viewBounds.Width >= 0)
10651069
{
10661070
AbsoluteLayout.SetLayoutBounds(Content, _viewBounds);
10671071
}
@@ -1117,7 +1121,7 @@ void UpdateLeadingViewPosition()
11171121
LeadingView.VerticalOptions = LayoutOptions.End;
11181122
}
11191123

1120-
if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
1124+
if (_viewBounds.Height >= 0 && _viewBounds.Width >= 0)
11211125
{
11221126
AbsoluteLayout.SetLayoutBounds(LeadingView, _viewBounds);
11231127
}
@@ -1151,7 +1155,7 @@ void UpdateTrailingViewPosition()
11511155
TrailingView.VerticalOptions = LayoutOptions.End;
11521156
}
11531157

1154-
if (_viewBounds.Height >= 0 || _viewBounds.Width >= 0)
1158+
if (_viewBounds.Height >= 0 && _viewBounds.Width >= 0)
11551159
{
11561160
AbsoluteLayout.SetLayoutBounds(TrailingView, _viewBounds);
11571161
}
@@ -1454,7 +1458,7 @@ void UpdateHelperTextPosition()
14541458
_helperTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
14551459
}
14561460

1457-
_helperTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
1461+
_helperTextRect.Width = (int)Math.Max(1, Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
14581462
_helperTextRect.Height = HelperTextSize.Height;
14591463

14601464
if (IsRTL)
@@ -1471,11 +1475,13 @@ double GetAssistiveTextWidth()
14711475

14721476
if (Width >= 0)
14731477
{
1474-
return Width - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
1478+
var calculatedWidth = Width - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
1479+
return Math.Max(1, calculatedWidth);
14751480
}
14761481
else if (WidthRequest != -1)
14771482
{
1478-
return WidthRequest - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
1483+
var calculatedWidth = WidthRequest - (IsNone ? 0 : StartX + DefaultAssistiveLabelPadding) - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth;
1484+
return Math.Max(1, calculatedWidth);
14791485
}
14801486
else
14811487
{
@@ -1495,11 +1501,13 @@ double GetHintTextWidth()
14951501

14961502
if (Width >= 0)
14971503
{
1498-
return Width - (IsNone ? 0 : ((2 * StartX) + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
1504+
var calculatedWidth = Width - (IsNone ? 0 : ((2 * StartX) + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
1505+
return Math.Max(1, calculatedWidth);
14991506
}
15001507
else if (WidthRequest != -1)
15011508
{
1502-
return WidthRequest - (IsNone ? 0 : 2 * (StartX + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
1509+
var calculatedWidth = WidthRequest - (IsNone ? 0 : 2 * (StartX + DefaultAssistiveLabelPadding)) - _trailViewWidth - _leadViewWidth;
1510+
return Math.Max(1, calculatedWidth);
15031511
}
15041512
else
15051513
{
@@ -1525,7 +1533,7 @@ void UpdateErrorTextPosition()
15251533
_errorTextRect.Y = (int)(Height - TotalAssistiveTextHeight());
15261534
}
15271535

1528-
_errorTextRect.Width = (int)(Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
1536+
_errorTextRect.Width = (int)Math.Max(1, Width - startPadding - CounterTextPadding - DefaultAssistiveLabelPadding - ((ShowCharCount) ? CounterTextSize.Width + CounterTextPadding : 0) - _trailViewWidth - _leadViewWidth);
15291537
_errorTextRect.Height = ErrorTextSize.Height;
15301538

15311539
if (IsRTL)
@@ -1585,11 +1593,11 @@ void UpdateOutlineRectF()
15851593
UpdateTrailViewWidthForBorder();
15861594
if (BaseLineMaxHeight <= 2)
15871595
{
1588-
_outlineRectF.Width = (float)((Width - (BaseLineMaxHeight * 2)) - _leadViewWidth - _trailViewWidth);
1596+
_outlineRectF.Width = (float)Math.Max(1, (Width - (BaseLineMaxHeight * 2)) - _leadViewWidth - _trailViewWidth);
15891597
}
15901598
else
15911599
{
1592-
_outlineRectF.Width = (float)((Width - (BaseLineMaxHeight)) - _leadViewWidth - _trailViewWidth);
1600+
_outlineRectF.Width = (float)Math.Max(1, (Width - (BaseLineMaxHeight)) - _leadViewWidth - _trailViewWidth);
15931601
}
15941602

15951603
_outlineRectF.Height = (float)(Height - _outlineRectF.Y - TotalAssistiveTextHeight() - AssistiveLabelPadding);
@@ -1601,14 +1609,14 @@ void UpdateOutlineBackgroundRectF()
16011609
{
16021610
_backgroundRectF.X = (float)(_outlineRectF.X + (FocusedStrokeThickness / 2));
16031611
_backgroundRectF.Y = (float)(_outlineRectF.Y + (FocusedStrokeThickness / 2));
1604-
_backgroundRectF.Width = (float)(_outlineRectF.Width - (FocusedStrokeThickness));
1612+
_backgroundRectF.Width = (float)Math.Max(1, _outlineRectF.Width - FocusedStrokeThickness);
16051613
_backgroundRectF.Height = (float)(_outlineRectF.Height - (FocusedStrokeThickness));
16061614
}
16071615
else
16081616
{
16091617
_backgroundRectF.X = (float)(_outlineRectF.X + (UnfocusedStrokeThickness / 2));
16101618
_backgroundRectF.Y = (float)(_outlineRectF.Y + (UnfocusedStrokeThickness / 2));
1611-
_backgroundRectF.Width = (float)(_outlineRectF.Width - (UnfocusedStrokeThickness));
1619+
_backgroundRectF.Width = (float)Math.Max(1, _outlineRectF.Width - UnfocusedStrokeThickness);
16121620
_backgroundRectF.Height = (float)(_outlineRectF.Height - (UnfocusedStrokeThickness));
16131621
}
16141622

@@ -1623,7 +1631,7 @@ void UpdateBackgroundRectF()
16231631
UpdateTrailViewWidthForBorder();
16241632
_backgroundRectF.X = (float)_leadViewWidth;
16251633
_backgroundRectF.Y = 0;
1626-
_backgroundRectF.Width = (float)(Width - _leadViewWidth - _trailViewWidth);
1634+
_backgroundRectF.Width = (float)Math.Max(1, Width - _leadViewWidth - _trailViewWidth);
16271635
if (BaseLineMaxHeight <= 2)
16281636
{
16291637
_backgroundRectF.Height = (float)(Height - TotalAssistiveTextHeight() - AssistiveLabelPadding);

maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Layout/SfTextInputLayoutUnitTests.cs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -715,12 +715,12 @@ public void TestUpdateIconRectFMethod()
715715
TrailingViewPosition = ViewPosition.Outside
716716
};
717717
InvokePrivateMethod(inputLayout, "UpdateIconRectF");
718-
var outRectExpected = new RectF() { X = 21, Y = 2, Width = -43, Height = -24 };
718+
var outRectExpected = new RectF() { X = 21, Y = 2, Width = 1, Height = -24 };
719719
var resultOutRect = GetPrivateField(inputLayout, "_outlineRectF");
720-
Assert.Equal(resultOutRect, outRectExpected);
721-
var backgroundRectExpected = new RectF() { X = 19, Y = 0, Width = -39, Height = -22 };
720+
Assert.Equal(outRectExpected, resultOutRect);
721+
var backgroundRectExpected = new RectF() { X = 19, Y = 0, Width = 1, Height = -22 };
722722
var resultBackgroundRect = GetPrivateField(inputLayout, "_backgroundRectF");
723-
Assert.Equal(resultBackgroundRect, backgroundRectExpected);
723+
Assert.Equal(backgroundRectExpected, resultBackgroundRect);
724724
var _passwordToggleIconRectF = new RectF() { X = -83, Y = -27, Width = 32, Height = 32 };
725725
var resultPassToggleRect = GetPrivateField(inputLayout, "_passwordToggleIconRectF");
726726
Assert.Equal(resultPassToggleRect, _passwordToggleIconRectF);
@@ -816,7 +816,7 @@ public void TestUpdateHelperTextPositionMethod()
816816
TrailingView = new Entry(),
817817
TrailingViewPosition = ViewPosition.Outside
818818
};
819-
var expected = new RectF() { X = 60, Y = -18, Width = -96, Height = 16 };
819+
var expected = new RectF() { X = 60, Y = -18, Width = 1, Height = 16 };
820820
InvokePrivateMethod(inputLayout, "UpdateHelperTextPosition");
821821
var result = GetPrivateField(inputLayout, "_helperTextRect");
822822
Assert.Equal(expected, result);
@@ -873,7 +873,7 @@ public void TestUpdateErrorTextPositionMethod()
873873
{
874874
var inputLayout = new SfTextInputLayout();
875875
InvokePrivateMethod(inputLayout, "UpdateErrorTextPosition");
876-
RectF expectedRect = new RectF() { X = 16, Y = -18, Width = -33, Height = 16 };
876+
RectF expectedRect = new RectF() { X = 16, Y = -18, Width = 1, Height = 16 };
877877
var result = GetPrivateField(inputLayout, "_errorTextRect");
878878
Assert.Equal(expectedRect, result);
879879
}
@@ -1191,6 +1191,72 @@ public void Content_TimePicker(ContainerType container)
11911191
Assert.Equal(new TimeSpan(10, 30, 0), timePicker.Time);
11921192
}
11931193

1194+
[Fact]
1195+
public void VerySmallContainerWidth_ShouldNotCrash()
1196+
{
1197+
// Test for the fix of issue where very small container widths cause Android crash
1198+
// with Java.Lang.IllegalArgumentException: 'Layout: -46 < 0'
1199+
var inputLayout = new SfTextInputLayout
1200+
{
1201+
Content = new Entry { Text = "Test" },
1202+
Hint = "Name",
1203+
WidthRequest = 10, // Very small width that could cause negative layout bounds
1204+
HeightRequest = 50
1205+
};
1206+
1207+
// The control should handle very small dimensions gracefully without throwing exceptions
1208+
Assert.NotNull(inputLayout);
1209+
Assert.NotNull(inputLayout.Content);
1210+
Assert.Equal("Test", ((Entry)inputLayout.Content).Text);
1211+
Assert.Equal("Name", inputLayout.Hint);
1212+
}
1213+
1214+
[Fact]
1215+
public void VerySmallContainerWidthWithLeadingAndTrailingViews_ShouldNotCrash()
1216+
{
1217+
// Test for the fix where very small container widths with leading/trailing views cause crashes
1218+
var inputLayout = new SfTextInputLayout
1219+
{
1220+
Content = new Entry { Text = "Test" },
1221+
Hint = "Name",
1222+
LeadingView = new Label { Text = "L" },
1223+
TrailingView = new Label { Text = "T" },
1224+
ShowLeadingView = true,
1225+
ShowTrailingView = true,
1226+
WidthRequest = 10, // Very small width that could cause negative layout bounds
1227+
HeightRequest = 50
1228+
};
1229+
1230+
// The control should handle very small dimensions gracefully without throwing exceptions
1231+
Assert.NotNull(inputLayout);
1232+
Assert.NotNull(inputLayout.Content);
1233+
Assert.NotNull(inputLayout.LeadingView);
1234+
Assert.NotNull(inputLayout.TrailingView);
1235+
Assert.True(inputLayout.ShowLeadingView);
1236+
Assert.True(inputLayout.ShowTrailingView);
1237+
}
1238+
1239+
[Fact]
1240+
public void VerySmallContainerWidthWithErrorAndHelperText_ShouldNotCrash()
1241+
{
1242+
// Test for the fix where very small container widths with error/helper text cause crashes
1243+
var inputLayout = new SfTextInputLayout
1244+
{
1245+
Content = new Entry { Text = "Test" },
1246+
Hint = "Name",
1247+
ErrorText = "This is an error message",
1248+
HelperText = "This is a helper message",
1249+
WidthRequest = 10, // Very small width that could cause negative text layout bounds
1250+
HeightRequest = 50
1251+
};
1252+
1253+
// The control should handle very small dimensions gracefully without throwing exceptions
1254+
Assert.NotNull(inputLayout);
1255+
Assert.NotNull(inputLayout.Content);
1256+
Assert.Equal("This is an error message", inputLayout.ErrorText);
1257+
Assert.Equal("This is a helper message", inputLayout.HelperText);
1258+
}
1259+
11941260

11951261
#endregion
11961262
}

0 commit comments

Comments
 (0)