pythonjavachaquopy

chaquopy and android: interactive dialog


I have been trying to implement this functionality for a while, but all my approaches failed.

I would like to have some python code opening a dialog in the android app and wait for the user to click on the ok button (this is the first step I want to complete before I can create more complex YES/NO dialogs).

So far the only behavior I'm able to obtain is a non-blocking dialog, not matter what I try (signals, sockets, shared variables) but it seems like that the dialog is not shown until the python code has ended its execution.

here is my example that uses a global variable to confirm the user has dismissed the dialog:

Python

from java import dynamic_proxy, jboolean, jvoid, Override, static_proxy
from java.lang import Runnable
from com.chaquo.python import Python
from android.app import AlertDialog
from android.content import DialogInterface
import threading

state_done = False

def open_dialog(activity):
    def show_dialog():
        print('Dialog shown')
        builder = AlertDialog.Builder(activity)
        builder.setTitle("Title")
        builder.setMessage("This is a simple dialog from Python!")

        class Listener(dynamic_proxy(DialogInterface.OnClickListener)):
            def onClick(self, dialog, which):
                print("OK button pressed")
                state_done = True

        listener = Listener()

        builder.setPositiveButton("OK", listener)
        dialog = builder.create()
        dialog.show()
        print('Dialog shown')

    class R(dynamic_proxy(Runnable)):
        def run(self):
            show_dialog()
    
    def dummy():
        activity.runOnUiThread(R())
    
    dialog_thread = threading.Thread(target=dummy)
    dialog_thread.start()
    dialog_thread.join()
    while not state_done:
        pass
    print('done')

Java

if (!Python.isStarted()) {
    Python.start(new AndroidPlatform(this));
}
Python py = Python.getInstance();
PyObject pyObject = py.getModule("your_script");
pyObject.callAttr("open_dialog", this);

few notes: with this code, no print is ever executed, it is as if show_dialog is never called in the first place, no chances that anything is displayed. If I remove the while loop all the prints are executed in the following order:

I/python.stdout: done
I/python.stdout: show_dialog
I/python.stdout: Dialog shown
*here i press the ok button*
I/python.stdout: OK button pressed
I/python.stdout: dismissed

Is there a way to create a blocking dialog that interface with python directly? indirect solutions are welcomed as well, but even with java callbacks I get the same behavior.

SOLUTION by @mhsmith:

        if (!Python.isStarted()) {
            Python.start(new AndroidPlatform(this));
        }

        MainActivity activity = this;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // Your code here
                Python py = Python.getInstance();
                PyObject pyObject = py.getModule("your_script");
                pyObject.callAttr("open_dialog", activity);
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();

Solution

  • Everything on Android runs on the UI thread unless you explicitly create another thread. So you're probably calling open_dialog on the UI thread as well, in which case the while loop will block forever, and the show_dialog call that was queued on the same thread by runOnUiThread will never get a chance to run.

    Try calling open_dialog on a background thread instead. You can create it either from Java or from Python. In that case, there will obviously be no need for the dialog_thread.

    You might also want to try BeeWare, which is a higher-level Python app toolkit which runs on Android with the help of Chaquopy, and also on every other major desktop and mobile platform. Here are links to the tutorial, and to the dialog API, which allows you to get the result of a dialog using Python async syntax.