I'm trying to use Tesseract in flutter using the following package https://github.com/arrrrny/tesseract_ocr
I've download the app and run in.
The problem is that the extractText hangs the UI.
Looking at the Java code:
Thread t = new Thread(new Runnable() {
public void run() {
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getUTF8Text();
baseApi.end();
}
});
t.start();
try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); }
result.success(recognizedText[0]);
I can see that it is running on a new thread, so I expect it not to hang the app, but it still does.
I found this example:
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Call the desired channel message here.
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
result.success(recognizedText[0]);
}
});
from https://flutter.dev/docs/development/platform-integration/platform-channels#channels-and-platform-threading
but it also hangs the UI.
The docs also say
**Channels and Platform Threading**
Invoke all channel methods on the platform’s main thread when writing code on the platform side.
Can someone clarify this sentence?
According to Richard Heap answer, I tried to call a method from native to dart, passing the result:
Dart side:
_channel.setMethodCallHandler((call) {
print(call);
switch (call.method) {
case "extractTextResult":
final String result = call.arguments;
print(result);
}
var t;
return t;
});
Java side:
channel.invokeMethod("extractTextResult","hello");
if I call this method from the main thread, this works fine, but then the thread is blocking.
If I do
Thread t = new Thread(new Runnable() {
public void run() {
channel.invokeMethod("extractTextResult","test1231231");
}
});
t.start();
result.success("tst"); // return immediately
Then the app crashes with the following message:
I also tried:
Thread t = new Thread(new Runnable() {
public void run() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Call the desired channel message here.
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
result.success(recognizedText[0]);
// channel.invokeMethod("extractTextResult", "test1231231");
}
});
}
});
t.start();
result.success("tst");
which is what I understand that Richard Heap last comment meant, but It still hangs the ui.
I had the same Issue and fixed it with a MethodCallWrapper in TesseractOcrPlugin.java
This Code works for me (no Dart-code change is needed):
package io.paratoner.tesseract_ocr;
import com.googlecode.tesseract.android.TessBaseAPI;
import android.os.Handler;
import android.os.Looper;
import android.os.AsyncTask;
import java.io.File;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/** TesseractOcrPlugin */
public class TesseractOcrPlugin implements MethodCallHandler {
private static final int DEFAULT_PAGE_SEG_MODE = TessBaseAPI.PageSegMode.PSM_SINGLE_BLOCK;
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "tesseract_ocr");
channel.setMethodCallHandler(new TesseractOcrPlugin());
}
// MethodChannel.Result wrapper that responds on the platform thread.
private static class MethodResultWrapper implements Result {
private Result methodResult;
private Handler handler;
MethodResultWrapper(Result result) {
methodResult = result;
handler = new Handler(Looper.getMainLooper());
}
#Override
public void success(final Object result) {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.success(result);
}
});
}
#Override
public void error(final String errorCode, final String errorMessage, final Object errorDetails) {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.error(errorCode, errorMessage, errorDetails);
}
});
}
#Override
public void notImplemented() {
handler.post(new Runnable() {
#Override
public void run() {
methodResult.notImplemented();
}
});
}
}
#Override
public void onMethodCall(MethodCall call, Result rawResult) {
Result result = new MethodResultWrapper(rawResult);
if (call.method.equals("extractText")) {
final String tessDataPath = call.argument("tessData");
final String imagePath = call.argument("imagePath");
String DEFAULT_LANGUAGE = "eng";
if (call.argument("language") != null) {
DEFAULT_LANGUAGE = call.argument("language");
}
calculateResult(tessDataPath, imagePath, DEFAULT_LANGUAGE, result);
} else {
result.notImplemented();
}
}
private void calculateResult(final String tessDataPath, final String imagePath, final String language,
final Result result) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
final String[] recognizedText = new String[1];
final TessBaseAPI baseApi = new TessBaseAPI();
baseApi.init(tessDataPath, language);
final File tempFile = new File(imagePath);
baseApi.setPageSegMode(DEFAULT_PAGE_SEG_MODE);
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getUTF8Text();
baseApi.end();
result.success(recognizedText[0]);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}
}.execute();
}
}
By using join you're making the main thread wait for the background thread, blocking it. You have to remove the join and return a result immediately.
So, how do you return the ocr result, which won't be available immediately. When it becomes available, you then call a method from native to dart, passing the result. At the dart end, you then handle the result as any async event.
The point of the last paragraph of your question is that your result will become available on your background thread, so you'd want to call the native to dart method there. You can't. You have to post the method call code to the main looper - you already show some code for posting to the main looper which you can use as an example.
Based on Richard Heap answer I came up with this:
Dart code:
_channel.setMethodCallHandler((call) {
switch (call.method) {
case "extractTextResult":
final String result = call.arguments;
print(result);
}
var t;
return t;
});
Java code:
Thread t = new Thread(new Runnable() {
public void run() {
baseApi.setImage(tempFile);
recognizedText[0] = baseApi.getHOCRText(0);
baseApi.end();
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
channel.invokeMethod("extractTextResult", recognizedText[0]);
}
});
}
});
t.start();
result.success("tst");
explain:
This code will run the Java extractText in a separate thread, and when the result is ready it will hopp back to the ui thread with the call to Looper.getMainLooper() which will then send the message back to the Dart side which must receive the message on the ui thread, which is what this message means:
**Channels and Platform Threading**
Invoke all channel methods on the platform’s main thread when writing code on the platform side.
NOTE on the Dart side, this is still incomplete example since you then need to report to the ui that a message received, this can be done with a Completer, which is used to create and complete a future
At the end of your method channel just return the response back to dart side
Add this line at the end of method channel result.success(true)
full example
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"method-channel"
).setMethodCallHandler { call, result ->
if (call.method == "getFirebaseAppCheckDebugToken") {
...
result.success(true) // just add this line
}
}
}```
I have the first scene in which I have a registration button on click of the button I try to establish a connection to my server in a background thread. Now I want to go to next scene only when I gets 200 as response code from my server.
I have used Service class for background thread.I have also created a method to change the scene but I am not able to understand where and when to call mehod.
public class MainController implements Initializable {
int responseCodeFromServer;;
// creating background thread
private Service<Void>backgroundThread;
backgroundThread = new Service<Void>()
{
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception
{
// Now here we will try to establish the connection with the Server
EstablishServerConnection obj = new EstablishServerConnection();
responseCodeFromServer = obj.establishConnectionToServer(registrationBeanObj);
System.out.println("Response Code received in UI thread "+ responseCodeFromServer);
if(responseCodeFromServer == 200)
{
updateMessage("All Ok");
// now when we get response code as 200 then we need to take the user to the next window
}
else
{
updateMessage("Server Issue");
}
// TODO Auto-generated method stub
return null;
}
};
}
};
// we will define here what will happen when this background thread completes its job successfully (we can also try for failed or cancelled events)
backgroundThread.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{
#Override
public void handle(WorkerStateEvent event) {
// TODO Auto-generated method stub
if(responseCodeFromServer == 200)
{
System.out.println("Done");
}
// It is a good idea to unbind the label when our background task is finished
status.textProperty().unbind();
}
});
// we need to bind status label text property to the message property in our background thread
status.textProperty().bind(backgroundThread.messageProperty());
// we need to start our background thread
backgroundThread.restart();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
System.out.println("Hello World");
}
public void goToProductKey(ActionEvent event) throws IOException
{
Parent goToProductKeyParent = FXMLLoader.load(getClass().getResource("ProductKeyFXML.fxml"));
Scene goToProductKeyScene = new Scene(goToProductKeyParent);
// This line gets the stage Information
Stage window = (Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(goToProductKeyScene);
window.show();
}
My Question is I want to go to next scene only when i get 200 as response code from my server.I am new to JavaFX
backgroundThread.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{
#Override
public void handle(WorkerStateEvent event)
{
// TODO Auto-generated method stub
if(responseCodeFromServer == 1)
{
Parent goToProductKeyParent = null;
try {
goToProductKeyParent = FXMLLoader.load(getClass().getResource("ProductKeyFXML.fxml"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Scene goToProductKeyScene = new Scene(goToProductKeyParent);
// This line gets the stage Information
Stage window = (Stage) rootPane.getScene().getWindow();
//Stage window = (Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(goToProductKeyScene);
window.show();
}
// It is a good idea to unbind the label when our background task is finished
status.textProperty().unbind();
}
});
// we need to bind status label text property to the message property in our background thread
status.textProperty().bind(backgroundThread.messageProperty());
// we need to start our background thread
backgroundThread.start();
In a JavaFX application, I have a method which takes a long time on large input. I'm opening a dialog when it is loading and I'd like the user to be able to cancel/close out the dialog and the task will quit. I created a task and added its cancellation in the cancel button handling. But the cancellation doesn't happen, the task doesn't stop executing.
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
// calling a function that does heavy calculations in another class
};
task.setOnSucceeded(e -> {
startButton.setDisable(false);
});
}
new Thread(task).start();
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
System.out.println("Button handled");
task.cancel();
}
);
Why isn't the task getting canceled when the button clicked?
You have to check on the cancel state (see Task's Javadoc). Have a look at this MCVE:
public class Example extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
new AnotherClass().doHeavyCalculations(this);
return null;
}
};
Button start = new Button("Start");
start.setOnMouseClicked(event -> new Thread(task).start());
Button cancel = new Button("Cancel");
cancel.setOnMouseClicked(event -> task.cancel());
primaryStage.setScene(new Scene(new HBox(start, cancel)));
primaryStage.show();
}
private class AnotherClass {
public void doHeavyCalculations(Task<Void> task) {
while (true) {
if (task.isCancelled()) {
System.out.println("Canceling...");
break;
} else {
System.out.println("Working...");
}
}
}
}
}
Note that…
You should use Task#updateMessage(String) rather than printing to System.out, here it's just for demonstration.
Directly injecting the Task object creates a cyclic dependency. However, you can use a proxy or something else that fits your situation.
I interested in one interesting task. I have UI in JavaFx with another thread which updates UI. I started updates from Platform.runLater. Code:
private void startUpdateDaemon() {
updateUserStatus();
updateTable();
}
private void startUpdateDaemonTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(() -> {
startUpdateDaemon();
});
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
startUpdateDaemonTask();
}
Also I have place in another class where I updates UI:
private void startUpdateDaemonTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run() {
updateGameStatus();
}
});
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
So, finally I have two places with call "Platform.runLater" and different methods inside.
My question is Can I create only "one" method with one time call "Platform.runLater" and send to this method different methods which will be call ?? May be I can write finish method with consumers and send to him methods 'startUpdateDaemon()' and 'updateGameStatus()'?
Thanks a lot.
You can add a Runnable parameter to your method. This parameter is given to you Platform.runLater:
private void startUpdateDaemonTask(Runnable runner) {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(runner);
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Now you can invoke this method with your method references:
startUpdateDaemonTask(this::startUpdateDaemon);
startUpdateDaemonTask(this::updateGameStatus);
I have the following section of JavaFX that implements Service class:
public void processingImage() {
Task<Void> track = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
if (flag == false) {
if (someCondition) {
flag = true;
CommunicateServer.sendObject = new Object[2];
CommunicateServer.sendObject[0] = 6;
CommunicateServer.sendObject[1] = "hello";
myService.start();
flag = false;
System.out.println("this line does not print");
}
}
return null;
}
};
Thread th1 = new Thread(track);
th1.setDaemon(true);
th1.start();
}
And the MyService class is implemented as:
private class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
CommunicateServer.callSendObject(CommunicateServer.sendObject, true);
response = CommunicateServer.getObject();
System.out.println("this print should have been many times but only executed once!!!!");
return null;
}
};
}
}
My problem is although I expect the code to print this line does not print, the code actually does not print this. Moreover, the line this print should have been many times but only executed once!!!! is printed only once although I think it should have been printed many times. I don't know how to fix this problem. Any help or suggestion will be met with gratitude.
It's not really clear what you expect your code to do, but Service.start() should be called from the FX Application Thread. Since you are calling it from a background thread, this may be throwing an exception, preventing you reaching the System.out.println(...) statement.
Moreover, the service must be in the READY state to receive the call to start(), so on the second execution (if there is one), since the service has not been reset, you will get an IllegalArgumentException, exiting the call() method in the task defined in processingImage(). Hence your service will execute at most once.