How do I use EditableFactory in Xamarin Forms?

97 views Asked by At

My EditorFactory is crashing under Google Play testing. The class is embedded in a Renderer I have wrote to extend the Xamarin Forms Editor control.

Error:

    FATAL EXCEPTION: Thread-3
Process: com.MyApp.MyApp, PID: 23307
android.runtime.JavaProxyThrowable: System.InvalidCastException: Specified cast is not valid.
  at MyApp.EditorExRenderer+NoCopyEditableFactory.NewEditable (Java.Lang.ICharSequence source) [0x00000] in <0c33163fb4704d40bbc13286b4698089>:0 
  at Android.Text.EditableFactory.n_NewEditable_Ljava_lang_CharSequence_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_source) [0x0000f] in <0ce592b1e5104b27bf696797bee3407f>:0 
  at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_L (_JniMarshal_PPL_L callback, System.IntPtr jnienv, System.IntPtr klazz, System.IntPtr p0) [0x00005] in <0ce592b1e5104b27bf696797bee3407f>:0 
        at crc645beb7f91af2d2a49.EditorExRenderer_NoCopyEditableFactory.n_newEditable(Native Method)
        at crc645beb7f91af2d2a49.EditorExRenderer_NoCopyEditableFactory.newEditable(EditorExRenderer_NoCopyEditableFactory.java:29)
        at android.widget.TextView.setText(TextView.java:6219)
        at android.widget.TextView.setText(TextView.java:6156)
        at android.widget.EditText.setText(EditText.java:121)
        at android.widget.TextView.setText(TextView.java:6108)
        at androidx.test.espresso.action.ReplaceTextAction.perform(ReplaceTextAction.java:1)
        at androidx.test.tools.crawler.platform.hybrid.HybridInteractionController$1.perform(HybridInteractionController.java:2)
        at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
        at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:21)
        at androidx.test.espresso.ViewInteraction.-$$Nest$mdoPerform(Unknown Source:0)
        at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:6)
        at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7664)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

A skeleton of my renderer code is below. I hope it's enough to give a rough idea of how I'm using the factory:

public class EditorExRenderer : EditorEx
{
  public EditorExRenderer(Android.Content.Context context) : base(context)
  {
  }

  private class NoCopyEditableFactory : EditableFactory // Editable.Factory in Java
  {
    public override IEditable NewEditable(ICharSequence source) => (IEditable)source;
  }

  void Load(string displayString)
  {
    ...
    ...
    SetSpannable(displayString);
    ...
    ...
  }

  void SetSpannable(string text)
  {
    _spannable = new SpannableStringBuilder(text);

    //set span
    Span span = new Span();
    ...
    ...
    _spannable.SetSpan(span, start, end, SpanTypes.InclusiveInclusive);
    ...
    ...

    Control.SetText(_spannable, TextView.BufferType.Spannable);

  }

  private void Control_TextChanged(object sender, Android.Text.TextChangedEventArgs e)
  {
    ...
    ...

    SetSpannable(newText);
  }

}

Any ideas of why it might be crashing?

2

There are 2 answers

12
Liyun Zhang - MSFT On

ICharSequence and IEditable are two different kinds of interface. So we can't do a type cast between them.

In addition, you can try to create a instance of the EditableFactory and use it somewhere. Such as:

var NoCopyEditableFactory = new EditableFactory();
Control.SetEditableFactory(NoCopyEditableFactory);
  
5
jho On

I had copied the NoCopyEditableFactory blindly from another StackOverflow post, so I removed it and everything works fine:

public class EditorExRenderer : EditorEx
{
  public EditorExRenderer(Android.Content.Context context) : base(context)
  {
  }

    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);

        if (Control != null)
        {
            ...
            ... 
            //****************
            //create new SpannableFactory, rather than NoCopyEditableFactory
            Control.SetSpannableFactory(new SpannableFactory());
            ...
            ... 

        }

    }

  //private class NoCopyEditableFactory : EditableFactory // Editable.Factory in Java
  //{
    //public override IEditable NewEditable(ICharSequence source) => (IEditable)source;
  //}

  void Load(string displayString)
  {
    ...
    ...
    SetSpannable(displayString);
    ...
    ...
  }

  void SetSpannable(string text)
  {
    _spannable = new SpannableStringBuilder(text);

    //set span
    ForegroundColorSpanEx span = new Span();
    ...
    ...
    _spannable.SetSpan(span, start, end, SpanTypes.InclusiveInclusive);
    ...
    ...

    Control.SetText(_spannable, TextView.BufferType.Spannable);

  }

  private void Control_TextChanged(object sender, Android.Text.TextChangedEventArgs e)
  {
    ...
    ...

    SetSpannable(newText);
  }

}

UPDATE

I discovered my new code is creating errors. With the previous NoCopyEditableFactory, the following SpanChange method could dynamically change the color of the text font. With my new code above the dynamic coloring doesn't work. I tried using the default EditableFactory, instead of the NoCopyEditableFactory, and this also doesn't dynamically color the text.

So I am back to square one again. Any ideas?

void SpanChange(int index)
{
  Span span;
  int start, end;

  span = _spans[index];
  start = _spannable.GetSpanStart(span);
  end = _spannable.GetSpanEnd(span);

  span.SetColor(cb.Color);

  _spannable.SetSpan(span, start, end, SpanTypes.InclusiveInclusive);

  Invalidate();

}


public class ForegroundColorSpanEx : ForegroundColorSpan
{
  private Color Color;

    public ForegroundColorSpanEx(Color c)
      : base(c)
  {
    Color = c;
  }
  public override void UpdateDrawState(TextPaint textPaint)
  {
    base.UpdateDrawState(textPaint);
    textPaint.Color = Color;

  }
  public void SetColor(Color c)
  {
    Color = c;
  }
}