Skip to content

Commit

Permalink
[android] improve performance of Entry.MaxLength
Browse files Browse the repository at this point in the history
Context: dotnet#10713

When investigating a customer sample:

* Navigate from a Shell flyout

* To a new page with several `Entry` controls

When profiling on a Pixel 5, it seems one hot path is `Entry.MaxLength`:

    18.52ms (0.22%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.UpdateMaxLength(Android.Widget.EditText,Microsoft.Maui.IEntry)
    16.03ms (0.19%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.UpdateMaxLength(Android.Widget.EditText,int)
    12.16ms (0.14%) microsoft.maui!Microsoft.Maui.Platform.EditTextExtensions.SetLengthFilter(Android.Widget.EditText,int)

* `EditTextExtensions.UpdateMaxLength()` calls
    * `EditText.Text` getter and setter
    * `EditTextExtensions.SetLengthFilter()` calls
        * `EditText.Get/SetFilters()`

What happens is we end up marshaling strings and `IInputFilter[]` back
and forth between C# and Java for every `Entry` on the page.

This seems like a prime candidate to move code from C# to Java. I
tried to just port the code as-is without changing logic -- so we
*should* get the exact same behavior as before.

Since all `Entry`s go through this code path (even ones with a default
value for `MaxLength`), this improves the performance of all `Entry`
and `SearchBar` on Android. With these changes in place, the calls to
`EditTextExtensions.UpdateMaxLength()` are now so fast they are
missing from the trace now, saving ~19ms when navigating to this page.

One note, is I found this was the recommended way to create a mutable
`List<T>` from an array in Java:

    List<InputFilter> currentFilters = new ArrayList<>(Arrays.asList(editText.getFilters()));

https://stackoverflow.com/a/11659198

Makes me appreciate C#! :)
  • Loading branch information
jonathanpeppers committed Jun 13, 2023
1 parent c096b3a commit 846ca75
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 35 deletions.
20 changes: 20 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ public async Task TextTransformUpdated(string text, TextTransform transform, str
Assert.Equal(expected, platformText);
}

[Fact]
public async Task MaxLengthTrims()
{
var entry = new Entry
{
Text = "This is text",
};

await InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler<EntryHandler>(entry);
entry.MaxLength = 4;
Assert.Equal("This", entry.Text);
var platformText = await GetPlatformText(handler);
Assert.Equal("This", platformText);
});
}

#if WINDOWS
// Only Windows needs the IsReadOnly workaround for MaxLength==0 to prevent text from being entered
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
import android.graphics.drawable.PaintDrawable;
import android.net.Uri;
import android.os.Build;
import android.text.Editable;
import android.text.InputFilter;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
Expand Down Expand Up @@ -47,6 +50,9 @@

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PlatformInterop {
public static void requestLayoutIfNeeded(View view) {
Expand Down Expand Up @@ -455,6 +461,53 @@ private static boolean isColorType(TypedValue value)
}
}

/**
* Sets the maxLength of an EditText
* @param editText
* @param maxLength
*/
public static void updateMaxLength(@NonNull EditText editText, int maxLength)
{
setLengthFilter(editText, maxLength);

Editable currentText = editText.getText();
if (currentText.length() > maxLength)
{
editText.setText(currentText.subSequence(0, maxLength));
}
}

/**
* Updates the InputFilter[] of an EditText. Used for Entry and SearchBar.
* @param editText
* @param maxLength
*/
public static void setLengthFilter(@NonNull EditText editText, int maxLength)
{
if (maxLength == -1)
maxLength = Integer.MAX_VALUE;

List<InputFilter> currentFilters = new ArrayList<>(Arrays.asList(editText.getFilters()));
boolean changed = false;
for (int i = 0; i < currentFilters.size(); i++) {
InputFilter filter = currentFilters.get(i);
if (filter instanceof InputFilter.LengthFilter) {
currentFilters.remove(i);
changed = true;
break;
}
}

if (maxLength >= 0) {
currentFilters.add(new InputFilter.LengthFilter(maxLength));
changed = true;
}
if (changed) {
InputFilter[] newFilter = new InputFilter[currentFilters.size()];
editText.setFilters(currentFilters.toArray(newFilter));
}
}

private static class ColorStates
{
static final int[] EMPTY = new int[] { };
Expand Down
39 changes: 4 additions & 35 deletions src/Core/src/Platform/Android/EditTextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,42 +82,11 @@ public static void UpdateMaxLength(this EditText editText, IEntry entry) =>
public static void UpdateMaxLength(this EditText editText, IEditor editor) =>
UpdateMaxLength(editText, editor.MaxLength);

public static void UpdateMaxLength(this EditText editText, int maxLength)
{
editText.SetLengthFilter(maxLength);

var newText = editText.Text.TrimToMaxLength(maxLength);
if (editText.Text != newText)
editText.Text = newText;
}

public static void SetLengthFilter(this EditText editText, int maxLength)
{
if (maxLength == -1)
maxLength = int.MaxValue;

var currentFilters = new List<IInputFilter>(editText.GetFilters() ?? new IInputFilter[0]);
var changed = false;
public static void UpdateMaxLength(this EditText editText, int maxLength) =>
PlatformInterop.UpdateMaxLength(editText, maxLength);

for (var i = 0; i < currentFilters.Count; i++)
{
if (currentFilters[i] is InputFilterLengthFilter)
{
currentFilters.RemoveAt(i);
changed = true;
break;
}
}

if (maxLength >= 0)
{
currentFilters.Add(new InputFilterLengthFilter(maxLength));
changed = true;
}

if (changed)
editText.SetFilters(currentFilters.ToArray());
}
public static void SetLengthFilter(this EditText editText, int maxLength) =>
PlatformInterop.SetLengthFilter(editText, maxLength);

public static void UpdatePlaceholder(this EditText editText, IPlaceholder textInput)
{
Expand Down
Binary file modified src/Core/src/maui.aar
Binary file not shown.

0 comments on commit 846ca75

Please sign in to comment.