dankito / RichTextEditor

Rich text WYSIWYG editor for Android and JavaFX
Apache License 2.0
92 stars 36 forks source link

getHtml returns null content when not focused #8

Closed E-Mans-Application closed 5 years ago

E-Mans-Application commented 6 years ago

Hello,

The getHtml method always returns <p></p> when the editor is not focused. It returns the appropriate value as soon as the editor is focused back.

dankito commented 6 years ago

Can you provide any example so i can see the issue? (From just looking at the code i currently cannot see it.)

Is this also true after you executed some commands (e. g. setting text to bold or inserting an image)?

And does using instead of getHtml() this method work?

editor.retrieveCurrentHtmlAsync(new Function1<String, Unit>() {
  @Override
  public Unit invoke(String html) {
    // handle html here
    return null; // return whatever you what, a return is just needed due to translation from Kotlin to Java
  }
});

If you want to debug this issue yourself, go to class AndroidJavaScriptExecutor -> editorWebViewClient -> shouldOverrideUrlLoading() (which one of the two depends on the version of your Android). Then execute any command (e. g. bold). Check if parent class' shouldOverrideUrlLoading() gets called and then editorStateChanged(). There the current html gets saved to field this.html (this is what gets returned by getHtml() ).

Then unfocus the editor and see what happens then. Does this provide an explanation and can you see an workaround?

E-Mans-Application commented 6 years ago

There is not really a code: When the activity is created, the application downloads the content on the server and programmatically sets the editor's html. Then, the getHtml() function keeps returning an empty content until the user focuses the editor. I'm not sure if the editor is unfocused after using the toolbar or after selecting an option menu item, but the getHtml() return value remains correct thereafter (if I remember well).

Unfortunately, I'm currently not able to test anything else at the moment.

dankito commented 6 years ago

I may now found the reason. Does this issue only occur at editor's start up?

As at start up it takes some milliseconds till the editor is really loaded and the setHtml() method really gets executed after the editor has loaded. So at start up calling setHtml() and soon after getHtml() will not succeed as setHtml() is still waiting for the editor to be loaded.

Examples in Kotlin code:

Does not work:

    // at start-up
    editor.setHtml(html)

    val currentHtml = editor.getHtml()
    val doesEqual = currentHtml == html // does not equal, currentHtml is still "" as editor is not loaded yet

Does work:

    editor.javaScriptExecutor.addLoadedListener {
        editor.setHtml(html)

        val currentHtml = editor.getHtml()
        val doesEqual = currentHtml == html // does equal
    }

Can you confirm, that after the editor is loaded (you may also set a timer of let's say 5 seconds) this issue does not occur? If so, then please close this issue.

To circumvent this at the start up you may either

joel4015 commented 6 years ago

Same issue for me. Don't work when the editor is not focused.

But... I've found a workaround, using editor.requestFocus(); just before saving the editor content. Seems to do the trick :-)

E-Mans-Application commented 6 years ago

I had also tried editor.requestFocus()but it didn't always work for me.

I tried waiting for both the editor and the content to be loaded (by setting and checking static Boolean variables) before I call setHtml (the content was loaded from the database before the editor was loaded).

If I show getHtml() immediately after calling setHtml, the result is correct. However, if I call getHtml later (for example, in a Timer with a second delay), it is random: it sometimes returns the correct result, but it sometimes returns <p></p> again (the default editor's value).

(Sorry for the inconsistent indent, the tab button is not supported by the GitHub text editor.)

private boolean contentLoaded;
private boolean editorLoaded;
private JSONObject loadedContent;

public synchronized onContentLoaded(final JSONObject content){ //Called by the onPostExecute method in the AsyncTask responsible for loading the content.

       contentLoaded = true;
      if(editorLoaded){

          updateEditor(content); //calls editor.setHtml and performs other required actions.
      }else{
         loadedContent = content;
}

}

 @Override
    protected void onCreate(final Bundle savedInstanceState) {

[...]

 this.editor.getJavaScriptExecutor().addLoadedListener(new Function0<Unit>() {
            @Override
            public Unit invoke() {
                synchronized (MyActivity.this) {
                    editorLoaded = true;

                    if (contentLoaded) {
                       runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                updateEditor(loadedContent);
                                Log.e(Variables.LOG_TAG, "editor loaded");
                                Log.e(Variables.LOG_TAG, editor.getHtml()); // correct value

                            }
                        });

                    }
                }

                final Timer timer = new Timer();
                timer.schedule(new TimerTask() {

                    @Override
                    public void run() {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Log.e(Variables.LOG_TAG, "timer: " + editor.getHtml()); //random (so unreliable) result.
                            }
                        });

                    }
                }, 1000);
                return null;
            }
        });
}

