← Volver al listado de tecnologías

APIs Nativas

Por: SiempreListo
kivyplyerpyjniusapisandroid

Plyer - API Cross-Platform

Plyer proporciona acceso a funciones nativas de forma unificada.

Instalación

pip install plyer

buildozer.spec

requirements = python3,kivy,plyer

Funciones de Plyer

FunciónPlataformasDescripción
accelerometerAndroid, iOSAcelerómetro
batteryAndroid, iOS, Linux, WindowsEstado batería
cameraAndroid, iOSTomar fotos
compassAndroid, iOSBrújula
gpsAndroid, iOSUbicación GPS
notificationAndroid, iOS, Linux, WindowsNotificaciones
smsAndroidEnviar SMS
vibratorAndroid, iOSVibración
ttsAndroid, iOSTexto a voz
filechooserTodasSelector archivos

GPS / Ubicación

from plyer import gps

class GPSApp(App):
    def build(self):
        gps.configure(on_location=self.on_location)
        return MiWidget()

    def iniciar_gps(self):
        gps.start(minTime=1000, minDistance=1)

    def detener_gps(self):
        gps.stop()

    def on_location(self, **kwargs):
        lat = kwargs.get('lat')
        lon = kwargs.get('lon')
        print(f'Ubicación: {lat}, {lon}')

Permisos Necesarios

# buildozer.spec
android.permissions = ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION

Notificaciones

from plyer import notification

notification.notify(
    title='Mi App',
    message='Tienes un nuevo mensaje',
    app_name='MiApp',
    app_icon='assets/icon.png',
    timeout=10
)

Cámara

from plyer import camera
from os.path import join
from kivy.app import App

class CamaraApp(App):
    def capturar(self):
        ruta = join(self.user_data_dir, 'foto.jpg')
        camera.take_picture(
            filename=ruta,
            on_complete=self.foto_lista
        )

    def foto_lista(self, filepath):
        print(f'Foto guardada: {filepath}')

Permisos

android.permissions = CAMERA,WRITE_EXTERNAL_STORAGE

Vibración

from plyer import vibrator

# Vibrar por 0.5 segundos
vibrator.vibrate(time=0.5)

# Patrón de vibración
vibrator.pattern(pattern=[0, 0.2, 0.1, 0.2])

# Verificar soporte
if vibrator.exists():
    vibrator.vibrate()

Permisos

android.permissions = VIBRATE

Batería

from plyer import battery

estado = battery.status
print(f'Nivel: {estado["percentage"]}%')
print(f'Cargando: {estado["isCharging"]}')

Selector de Archivos

from plyer import filechooser

def seleccionar_archivo():
    filechooser.open_file(
        on_selection=archivo_seleccionado,
        filters=['*.png', '*.jpg']
    )

def archivo_seleccionado(selection):
    if selection:
        print(f'Archivo: {selection[0]}')

Texto a Voz (TTS)

from plyer import tts

tts.speak('Hola, esto es texto a voz')

Pyjnius - Acceso a Java/Android

Para funciones no disponibles en Plyer, usa Pyjnius directamente.

Instalación

pip install pyjnius

buildozer.spec

requirements = python3,kivy,pyjnius

Ejemplo: Toast Android

from jnius import autoclass

def mostrar_toast(mensaje):
    PythonActivity = autoclass('org.kivy.android.PythonActivity')
    Toast = autoclass('android.widget.Toast')
    String = autoclass('java.lang.String')

    activity = PythonActivity.mActivity

    def _toast(dt):
        Toast.makeText(
            activity,
            String(mensaje),
            Toast.LENGTH_SHORT
        ).show()

    from kivy.clock import Clock
    Clock.schedule_once(_toast, 0)

Ejemplo: Compartir

from jnius import autoclass, cast

def compartir(texto, titulo='Compartir'):
    Intent = autoclass('android.content.Intent')
    String = autoclass('java.lang.String')
    PythonActivity = autoclass('org.kivy.android.PythonActivity')

    intent = Intent()
    intent.setAction(Intent.ACTION_SEND)
    intent.putExtra(Intent.EXTRA_TEXT, String(texto))
    intent.setType('text/plain')

    chooser = Intent.createChooser(intent, String(titulo))
    PythonActivity.mActivity.startActivity(chooser)

