1618875960
In this video We will show you, how you can easily do this by using Python & the Pushbullet API. Pushbullet is a free service and helps you to connect your smartphone with your computer.
Python Code and Jupyter Notebook [Google Drive]:
👉 http://bit.ly/3brhSyy
Further Links:
Pushbullet: https://www.pushbullet.com/
Pushbullet Python Library: https://pypi.org/project/pushbullet.py/
Subscribe: https://www.youtube.com/channel/UCZjRcM1ukeciMZ7_fvzsezQ
#python #excel
1642405260
If you’re a Python developer thinking about getting started with mobile development, then the Kivy framework is your best bet. With Kivy, you can develop platform-independent applications that compile for iOS, Android, Windows, macOS, and Linux. In this article, we’ll cover Android specifically because it is the most used.
We’ll build a simple random number generator app that you can install on your phone and test when you are done. To follow along with this article, you should be familiar with Python. Let’s get started!
First, you’ll need a new directory for your app. Make sure you have Python installed on your machine and open a new Python file. You’ll need to install the Kivy module from your terminal using either of the commands below. To avoid any package conflicts, be sure you’re installing Kivy in a virtual environment:
pip install kivy
//
pip3 install kivy
Once you have installed Kivy, you should see a success message from your terminal that looks like the screenshots below:
Kivy installation
Successful Kivy installation
Next, navigate into your project folder. In the main.py
file, we’ll need to import the Kivy module and specify which version we want. You can use Kivy v2.0.0, but if you have a smartphone that is older than Android 8.0, I recommend using Kivy v1.9.0. You can mess around with the different versions during the build to see the differences in features and performance.
Add the version number right after the import kivy
line as follows:
kivy.require('1.9.0')
Now, we’ll create a class that will basically define our app; I’ll name mine RandomNumber
. This class will inherit the app
class from Kivy. Therefore, you need to import the app
by adding from kivy.app import App
:
class RandomNumber(App):
In the RandomNumber
class, you’ll need to add a function called build
, which takes a self
parameter. To actually return the UI, we’ll use the build
function. For now, I have it returned as a simple label. To do so, you’ll need to import Label
using the line from kivy.uix.label import Label
:
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
Now, our app skeleton is complete! Before moving forward, you should create an instance of the RandomNumber
class and run it in your terminal or IDE to see the interface:
import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App): def build(self): return Label(text="Random Number Generator") randomApp = RandomNumber() randomApp.run()
When you run the class instance with the text Random Number Generator
, you should see a simple interface or window that looks like the screenshot below:
Simple interface after running the code
You won’t be able to run the text on Android until you’ve finished building the whole thing.
Next, we’ll need a way to outsource the interface. First, we’ll create a Kivy file in our directory that will house most of our design work. You’ll want to name this file the same name as your class using lowercase letters and a .kv
extension. Kivy will automatically associate the class name and the file name, but it may not work on Android if they are exactly the same.
Inside that .kv
file, you need to specify the layout for your app, including elements like the label, buttons, forms, etc. To keep this demonstration simple, I’ll add a label for the title Random Number
, a label that will serve as a placeholder for the random number that is generated _
, and a Generate
button that calls the generate
function.
My .kv
file looks like the code below, but you can mess around with the different values to fit your requirements:
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
In the main.py
file, you no longer need the Label
import statement because the Kivy file takes care of your UI. However, you do need to import boxlayout
, which you will use in the Kivy file.
In your main file, you need to add the import statement and edit your main.py
file to read return BoxLayout()
in the build
method:
from kivy.uix.boxlayout import BoxLayout
If you run the command above, you should see a simple interface that has the random number title, the _
place holder, and the clickable generate
button:
Random Number app rendered
Notice that you didn’t have to import anything for the Kivy file to work. Basically, when you run the app, it returns boxlayout
by looking for a file inside the Kivy file with the same name as your class. Keep in mind, this is a simple interface, and you can make your app as robust as you want. Be sure to check out the Kv language documentation.
Now that our app is almost done, we’ll need a simple function to generate random numbers when a user clicks the generate
button, then render that random number into the app interface. To do so, we’ll need to change a few things in our files.
First, we’ll import the module that we’ll use to generate a random number with import random
. Then, we’ll create a function or method that calls the generated number. For this demonstration, I’ll use a range between 0
and 2000
. Generating the random number is simple with the random.randint(0, 2000)
command. We’ll add this into our code in a moment.
Next, we’ll create another class that will be our own version of the box layout
. Our class will have to inherit the box layout
class, which houses the method to generate random numbers and render them on the interface:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
Within that class, we’ll create the generate
method, which will not only generate random numbers but also manipulate the label that controls what is displayed as the random number in the Kivy file.
To accommodate this method, we’ll first need to make changes to the .kv
file . Since the MyRoot
class has inherited the box layout
, you can make MyRoot
the top level element in your .kv
file:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
Notice that you are still keeping all your UI specifications indented in the Box Layout
. After this, you need to add an ID to the label that will hold the generated numbers, making it easy to manipulate when the generate
function is called. You need to specify the relationship between the ID in this file and another in the main code at the top, just before the BoxLayout
line:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
The random_label: random_label
line basically means that the label with the ID random_label
will be mapped to random_label
in the main.py
file, meaning that any action that manipulates random_label
will be mapped on the label with the specified name.
We can now create the method to generate the random number in the main file:
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
The MyRoot
class should look like the code below:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
Congratulations! You’re now done with the main file of the app. The only thing left to do is make sure that you call this function when the generate
button is clicked. You need only add the line on_press: root.generate_number()
to the button selection part of your .kv
file:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
Now, you can run the app.
Before compiling our app on Android, I have some bad news for Windows users. You’ll need Linux or macOS to compile your Android application. However, you don’t need to have a separate Linux distribution, instead, you can use a virtual machine.
To compile and generate a full Android .apk
application, we’ll use a tool called Buildozer. Let’s install Buildozer through our terminal using one of the commands below:
pip3 install buildozer
//
pip install buildozer
Now, we’ll install some of Buildozer’s required dependencies. I am on Linux Ergo, so I’ll use Linux-specific commands. You should execute these commands one by one:
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
After executing the specific commands, run buildozer init
. You should see an output similar to the screenshot below:
Buildozer successful initialization
The command above creates a Buildozer .spec
file, which you can use to make specifications to your app, including the name of the app, the icon, etc. The .spec
file should look like the code block below:
[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
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# 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
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = 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
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# 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.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[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
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
If you want to specify things like the icon, requirements, loading screen, etc., you should edit this file. After making all the desired edits to your application, run buildozer -v android debug
from your app directory to build and compile your application. This may take a while, especially if you have a slow machine.
After the process is done, your terminal should have some logs, one confirming that the build was successful:
Android successful build
You should also have an APK version of your app in your bin directory. This is the application executable that you will install and run on your phone:
Android .apk in the bin directory
Congratulations! If you have followed this tutorial step by step, you should have a simple random number generator app on your phone. Play around with it and tweak some values, then rebuild. Running the rebuild will not take as much time as the first build.
As you can see, building a mobile application with Python is fairly straightforward, as long as you are familiar with the framework or module you are working with. Regardless, the logic is executed the same way.
Get familiar with the Kivy module and it’s widgets. You can never know everything all at once. You only need to find a project and get your feet wet as early as possible. Happy coding.
Link: https://blog.logrocket.com/build-android-application-kivy-python-framework/
1641693600
Si es un desarrollador de Python que está pensando en comenzar con el desarrollo móvil, entonces el marco Kivy es su mejor opción. Con Kivy, puede desarrollar aplicaciones independientes de la plataforma que compilan para iOS, Android, Windows, macOS y Linux. En este artículo, cubriremos Android específicamente porque es el más utilizado.
Construiremos una aplicación generadora de números aleatorios simple que puede instalar en su teléfono y probar cuando haya terminado. Para continuar con este artículo, debe estar familiarizado con Python. ¡Empecemos!
Primero, necesitará un nuevo directorio para su aplicación. Asegúrese de tener Python instalado en su máquina y abra un nuevo archivo de Python. Deberá instalar el módulo Kivy desde su terminal usando cualquiera de los comandos a continuación. Para evitar conflictos de paquetes, asegúrese de instalar Kivy en un entorno virtual:
pip install kivy
//
pip3 install kivy
Una vez que haya instalado Kivy, debería ver un mensaje de éxito de su terminal que se parece a las capturas de pantalla a continuación:
Instalación decepcionada
Instalación exitosa de Kivy
A continuación, navegue a la carpeta de su proyecto. En el main.py
archivo, necesitaremos importar el módulo Kivy y especificar qué versión queremos. Puede usar Kivy v2.0.0, pero si tiene un teléfono inteligente anterior a Android 8.0, le recomiendo usar Kivy v1.9.0. Puede jugar con las diferentes versiones durante la compilación para ver las diferencias en las características y el rendimiento.
Agregue el número de versión justo después de la import kivy
línea de la siguiente manera:
kivy.require('1.9.0')
Ahora, crearemos una clase que básicamente definirá nuestra aplicación; Voy a nombrar el mío RandomNumber
. Esta clase heredará la app
clase de Kivy. Por lo tanto, debe importar app
agregando from kivy.app import App
:
class RandomNumber(App):
En la RandomNumber
clase, deberá agregar una función llamada build
, que toma un self
parámetro. Para devolver la interfaz de usuario, usaremos la build
función. Por ahora, lo tengo devuelto como una simple etiqueta. Para hacerlo, deberá importar Label
usando la línea from kivy.uix.label import Label
:
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
¡Ahora, el esqueleto de nuestra aplicación está completo! Antes de continuar, debe crear una instancia de la RandomNumber
clase y ejecutarla en su terminal o IDE para ver la interfaz:
importar kivy de kivy.app importar aplicación de kivy.uix.label clase de etiqueta de importación RandomNumber(App): def build(self): return Label(text="Generador de números aleatorios") randomApp = RandomNumber() randomApp.run()
Cuando ejecuta la instancia de clase con el texto Random Number Generator
, debería ver una interfaz o ventana simple que se parece a la siguiente captura de pantalla:
Interfaz simple después de ejecutar el código.
No podrá ejecutar el texto en Android hasta que haya terminado de construir todo.
A continuación, necesitaremos una forma de subcontratar la interfaz. Primero, crearemos un archivo Kivy en nuestro directorio que albergará la mayor parte de nuestro trabajo de diseño. Querrá nombrar este archivo con el mismo nombre que su clase usando letras minúsculas y una .kv
extensión. Kivy asociará automáticamente el nombre de la clase y el nombre del archivo, pero es posible que no funcione en Android si son exactamente iguales.
Dentro de ese .kv
archivo, debe especificar el diseño de su aplicación, incluidos elementos como la etiqueta, los botones, los formularios, etc. Para simplificar esta demostración, agregaré una etiqueta para el título Random Number
, una etiqueta que servirá como marcador de posición. para el número aleatorio que se genera _
, y un Generate
botón que llama a la generate
función.
Mi .kv
archivo se parece al siguiente código, pero puede jugar con los diferentes valores para que se ajusten a sus requisitos:
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
En el main.py
archivo, ya no necesita la Label
declaración de importación porque el archivo Kivy se encarga de su interfaz de usuario. Sin embargo, necesita importar boxlayout
, que utilizará en el archivo Kivy.
En su archivo principal, debe agregar la declaración de importación y editar su main.py
archivo para leer return BoxLayout()
el build
método:
from kivy.uix.boxlayout import BoxLayout
Si ejecuta el comando anterior, debería ver una interfaz simple que tiene el título del número aleatorio, el _
marcador de posición y el generate
botón en el que se puede hacer clic:
Aplicación de números aleatorios renderizada
Tenga en cuenta que no tuvo que importar nada para que funcione el archivo Kivy. Básicamente, cuando ejecuta la aplicación, regresa boxlayout
buscando un archivo dentro del archivo Kivy con el mismo nombre que su clase. Tenga en cuenta que esta es una interfaz simple y puede hacer que su aplicación sea tan robusta como desee. Asegúrese de consultar la documentación del idioma Kv .
Ahora que nuestra aplicación está casi terminada, necesitaremos una función simple para generar números aleatorios cuando un usuario haga clic en el generate
botón y luego mostrar ese número aleatorio en la interfaz de la aplicación. Para hacerlo, necesitaremos cambiar algunas cosas en nuestros archivos.
Primero, importaremos el módulo que usaremos para generar un número aleatorio con import random
. Luego, crearemos una función o método que llame al número generado. Para esta demostración, usaré un rango entre 0
y 2000
. Generar el número aleatorio es simple con el random.randint(0, 2000)
comando. Agregaremos esto a nuestro código en un momento.
A continuación, crearemos otra clase que será nuestra propia versión del box layout
. Nuestra clase tendrá que heredar la box layout
clase, que alberga el método para generar números aleatorios y representarlos en la interfaz:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
Dentro de esa clase, crearemos el generate
método, que no solo generará números aleatorios, sino que también manipulará la etiqueta que controla lo que se muestra como número aleatorio en el archivo Kivy.
Para acomodar este método, primero necesitaremos hacer cambios en el .kv
archivo. Dado que la MyRoot
clase ha heredado el box layout
, puede crear MyRoot
el elemento de nivel superior en su .kv
archivo:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
Tenga en cuenta que todavía mantiene todas las especificaciones de la interfaz de usuario con sangría en el archivo Box Layout
. Después de esto, debe agregar una identificación a la etiqueta que contendrá los números generados, lo que facilita la manipulación cuando generate
se llama a la función. Debe especificar la relación entre la ID en este archivo y otra en el código principal en la parte superior, justo antes de la BoxLayout
línea:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
La random_label: random_label
línea básicamente significa que la etiqueta con el ID random_label
se asignará a random_label
en el main.py
archivo, lo que significa que cualquier acción que manipula random_label
serán mapeados en la etiqueta con el nombre especificado.
Ahora podemos crear el método para generar el número aleatorio en el archivo principal:
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
La MyRoot
clase debería parecerse al siguiente código:
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
¡Felicidades! Ya ha terminado con el archivo principal de la aplicación. Lo único que queda por hacer es asegurarse de llamar a esta función cuando se haga generate
clic en el botón. Solo necesita agregar la línea on_press: root.generate_number()
a la parte de selección de botones de su .kv
archivo:
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
Ahora, puede ejecutar la aplicación.
Antes de compilar nuestra aplicación en Android, tengo malas noticias para los usuarios de Windows. Necesitará Linux o macOS para compilar su aplicación de Android. Sin embargo, no necesita tener una distribución de Linux separada, en su lugar, puede usar una máquina virtual.
Para compilar y generar una .apk
aplicación Android completa , usaremos una herramienta llamada Buildozer . Instalemos Buildozer a través de nuestra terminal usando uno de los siguientes comandos:
pip3 install buildozer
//
pip install buildozer
Ahora, instalaremos algunas de las dependencias requeridas de Buildozer. Estoy en Linux Ergo, así que usaré comandos específicos de Linux. Debe ejecutar estos comandos uno por uno:
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
Después de ejecutar los comandos específicos, ejecute buildozer init
. Debería ver un resultado similar a la captura de pantalla a continuación:
Inicialización exitosa de Buildozer
El comando anterior crea un .spec
archivo Buildozer , que puede usar para hacer especificaciones para su aplicación, incluido el nombre de la aplicación, el ícono, etc. El .spec
archivo debe verse como el bloque de código a continuación:
[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
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# 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
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = 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
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# 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.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[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
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
Si desea especificar cosas como el ícono, los requisitos, la pantalla de carga, etc., debe editar este archivo. Después de realizar todas las ediciones deseadas en su aplicación, ejecute buildozer -v android debug
desde el directorio de su aplicación para construir y compilar su aplicación. Esto puede llevar un tiempo, especialmente si tiene una máquina lenta.
Una vez finalizado el proceso, su terminal debería tener algunos registros, uno que confirme que la compilación fue exitosa:
Construcción exitosa de Android
También debe tener una versión APK de su aplicación en su directorio bin. Este es el ejecutable de la aplicación que instalará y ejecutará en su teléfono:
Android .apk en el directorio bin
¡Felicidades! Si ha seguido este tutorial paso a paso, debería tener una aplicación simple de generador de números aleatorios en su teléfono. Juega con él y ajusta algunos valores, luego reconstruye. Ejecutar la reconstrucción no llevará tanto tiempo como la primera compilación.
Como puede ver, crear una aplicación móvil con Python es bastante sencillo , siempre que esté familiarizado con el marco o módulo con el que está trabajando. Independientemente, la lógica se ejecuta de la misma manera.
Familiarícese con el módulo Kivy y sus widgets. Nunca se puede saber todo a la vez. Solo necesita encontrar un proyecto y mojarse los pies lo antes posible. Codificación feliz.
Enlace: https://blog.logrocket.com/build-android-application-kivy-python-framework/
1641693600
あなたがモバイル開発を始めることを考えているPython開発者なら、Kivyフレームワークが最善の策です。Kivyを使用すると、iOS、Android、Windows、macOS、およびLinux用にコンパイルされるプラットフォームに依存しないアプリケーションを開発できます。この記事では、Androidが最も使用されているため、特にAndroidについて説明します。
簡単な乱数ジェネレーターアプリを作成します。このアプリを携帯電話にインストールして、完了したらテストできます。この記事を続けるには、Pythonに精通している必要があります。始めましょう!
まず、アプリ用の新しいディレクトリが必要になります。マシンにPythonがインストールされていることを確認し、新しいPythonファイルを開きます。以下のコマンドのいずれかを使用して、ターミナルからKivyモジュールをインストールする必要があります。パッケージの競合を避けるために、Kivyを仮想環境にインストールしていることを確認してください。
pip install kivy
//
pip3 install kivy
Kivyをインストールすると、以下のスクリーンショットのような成功メッセージがターミナルから表示されます。
がっかりしたインストール
Kivyのインストールに成功
次に、プロジェクトフォルダに移動します。このmain.py
ファイルで、Kivyモジュールをインポートし、必要なバージョンを指定する必要があります。Kivy v2.0.0を使用できますが、Android 8.0より古いスマートフォンを使用している場合は、Kivyv1.9.0を使用することをお勧めします。ビルド中にさまざまなバージョンをいじって、機能とパフォーマンスの違いを確認できます。
import kivy
次のように、行の直後にバージョン番号を追加します。
kivy.require('1.9.0')
次に、基本的にアプリを定義するクラスを作成します。私の名前を付けますRandomNumber
。このクラスはapp
Kivyからクラスを継承します。したがって、次app
を追加してインポートする必要がありますfrom kivy.app import App
。
class RandomNumber(App):
ではRandomNumber
クラスは、呼び出された関数を追加する必要がありますbuild
とり、self
パラメータを。実際にUIを返すには、このbuild
関数を使用します。今のところ、単純なラベルとして返送しています。そのためには、次Label
の行を使用してインポートする必要がありますfrom kivy.uix.label import Label
。
import kivy
from kivy.app import App
from kivy.uix.label import Label
class RandomNumber(App):
def build(self):
return Label(text="Random Number Generator")
これで、アプリのスケルトンが完成しました。先に進む前に、RandomNumber
クラスのインスタンスを作成し、ターミナルまたはIDEで実行して、インターフェイスを確認する必要があります。
import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App):def build(self):return Label(text = "Random Number Generator")randomApp = RandomNumber()randomApp.run()
テキストを使用してクラスインスタンスを実行すると、Random Number Generator
次のスクリーンショットのような単純なインターフェイスまたはウィンドウが表示されます。
コードを実行した後のシンプルなインターフェイス
すべての構築が完了するまで、Androidでテキストを実行することはできません。
次に、インターフェースをアウトソーシングする方法が必要になります。まず、ディレクトリにKivyファイルを作成します。このファイルには、ほとんどの設計作業が含まれています。このファイルには、小文字と.kv
拡張子を使用して、クラスと同じ名前を付けることができます。Kivyはクラス名とファイル名を自動的に関連付けますが、それらがまったく同じである場合、Androidでは機能しない可能性があります。
その.kv
ファイル内で、ラベル、ボタン、フォームなどの要素を含むアプリのレイアウトを指定する必要があります。このデモを簡単にするために、タイトルRandom Number
のラベル、プレースホルダーとして機能するラベルを追加します。生成される乱数_
、および関数Generate
を呼び出すボタンgenerate
。
私の.kv
ファイルは以下のコードのように見えますが、要件に合わせてさまざまな値をいじることができます。
<boxLayout>:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
このmain.py
ファイルではLabel
、KivyファイルがUIを処理するため、importステートメントは不要になりました。ただし、boxlayout
Kivyファイルで使用するをインポートする必要があります。
メインファイルで、importステートメントを追加し、main.py
ファイルを編集return BoxLayout()
してbuild
メソッドで読み取る必要があります。
from kivy.uix.boxlayout import BoxLayout
上記のコマンドを実行すると、乱数のタイトル、_
プレースホルダー、およびクリック可能なgenerate
ボタンを備えたシンプルなインターフェイスが表示されます。
レンダリングされた乱数アプリ
Kivyファイルを機能させるために何もインポートする必要がなかったことに注意してください。基本的に、アプリを実行するboxlayout
と、クラスと同じ名前のKivyファイル内のファイルを検索して戻ります。これはシンプルなインターフェースであり、アプリを必要に応じて堅牢にすることができます。Kv言語のドキュメントを必ず確認してください。
アプリがほぼ完成したので、ユーザーがgenerate
ボタンをクリックしたときに乱数を生成し、その乱数をアプリのインターフェイスにレンダリングする簡単な関数が必要になります。そのためには、ファイル内のいくつかの変更を行う必要があります。
まず、で乱数を生成するために使用するモジュールをインポートしますimport random
。次に、生成された番号を呼び出す関数またはメソッドを作成します。このデモでは、私は間の範囲を使用します0
と2000
。このrandom.randint(0, 2000)
コマンドを使用すると、乱数を簡単に生成できます。これをすぐにコードに追加します。
次に、独自のバージョンとなる別のクラスを作成しますbox layout
。このbox layout
クラスは、乱数を生成してインターフェイス上でレンダリングするメソッドを含むクラスを継承する必要があります。
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
そのクラス内で、generate
乱数を生成するだけでなく、Kivyファイルに乱数として表示されるものを制御するラベルを操作するメソッドを作成します。
この方法に対応するには、最初に.kv
ファイルに変更を加える必要があります。以来MyRoot
クラスが継承しているbox layout
、あなたが作ることができるMyRoot
あなたのトップレベルの要素.kv
ファイルを:
<MyRoot>:
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
でインデントされたすべてのUI仕様を保持していることに注意してくださいBox Layout
。この後、生成された番号を保持するIDをラベルに追加して、generate
関数が呼び出されたときに簡単に操作できるようにする必要があります。このファイルのIDと、上部のメインコードの別のIDとの関係を、次のBoxLayout
行の直前に指定する必要があります。
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
このrandom_label: random_label
行は基本的に、IDrandom_label
を持つラベルがファイルrandom_label
内にマップされることをmain.py
意味します。つまり、操作random_label
するアクションはすべて、指定された名前のラベルにマップされます。
これで、メインファイルに乱数を生成するメソッドを作成できます。
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.
MyRoot
このクラスは、以下のコードのようになります。
class MyRoot(BoxLayout):
def __init__(self):
super(MyRoot, self).__init__()
def generate_number(self):
self.random_label.text = str(random.randint(0, 2000))
おめでとう!これで、アプリのメインファイルが完成しました。あとは、generate
ボタンがクリックされたときに必ずこの関数を呼び出すようにしてください。ファイルのon_press: root.generate_number()
ボタン選択部分に行を追加するだけで済み.kv
ます。
<MyRoot>:
random_label: random_label
BoxLayout:
orientation: "vertical"
Label:
text: "Random Number"
font_size: 30
color: 0, 0.62, 0.96
Label:
id: random_label
text: "_"
font_size: 30
Button:
text: "Generate"
font_size: 15
on_press: root.generate_number()
これで、アプリを実行できます。
Androidでアプリをコンパイルする前に、Windowsユーザーにとって悪いニュースがあります。Androidアプリケーションをコンパイルするには、LinuxまたはmacOSが必要です。ただし、個別のLinuxディストリビューションを用意する必要はなく、代わりに仮想マシンを使用できます。
完全なAndroid.apk
アプリケーションをコンパイルして生成するには、Buildozerというツールを使用します。以下のコマンドのいずれかを使用して、ターミナルからBuildozerをインストールしましょう。
pip3 install buildozer
//
pip install buildozer
次に、Buildozerに必要な依存関係のいくつかをインストールします。私はLinuxErgoを使用しているので、Linux固有のコマンドを使用します。これらのコマンドを1つずつ実行する必要があります。
sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
pip3 install --upgrade Cython==0.29.19 virtualenv
# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/
特定のコマンドを実行した後、を実行しbuildozer init
ます。以下のスクリーンショットのような出力が表示されます。
Buildozerの初期化が成功しました
上記のコマンドはBuildozer.spec
ファイルを作成します。このファイルを使用して、アプリの名前やアイコンなどをアプリに指定.spec
できます。ファイルは次のコードブロックのようになります。
[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
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# 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
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
#android.permissions = INTERNET
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 19b
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = 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
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# 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.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[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
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug
アイコン、要件、ロード画面などを指定する場合は、このファイルを編集する必要があります。アプリケーションに必要なすべての編集を行った後buildozer -v android debug
、アプリディレクトリから実行して、アプリケーションをビルドおよびコンパイルします。特に低速のマシンを使用している場合は、これに時間がかかることがあります。
プロセスが完了すると、端末にいくつかのログが表示され、ビルドが成功したことを確認できます。
Androidの成功したビルド
また、binディレクトリにアプリのAPKバージョンが必要です。これは、携帯電話にインストールして実行するアプリケーションの実行可能ファイルです。
binディレクトリのAndroid.apk
おめでとう!このチュートリアルをステップバイステップで実行した場合は、電話に単純な乱数ジェネレーターアプリがインストールされているはずです。それをいじって、いくつかの値を微調整してから、再構築してください。再構築の実行は、最初のビルドほど時間はかかりません。
ご覧のとおり、Pythonを使用したモバイルアプリケーションの構築は、使用しているフレームワークまたはモジュールに精通している限り、かなり簡単です。とにかく、ロジックは同じ方法で実行されます。
Kivyモジュールとそのウィジェットに慣れてください。すべてを一度に知ることはできません。プロジェクトを見つけて、できるだけ早く足を濡らすだけです。ハッピーコーディング。
リンク:https://blog.logrocket.com/build-android-application-kivy-python-framework/
1667425440
Perl script converts PDF files to Gerber format
Pdf2Gerb generates Gerber 274X photoplotting and Excellon drill files from PDFs of a PCB. Up to three PDFs are used: the top copper layer, the bottom copper layer (for 2-sided PCBs), and an optional silk screen layer. The PDFs can be created directly from any PDF drawing software, or a PDF print driver can be used to capture the Print output if the drawing software does not directly support output to PDF.
The general workflow is as follows:
Please note that Pdf2Gerb does NOT perform DRC (Design Rule Checks), as these will vary according to individual PCB manufacturer conventions and capabilities. Also note that Pdf2Gerb is not perfect, so the output files must always be checked before submitting them. As of version 1.6, Pdf2Gerb supports most PCB elements, such as round and square pads, round holes, traces, SMD pads, ground planes, no-fill areas, and panelization. However, because it interprets the graphical output of a Print function, there are limitations in what it can recognize (or there may be bugs).
See docs/Pdf2Gerb.pdf for install/setup, config, usage, and other info.
#Pdf2Gerb config settings:
#Put this file in same folder/directory as pdf2gerb.pl itself (global settings),
#or copy to another folder/directory with PDFs if you want PCB-specific settings.
#There is only one user of this file, so we don't need a custom package or namespace.
#NOTE: all constants defined in here will be added to main namespace.
#package pdf2gerb_cfg;
use strict; #trap undef vars (easier debug)
use warnings; #other useful info (easier debug)
##############################################################################################
#configurable settings:
#change values here instead of in main pfg2gerb.pl file
use constant WANT_COLORS => ($^O !~ m/Win/); #ANSI colors no worky on Windows? this must be set < first DebugPrint() call
#just a little warning; set realistic expectations:
#DebugPrint("${\(CYAN)}Pdf2Gerb.pl ${\(VERSION)}, $^O O/S\n${\(YELLOW)}${\(BOLD)}${\(ITALIC)}This is EXPERIMENTAL software. \nGerber files MAY CONTAIN ERRORS. Please CHECK them before fabrication!${\(RESET)}", 0); #if WANT_DEBUG
use constant METRIC => FALSE; #set to TRUE for metric units (only affect final numbers in output files, not internal arithmetic)
use constant APERTURE_LIMIT => 0; #34; #max #apertures to use; generate warnings if too many apertures are used (0 to not check)
use constant DRILL_FMT => '2.4'; #'2.3'; #'2.4' is the default for PCB fab; change to '2.3' for CNC
use constant WANT_DEBUG => 0; #10; #level of debug wanted; higher == more, lower == less, 0 == none
use constant GERBER_DEBUG => 0; #level of debug to include in Gerber file; DON'T USE FOR FABRICATION
use constant WANT_STREAMS => FALSE; #TRUE; #save decompressed streams to files (for debug)
use constant WANT_ALLINPUT => FALSE; #TRUE; #save entire input stream (for debug ONLY)
#DebugPrint(sprintf("${\(CYAN)}DEBUG: stdout %d, gerber %d, want streams? %d, all input? %d, O/S: $^O, Perl: $]${\(RESET)}\n", WANT_DEBUG, GERBER_DEBUG, WANT_STREAMS, WANT_ALLINPUT), 1);
#DebugPrint(sprintf("max int = %d, min int = %d\n", MAXINT, MININT), 1);
#define standard trace and pad sizes to reduce scaling or PDF rendering errors:
#This avoids weird aperture settings and replaces them with more standardized values.
#(I'm not sure how photoplotters handle strange sizes).
#Fewer choices here gives more accurate mapping in the final Gerber files.
#units are in inches
use constant TOOL_SIZES => #add more as desired
(
#round or square pads (> 0) and drills (< 0):
.010, -.001, #tiny pads for SMD; dummy drill size (too small for practical use, but needed so StandardTool will use this entry)
.031, -.014, #used for vias
.041, -.020, #smallest non-filled plated hole
.051, -.025,
.056, -.029, #useful for IC pins
.070, -.033,
.075, -.040, #heavier leads
# .090, -.043, #NOTE: 600 dpi is not high enough resolution to reliably distinguish between .043" and .046", so choose 1 of the 2 here
.100, -.046,
.115, -.052,
.130, -.061,
.140, -.067,
.150, -.079,
.175, -.088,
.190, -.093,
.200, -.100,
.220, -.110,
.160, -.125, #useful for mounting holes
#some additional pad sizes without holes (repeat a previous hole size if you just want the pad size):
.090, -.040, #want a .090 pad option, but use dummy hole size
.065, -.040, #.065 x .065 rect pad
.035, -.040, #.035 x .065 rect pad
#traces:
.001, #too thin for real traces; use only for board outlines
.006, #minimum real trace width; mainly used for text
.008, #mainly used for mid-sized text, not traces
.010, #minimum recommended trace width for low-current signals
.012,
.015, #moderate low-voltage current
.020, #heavier trace for power, ground (even if a lighter one is adequate)
.025,
.030, #heavy-current traces; be careful with these ones!
.040,
.050,
.060,
.080,
.100,
.120,
);
#Areas larger than the values below will be filled with parallel lines:
#This cuts down on the number of aperture sizes used.
#Set to 0 to always use an aperture or drill, regardless of size.
use constant { MAX_APERTURE => max((TOOL_SIZES)) + .004, MAX_DRILL => -min((TOOL_SIZES)) + .004 }; #max aperture and drill sizes (plus a little tolerance)
#DebugPrint(sprintf("using %d standard tool sizes: %s, max aper %.3f, max drill %.3f\n", scalar((TOOL_SIZES)), join(", ", (TOOL_SIZES)), MAX_APERTURE, MAX_DRILL), 1);
#NOTE: Compare the PDF to the original CAD file to check the accuracy of the PDF rendering and parsing!
#for example, the CAD software I used generated the following circles for holes:
#CAD hole size: parsed PDF diameter: error:
# .014 .016 +.002
# .020 .02267 +.00267
# .025 .026 +.001
# .029 .03167 +.00267
# .033 .036 +.003
# .040 .04267 +.00267
#This was usually ~ .002" - .003" too big compared to the hole as displayed in the CAD software.
#To compensate for PDF rendering errors (either during CAD Print function or PDF parsing logic), adjust the values below as needed.
#units are pixels; for example, a value of 2.4 at 600 dpi = .0004 inch, 2 at 600 dpi = .0033"
use constant
{
HOLE_ADJUST => -0.004 * 600, #-2.6, #holes seemed to be slightly oversized (by .002" - .004"), so shrink them a little
RNDPAD_ADJUST => -0.003 * 600, #-2, #-2.4, #round pads seemed to be slightly oversized, so shrink them a little
SQRPAD_ADJUST => +0.001 * 600, #+.5, #square pads are sometimes too small by .00067, so bump them up a little
RECTPAD_ADJUST => 0, #(pixels) rectangular pads seem to be okay? (not tested much)
TRACE_ADJUST => 0, #(pixels) traces seemed to be okay?
REDUCE_TOLERANCE => .001, #(inches) allow this much variation when reducing circles and rects
};
#Also, my CAD's Print function or the PDF print driver I used was a little off for circles, so define some additional adjustment values here:
#Values are added to X/Y coordinates; units are pixels; for example, a value of 1 at 600 dpi would be ~= .002 inch
use constant
{
CIRCLE_ADJUST_MINX => 0,
CIRCLE_ADJUST_MINY => -0.001 * 600, #-1, #circles were a little too high, so nudge them a little lower
CIRCLE_ADJUST_MAXX => +0.001 * 600, #+1, #circles were a little too far to the left, so nudge them a little to the right
CIRCLE_ADJUST_MAXY => 0,
SUBST_CIRCLE_CLIPRECT => FALSE, #generate circle and substitute for clip rects (to compensate for the way some CAD software draws circles)
WANT_CLIPRECT => TRUE, #FALSE, #AI doesn't need clip rect at all? should be on normally?
RECT_COMPLETION => FALSE, #TRUE, #fill in 4th side of rect when 3 sides found
};
#allow .012 clearance around pads for solder mask:
#This value effectively adjusts pad sizes in the TOOL_SIZES list above (only for solder mask layers).
use constant SOLDER_MARGIN => +.012; #units are inches
#line join/cap styles:
use constant
{
CAP_NONE => 0, #butt (none); line is exact length
CAP_ROUND => 1, #round cap/join; line overhangs by a semi-circle at either end
CAP_SQUARE => 2, #square cap/join; line overhangs by a half square on either end
CAP_OVERRIDE => FALSE, #cap style overrides drawing logic
};
#number of elements in each shape type:
use constant
{
RECT_SHAPELEN => 6, #x0, y0, x1, y1, count, "rect" (start, end corners)
LINE_SHAPELEN => 6, #x0, y0, x1, y1, count, "line" (line seg)
CURVE_SHAPELEN => 10, #xstart, ystart, x0, y0, x1, y1, xend, yend, count, "curve" (bezier 2 points)
CIRCLE_SHAPELEN => 5, #x, y, 5, count, "circle" (center + radius)
};
#const my %SHAPELEN =
#Readonly my %SHAPELEN =>
our %SHAPELEN =
(
rect => RECT_SHAPELEN,
line => LINE_SHAPELEN,
curve => CURVE_SHAPELEN,
circle => CIRCLE_SHAPELEN,
);
#panelization:
#This will repeat the entire body the number of times indicated along the X or Y axes (files grow accordingly).
#Display elements that overhang PCB boundary can be squashed or left as-is (typically text or other silk screen markings).
#Set "overhangs" TRUE to allow overhangs, FALSE to truncate them.
#xpad and ypad allow margins to be added around outer edge of panelized PCB.
use constant PANELIZE => {'x' => 1, 'y' => 1, 'xpad' => 0, 'ypad' => 0, 'overhangs' => TRUE}; #number of times to repeat in X and Y directions
# Set this to 1 if you need TurboCAD support.
#$turboCAD = FALSE; #is this still needed as an option?
#CIRCAD pad generation uses an appropriate aperture, then moves it (stroke) "a little" - we use this to find pads and distinguish them from PCB holes.
use constant PAD_STROKE => 0.3; #0.0005 * 600; #units are pixels
#convert very short traces to pads or holes:
use constant TRACE_MINLEN => .001; #units are inches
#use constant ALWAYS_XY => TRUE; #FALSE; #force XY even if X or Y doesn't change; NOTE: needs to be TRUE for all pads to show in FlatCAM and ViewPlot
use constant REMOVE_POLARITY => FALSE; #TRUE; #set to remove subtractive (negative) polarity; NOTE: must be FALSE for ground planes
#PDF uses "points", each point = 1/72 inch
#combined with a PDF scale factor of .12, this gives 600 dpi resolution (1/72 * .12 = 600 dpi)
use constant INCHES_PER_POINT => 1/72; #0.0138888889; #multiply point-size by this to get inches
# The precision used when computing a bezier curve. Higher numbers are more precise but slower (and generate larger files).
#$bezierPrecision = 100;
use constant BEZIER_PRECISION => 36; #100; #use const; reduced for faster rendering (mainly used for silk screen and thermal pads)
# Ground planes and silk screen or larger copper rectangles or circles are filled line-by-line using this resolution.
use constant FILL_WIDTH => .01; #fill at most 0.01 inch at a time
# The max number of characters to read into memory
use constant MAX_BYTES => 10 * M; #bumped up to 10 MB, use const
use constant DUP_DRILL1 => TRUE; #FALSE; #kludge: ViewPlot doesn't load drill files that are too small so duplicate first tool
my $runtime = time(); #Time::HiRes::gettimeofday(); #measure my execution time
print STDERR "Loaded config settings from '${\(__FILE__)}'.\n";
1; #last value must be truthful to indicate successful load
#############################################################################################
#junk/experiment:
#use Package::Constants;
#use Exporter qw(import); #https://perldoc.perl.org/Exporter.html
#my $caller = "pdf2gerb::";
#sub cfg
#{
# my $proto = shift;
# my $class = ref($proto) || $proto;
# my $settings =
# {
# $WANT_DEBUG => 990, #10; #level of debug wanted; higher == more, lower == less, 0 == none
# };
# bless($settings, $class);
# return $settings;
#}
#use constant HELLO => "hi there2"; #"main::HELLO" => "hi there";
#use constant GOODBYE => 14; #"main::GOODBYE" => 12;
#print STDERR "read cfg file\n";
#our @EXPORT_OK = Package::Constants->list(__PACKAGE__); #https://www.perlmonks.org/?node_id=1072691; NOTE: "_OK" skips short/common names
#print STDERR scalar(@EXPORT_OK) . " consts exported:\n";
#foreach(@EXPORT_OK) { print STDERR "$_\n"; }
#my $val = main::thing("xyz");
#print STDERR "caller gave me $val\n";
#foreach my $arg (@ARGV) { print STDERR "arg $arg\n"; }
Author: swannman
Source Code: https://github.com/swannman/pdf2gerb
License: GPL-3.0 license
1615446243
Want to port your android app to IOS ? The Android to iOS portion can be easy with SISGAIN. Our android to ios porting services make it easier to port android apps to iOS in Virginia, USA. With our remote team you can port your app today. Our dedicated android to iOS Porting developers will help you to run your business smoothly without any hassle. For more information call us at +18444455767 or email us at hello@sisgain.com
#android to ios porting #port android app to ios #porting android to ios #android to ios porting #android app to ios app porting in usa #dedicated android to ios porting developers