I am trying to make an Android app with a Quart app running on Python with Chaquopy but as soon as i add a call to Kotlin from Python i get Error starting Python server: Can't create handler inside thread Thread[Thread-4,5,main] that has not called Looper.prepare()
from MainActivity. So i added a call to Looper but that gives me Error starting Python server: Method addObserver must be called on the main thread
even though the call to run the Quart app is in the thread where the Python interpreter is started.
Kotlin:
package com.example.chatterbot
import android.annotation.SuppressLint
import android.net.http.SslError
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.webkit.SslErrorHandler
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.chaquo.python.Python
import com.chaquo.python.android.AndroidPlatform
class MainActivity : AppCompatActivity() {
private val tag: String = "MainActivity" // Define a tag for your logs
private var serverPort: Int? = null
private lateinit var webView: WebView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Run the Python server in a background thread
Thread {
Looper.prepare()
Log.i(tag, "Starting server")
Log.i(tag, "IS STARTED")
Log.i(tag, Python.isStarted().toString())
if (! Python.isStarted()) {
Python.start(AndroidPlatform(this))
}
try {
val py = Python.getInstance()
py.getModule("app").callAttr("run")
} catch (e: Exception) {
Log.e(tag, "Error starting Python server: ${e.message}")
}
Looper.loop()
}.start()
// Set webview
Log.i(tag, "Starting webview")
webView = findViewById(R.id.webview)
webView.webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
// Ignore SSL certificate errors (not recommended for production)
handler?.proceed()
}
}
webView.settings.javaScriptEnabled = true
webView.loadUrl("file:///android_asset/iframe/iframe.html")
swipeRefreshLayout = findViewById(R.id.swipe_refresh_layout)
swipeRefreshLayout.setOnRefreshListener {
webView.reload() // Reload the current page
swipeRefreshLayout.isRefreshing = false // Stop the refreshing animation
}
}
fun closeLoader() {
Log.i(tag, "Close loader")
webView.evaluateJavascript("closeLoader();", null)
}
}
Python:
from hypercorn.asyncio import serve
from hypercorn.config import Config
from quart import Quart, render_template
from com.example.chatterbot import MainActivity
activity = MainActivity() # <-- **The problem comes when i add this**
app = Quart(__name__)
config = Config()
config.bind = [f"0.0.0.0:8000"]
config.debug = True
@app.route('/')
async def index():
return await render_template('index.html')
@app.after_serving
async def after_startup():
# This code will run after the server is fully loaded and ready to receive requests
print("Server ready")
activity.closeLoader()
def run():
app.run(host="0.0.0.0", port=8000, debug=True)
Turns out i simply had to remove the line android:name="com.chaquo.python.android.PyApplication"
from the manifest which caused the python interpreter to be started automatically outside the tread with the app therefore not using the Python.start()
method inside the thread. As said in the comment, the solution is here.