In all cases, the editor displays the correct html, and the getHtml remains correct when the user clicks on the editor.

editor.retrieveCurrentHtmlAsync(new Function1<String, Unit>() {
                    @Override
                    public Unit invoke(final String html) {
                        // handle html here
                        Log.e(Variables.LOG_TAG,  "invoke: " + html);
                        return null; // return whatever you what, a return is just needed due to translation from Kotlin to Java
                    }
                });

This code always works, but it is not convenient to use.

dankito commented 6 years ago

@E-Mans-Application: I tried now for more than an hour, but still could not reproduce the issue with the code you provided. May it be due to missing implementation of updateEditor() or the first part of onCreate().

But it sounds to me like a timing issue between when the content gets set in updateEditor() (there are two ways how updateEditor() can get called) and when it gets retrieved from editor in TimerTask.

If you start the Timer in updateEditor(), does it than work reliably?

In general, in updateEditor() if you call setHtml() and shortly after getHtml() (both on the UI thread!), does it work then?

E-Mans-Application commented 6 years ago

As I said, calling getHtml() immediately after setHtml() works. However, the call in the Timer is not reliable, and neither is the call after the user clicks on "Save content to database". (This is not due to the fact the editor is not loaded yet, because the save is ignored if not both the editor and the content are loaded, and I click on the save button after the timer executes.) The timer and the save method gives the same html, though. The delay between the loading of the editor and the click of the user is unpredictable, but there is necessarily a delay.

If I start the Timer in updateEditor, the result is the same. It is often correct, but sometimes gives <p></p>

dankito commented 6 years ago

OK, then there's clearly a bug. I still have to figure out how to reproduce it. May i find it the next days where i plan to do some tests and code rewrites.

But anyway, as getHtml() is also due to other reasons unreliable (see the method comment), i strongly recommend to use retrieveCurrentHtmlAsync() as this reliably returns current html. But to the very nature of the underlying JavaScript bridge this method has to be asynchronous, sorry.

To make its usage easier under Java i added in version 2.0.0 RetrieveCurrentHtmlCallback. Is it now a bit more convenient to use for you?

Also i renamed getHtml() to getCachedHtml() to make clear that it's not necessarily current html.

dankito commented 6 years ago

To make it even clearer in version 2.0.1 I renamed retrieveCurrentHtmlAsync() to getCurrentHtmlAsync(). (I know, according to semantic versioning there should be no api breaking changes in patch releases, sorry for that!)

Also updated README accordingly.

In the next minor release i may also remove getCachedHtml() so that no one gets confused by it.

E-Mans-Application commented 6 years ago

I will make all the changes needed to implement the async method, then I will tell you if it works properly.

E-Mans-Application commented 6 years ago

The async method seems to work properly, thank you. The delay before calling getCurrentHtmlAsync() and the actual obtaining of the html is not disruptive.

PS: before to close this issue, you should consider replacing "compile" with "implementation" or "api" in README (Gradle setup section).

sunny52525 commented 4 years ago

thank you random guy from 2 years ago.