Search code examples
pythonandroidpython-3.xkivy

kivy buildozer UrlRequest fails on android with Python3


I've read and tried out the solution from kivy buildozer android https request fail, but possibly Python3 changes things?

I'm trying to run a UrlRequest; it works fine when I run it on my laptop with python main.py, but when I try to run it on my Android device, I keep seeing the log line

11-14 10:12:14.202  6785  6939 I python  : [INFO   ] REQ   ERROR -- <UrlRequestUrllib(Thread-1, started daemon 517489732944)> <<[Errno 7] No address associated with hostname>>

and the label widget doesn't change text as expected. If I change UrlRequest to UrlRequestRequests, and add the requisite wod to the buildozer.spec file, I instead get the longer but not particularly more informitive

11-14 10:21:32.098  7626  7719 I python  : [INFO   ] [REQ   ERROR -- <UrlRequestRequests(Thread-1, started daemon 517496282448)> <<HTTPSConnectionPool(host='www.google.com', port=443)] Max retries exceeded with url: / (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x787e579ba0>: Failed to resolve 'www.google.com' ([Errno 7] No address associated with hostname)"))>>

I've followed the buildozer installation instructions at https://buildozer.readthedocs.io/en/latest/installation.html (including installing libssl-dev first, and doing buildozer android clean just for good measure).

The minimum reproducible error is:

# main.py

import kivy

kivy.require('2.2.1') # replace with your current kivy version !

import markdownify
from kivy.app import App
from kivy.logger import Logger
from kivy.network.urlrequest import UrlRequest
from kivy.uix.label import Label


class HelloApp(App):
    def build(self):
        lbl = Label(text="Placeholder Text")
        def _setlbl(new_text):
            Logger.info("REQ   callback started. Setting label text...")
            lbl.text = new_text
            Logger.info("REQ   text set")
            return new_text
        url = "https://www.google.com/"
        Logger.info("REQ Running UrlRequest...")
        UrlRequest(
            url,
            on_success=lambda req, res: _setlbl("Got a response!"),
            on_failure=lambda req, res: Logger.info(f"REQ   FAIL -- {req} {res}"),
            on_error=lambda req, err: Logger.info(f"REQ   ERROR -- {req} <<{err}>>")
        )
        Logger.info("REQ   request started...")
        return lbl


if __name__ == '__main__':
    HelloApp().run()

# buildozer.spec (with a giant pile of comments removed)
[app]

# (str) Title of your application
title = My Application

# (str) Package name
package.name = myapp

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,ttf

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
source.include_patterns = fonts

# (str) Application versioning (method 1)
version = 0.26

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = openssl,hostpython3,python3,kivy

# (list) Supported orientations
# Valid options are: landscape, portrait, portrait-reverse or landscape-reverse
orientation = landscape, portrait

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (list) Permissions
# (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
android.permissions = android.permission.INTERNET #, (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)

# (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
# In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
android.archs = arm64-v8a, armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1

# (bool) enables Android auto backup feature (Android API >=23)
android.allow_backup = True

#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.10.0

# (bool) Whether or not to sign the code
ios.codesign.allowed = false


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

What am I doing wrong here?

EDIT:

Adding a runtime permission request doesn't on its own resolve things. If I add

from kivy.utils import platform

Logger.info(f"PLATFORM {kivy.utils.platform}")
if platform == 'android':
    Logger.info("  ON ANDROID - REQUESTING PERMISSIONS")
    from android.permissions import Permission, request_permissions
    Logger.info("    imported permission things...")
    perms = [Permission.INTERNET]
    Logger.info(f"   asking for `{perms}`...")
    res = request_permissions(perms, None)
    Logger.info(f"    permissions requested {res} ...")

to main.py between the existing imports and HelloApp declaration, I can see each individual log line hit terminal, but it still results in a request error.

The complete log dump is:

11-14 14:32:56.597 20951 21073 I python  : Initializing Python for Android
11-14 14:32:56.597 20951 21073 I python  : Setting additional env vars from p4a_env_vars.txt
11-14 14:32:56.597 20951 21073 I python  : Changing directory to the one provided by ANDROID_ARGUMENT
11-14 14:32:56.597 20951 21073 I python  : /data/user/0/org.test.myapp/files/app
11-14 14:32:56.597 20951 21073 I python  : Preparing to initialize python
11-14 14:32:56.597 20951 21073 I python  : _python_bundle dir exists
11-14 14:32:56.597 20951 21073 I python  : calculated paths to be...
11-14 14:32:56.597 20951 21073 I python  : /data/user/0/org.test.myapp/files/app/_python_bundle/stdlib.zip:/data/user/0/org.test.myapp/files/app/_python_bundle/modules
11-14 14:32:56.597 20951 21073 I python  : set wchar paths...
11-14 14:32:56.723 20951 21073 I python  : Initialized python
11-14 14:32:56.723 20951 21073 I python  : AND: Init threads
11-14 14:32:56.724 20951 21073 I python  : testing python print redirection
11-14 14:32:56.727 20951 21073 I python  : Android path ['.', '/data/user/0/org.test.myapp/files/app/_python_bundle/stdlib.zip', '/data/user/0/org.test.myapp/files/app/_python_bundle/modules', '/data/user/0/org.test.myapp/files/app/_python_bundle/site-packages']
11-14 14:32:56.727 20951 21073 I python  : os.environ is environ({'PATH': '/sbin:/system/sbin:/product/bin:/apex/com.android.runtime/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin', 'ANDROID_BOOTLOGO': '1', 'ANDROID_ROOT': '/system', 'ANDROID_ASSETS': '/system/app', 'ANDROID_DATA': '/data', 'ANDROID_STORAGE': '/storage', 'ANDROID_RUNTIME_ROOT': '/apex/com.android.runtime', 'ANDROID_TZDATA_ROOT': '/apex/com.android.tzdata', 'EXTERNAL_STORAGE': '/sdcard', 'ASEC_MOUNTPOINT': '/mnt/asec', 'BOOTCLASSPATH': '/apex/com.android.runtime/javalib/core-oj.jar:/apex/com.android.runtime/javalib/core-libart.jar:/apex/com.android.runtime/javalib/okhttp.jar:/apex/com.android.runtime/javalib/bouncycastle.jar:/apex/com.android.runtime/javalib/apache-xml.jar:/system/framework/QPerformance.jar:/system/framework/UxPerformance.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/android.test.base.jar:/system/framework/tcmiface.jar:/system/framework/telephony-ext.jar:/system/framework/WfdCommon.jar:/system/framework/qcom.fmradio.jar:/system/framework/com.nxp.nfc.nq.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar', 'DEX2OATBOOTCLASSPATH': '/apex/com.android.runtime/javalib/core-oj.jar:/apex/com.android.runtime/javalib/core-libart.jar:/apex/com.android.runtime/javalib/okhttp.jar:/apex/com.android.runtime/javalib/bouncycastle.jar:/apex/com.android.runtime/javalib/apache-xml.jar:/system/framework/QPerformance.jar:/system/framework/UxPerformance.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/android.test.base.jar:/system/framework/tcmiface.jar:/system/framework/telephony-ext.jar:/system/framework/WfdCommon.jar:/system/framework/qcom.fmradio.jar:/system/framework/com.nxp.nfc.nq.jar', 'SYSTEMSERVERCLASSPATH': '/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar', 'DOWNLOAD_CACHE': '/data/cache', 'ANDROID_SOCKET_zygote': '18', 'ANDROID_SOCKET_usap_pool_primary': '19', 'ANDROID_ENTRYPOINT': 'main.pyc', 'ANDROID_ARGUMENT': '/data/user/0/org.test.myapp/files/app', 'ANDROID_APP_PATH': '/data/user/0/org.test.myapp/files/app', 'ANDROID_PRIVATE': '/data/user/0/org.test.myapp/files', 'ANDROID_UNPACK': '/data/user/0/org.test.myapp/files/app', 'PYTHONHOME': '/data/user/0/org.test.myapp/files/app', 'PYTHONPATH': '/data/user/0/org.test.myapp/files/app:/data/user/0/org.test.myapp/files/app/lib', 'PYTHONOPTIMIZE': '2', 'P4A_BOOTSTRAP': 'SDL2', 'PYTHON_NAME': 'python', 'P4A_IS_WINDOWED': 'True', 'KIVY_ORIENTATION': 'LandscapeLeft Portrait', 'P4A_NUMERIC_VERSION': 'None', 'P4A_MINSDK': '21', 'LC_CTYPE': 'C.UTF-8'})
11-14 14:32:56.727 20951 21073 I python  : Android kivy bootstrap done. __name__ is __main__
11-14 14:32:56.727 20951 21073 I python  : AND: Ran string
11-14 14:32:56.727 20951 21073 I python  : Run user program, change dir and execute entrypoint
11-14 14:32:57.057 20951 21073 I python  : [INFO   ] [Logger      ] Record log in /data/user/0/org.test.myapp/files/app/.kivy/logs/kivy_23-11-14_1.txt
11-14 14:32:57.058 20951 21073 I python  : [INFO   ] [Kivy        ] v2.2.1
11-14 14:32:57.059 20951 21073 I python  : [INFO   ] [Kivy        ] Installed at "/data/user/0/org.test.myapp/files/app/_python_bundle/site-packages/kivy/__init__.pyc"
11-14 14:32:57.059 20951 21073 I python  : [INFO   ] [Python      ] v3.10.10 (main, Nov 14 2023, 02:57:53) [Clang 14.0.6 (https://android.googlesource.com/toolchain/llvm-project 4c603efb0
11-14 14:32:57.060 20951 21073 I python  : [INFO   ] [Python      ] Interpreter at ""
11-14 14:32:57.061 20951 21073 I python  : [INFO   ] [Logger      ] Purge log fired. Processing...
11-14 14:32:57.062 20951 21073 I python  : [INFO   ] [Logger      ] Purge finished!
11-14 14:32:59.358 20951 21073 I python  : [INFO   ] [Factory     ] 190 symbols loaded
11-14 14:32:59.878 20951 21073 I python  : [INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2 (img_pil, img_ffpyplayer ignored)
11-14 14:33:00.246 20951 21073 I python  : [INFO   ] [Text        ] Provider: sdl2
11-14 14:33:00.251 20951 21073 I python  : [INFO   ] PLATFORM android
11-14 14:33:00.252 20951 21073 I python  : [INFO   ]   ON ANDROID - REQUESTING PERMISSIONS
11-14 14:33:00.256 20951 21073 I python  : [INFO   ]     imported permission things...
11-14 14:33:00.256 20951 21073 I python  : [INFO   ]    asking for `['android.permission.INTERNET']`...
11-14 14:33:00.375 20951 21073 I python  : [INFO   ]     permissions requested None ...
11-14 14:33:00.455 20951 21073 I python  : [INFO   ] [Window      ] Provider: sdl2
11-14 14:33:00.495 20951 21073 I python  : [INFO   ] [GL          ] Using the "OpenGL ES 2" graphics system
11-14 14:33:00.504 20951 21073 I python  : [INFO   ] [GL          ] Backend used <sdl2>
11-14 14:33:00.505 20951 21073 I python  : [INFO   ] [GL          ] OpenGL version <b'OpenGL ES 3.2 [email protected] (GIT@389f9e1, I4b4012dc33, 1609770135) (Date:01/04/21)'>
11-14 14:33:00.506 20951 21073 I python  : [INFO   ] [GL          ] OpenGL vendor <b'Qualcomm'>
11-14 14:33:00.506 20951 21073 I python  : [INFO   ] [GL          ] OpenGL renderer <b'Adreno (TM) 508'>
11-14 14:33:00.507 20951 21073 I python  : [INFO   ] [GL          ] OpenGL parsed version: 3, 2
11-14 14:33:00.507 20951 21073 I python  : [INFO   ] [GL          ] Texture max size <16384>
11-14 14:33:00.507 20951 21073 I python  : [INFO   ] [GL          ] Texture max units <16>
11-14 14:33:00.632 20951 21073 I python  : [INFO   ] [Window      ] auto add sdl2 input provider
11-14 14:33:00.634 20951 21073 I python  : [INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
11-14 14:33:00.825 20951 21073 I python  : [INFO   ] REQ Running UrlRequest...
11-14 14:33:00.829 20951 21073 I python  : [INFO   ] REQ   request started...
11-14 14:33:00.831 20951 21073 I python  : [WARNING] [Base        ] Unknown <android> provider
11-14 14:33:00.832 20951 21073 I python  : [INFO   ] [Base        ] Start application main loop
11-14 14:33:00.842 20951 21073 I python  : [INFO   ] [GL          ] NPOT texture support is available
11-14 14:33:00.914 20951 21073 I python  : [INFO   ] REQ   ERROR -- <UrlRequestUrllib(Thread-1, started daemon 517499501904)> <<[Errno 7] No address associated with hostname>>

Solution

  • It looks like the actual solution here is to add permissions to both INTERNET and ACCESS_NETWORK_STATE to the android.permissions line in buildozer.spec. In other words, the fix delta from the original looks like

    @@ -93,7 +93,7 @@ fullscreen = 0
     
     # (list) Permissions
     # (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
    -android.permissions = android.permission.INTERNET
    +android.permissions = android.permission.INTERNET,android.permission.ACCESS_NETWORK_STATE
     
     # (list) features (adds uses-feature -tags to manifest)
     #android.features = android.hardware.usb.host
    

    With that change, the app starts working on Android. I can see the Label text changing as part of the callback, and the logcat output looks like

    11-14 23:41:12.404  2489  2598 I python  : [INFO   ] REQ Running UrlRequest...
    11-14 23:41:12.406  2489  2598 I python  : [INFO   ] REQ   request started...
    11-14 23:41:12.409  2489  2598 I python  : [WARNING] [Base        ] Unknown <android> provider
    11-14 23:41:12.409  2489  2598 I python  : [INFO   ] [Base        ] Start application main loop
    11-14 23:41:12.418  2489  2598 I python  : [INFO   ] [GL          ] NPOT texture support is available
    11-14 23:41:12.857  2489  2598 I python  : [INFO   ] REQ   callback started. Setting label text...
    11-14 23:41:12.857  2489  2598 I python  : [INFO   ] REQ   text set
    

    Which is exactly what I was expecting. I don't think this is explicitly documented anywhere, including in the python-for-android permission documentation. So it goes sometimes.