Ejemplo: Abrir URL

from jnius import autoclass

def abrir_url(url):
    Intent = autoclass('android.content.Intent')
    Uri = autoclass('android.net.Uri')
    PythonActivity = autoclass('org.kivy.android.PythonActivity')

    intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
    PythonActivity.mActivity.startActivity(intent)

Clases Android Comunes

ClaseUso
PythonActivity.mActivityActivity actual
android.content.IntentIntents
android.widget.ToastMensajes toast
android.net.UriURIs
android.os.BuildInfo del dispositivo
android.provider.SettingsConfiguración

Verificar Plataforma

from kivy.utils import platform

if platform == 'android':
    from jnius import autoclass
    # Código Android
elif platform == 'ios':
    # Código iOS
else:
    # Desktop
    pass

Ciclo de Vida Android

from kivy.app import App

class MiApp(App):
    def on_pause(self):
        # App en segundo plano
        # Guardar estado, detener GPS, etc.
        return True  # Permitir pausar

    def on_resume(self):
        # App vuelve a primer plano
        pass

    def on_stop(self):
        # App terminando
        pass

Almacenamiento

from kivy.app import App

class MiApp(App):
    def guardar_datos(self, datos):
        # user_data_dir es persistente
        ruta = f'{self.user_data_dir}/datos.json'
        with open(ruta, 'w') as f:
            json.dump(datos, f)

    def cargar_datos(self):
        ruta = f'{self.user_data_dir}/datos.json'
        try:
            with open(ruta) as f:
                return json.load(f)
        except FileNotFoundError:
            return {}

Pyobjus - iOS

Para iOS, usa Pyobjus en lugar de Pyjnius.

from pyobjus import autoclass

# Ejemplo: Vibración en iOS
AudioToolbox = autoclass('AudioToolbox')
AudioToolbox.AudioServicesPlaySystemSound(1352)

Ejemplo Completo: App con GPS

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.utils import platform

class GPSWidget(BoxLayout):
    ubicacion = StringProperty('Esperando GPS...')

class GPSApp(App):
    def build(self):
        self.widget = GPSWidget()
        return self.widget

    def on_start(self):
        if platform == 'android':
            from plyer import gps
            gps.configure(on_location=self.on_ubicacion)
            gps.start(minTime=1000, minDistance=1)

    def on_ubicacion(self, **kwargs):
        lat = kwargs.get('lat', 0)
        lon = kwargs.get('lon', 0)
        self.widget.ubicacion = f'{lat:.6f}, {lon:.6f}'

    def on_pause(self):
        return True

    def on_resume(self):
        pass

Testing de APIs Nativas

# test_apis_nativas.py
import unittest
from kivy.utils import platform

class TestPlataforma(unittest.TestCase):
    def test_platform_valida(self):
        plataformas = ['android', 'ios', 'linux', 'macosx', 'win']
        self.assertIn(platform, plataformas)

    def test_plyer_importable(self):
        from plyer import notification
        self.assertTrue(hasattr(notification, 'notify'))

    def test_vibrator_existe(self):
        from plyer import vibrator
        self.assertTrue(hasattr(vibrator, 'vibrate'))

class TestMock(unittest.TestCase):
    """Tests que funcionan sin dispositivo real"""
    def test_notification_mock(self):
        from unittest.mock import MagicMock
        from plyer import notification
        notification.notify = MagicMock()
        notification.notify(title='Test', message='Hola')
        notification.notify.assert_called_once()

if __name__ == '__main__':
    unittest.main()

Probar en Dispositivo con ADB

# Probar vibración
adb shell cmd vibrator vibrate 500

# Ver estado de batería
adb shell dumpsys battery

# Simular nivel de batería
adb shell dumpsys battery set level 20

# Probar notificaciones
adb shell cmd notification post -t "Test" "Mensaje de prueba"

# Ver ubicación actual
adb shell dumpsys location | grep "last location"

# Tomar foto con cámara
adb shell am start -a android.media.action.IMAGE_CAPTURE

Resumen