Во вчерашней статье Python в Mobile development, в которой речь шла о библиотеке KivyMD (коллекции виджетов в стиле Material Design для использования их в кроссплатформенном фреймворке Kivy), в комментариях меня попросили рассказать о процессе сборки пакета для платформы Android. Для многих этот процесс, к сожалению, был и остается чем-то из ряда магического шаманства и не подъёмным для новичков делом. Что ж, давайте разбираться, так ли на самом деле все сложно и действительно ли я маг и волшебник...
Конечно, мог бы! Итак, вы написали свой код на Python и Kivy. Что нужно для того, чтобы это можно было запустить на Android устройствах? Перейдите в репозиторий KivyMD и вы увидите, что в этой инструкции уже давно прописаны шаги, которые позволят вам собрать APK пакет:
- Загрузите XUbuntu 18.04
Установите Virtual Box на свой компьютер.
Создайте новую виртуальную машину на основе загруженного образа XUbuntu
Запустите виртуальную машину XUbuntu, откройте терминал и выполните нижеследующую команду:
wget https://github.com/HeaTTheatR/KivyMD-data/raw/master/install-kivy-buildozer-dependencies.sh
chmod +x install-kivy-buildozer-dependencies.sh
./install-kivy-buildozer-dependencies.sh
Все! Теперь у вас есть виртуальная машина для сборки APK пакетов для приложений Kivy! Что дальше? Давайте, собственно, займемся сборкой тестового приложения. Создайте в домашнем каталоге вашей виртуальной машины директорию TestKivyMD с пустым файлом main.py:
Далее откройте файл main.py и напишите код нашего тестового приложения, которое будет использовать библиотеку KivyMD:
from kivy.lang import Builder
from kivymd.app import MDApp
KV = """
Screen:
MDToolbar:
title: "My firt app"
elevation: 10
md_bg_color: app.theme_cls.primary_color
left_action_items: [["menu", lambda x: x]]
pos_hint: {"top": 1}
MDRaisedButton:
text: "Hello World"
pos_hint: {"center_x": .5, "center_y": .5}
"""
class HelloWorld(MDApp):
def build(self):
return Builder.load_string(KV)
HelloWorld().run()
Сохраните, откройте терминал в директории с файлом main.py и установите библиотеку KivyMD:
sudo pip3 install kivymd
После установки можно протестировать наш код:
python3 main.py
Результатом работы скрипта будет экран с Toolbar и одной кнопкой «Hello World»:
Дальше нам нужно создать файл спецификации buildozer.spec, который должен располагаться в той же директории, что и файл main.py:
Если вы не закрывали терминал (если терминал был закрыт, откройте его в директории TestKivyMD), введите команду:
buildozer init
Эта команда создаст дефолтный файл спецификации. Откройте его и отредактируйте:
[app]
# (str) Title of your application
title = KivyMDTest
# (str) Package name
package.name = kivymd_test
# (str) Package domain (needed for android/ios packaging)
package.domain = com.heattheatr
# (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,jpeg,ttf
# (list) Application version
version = 0.0.1
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy==1.11.1,kivymd
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 1
# (list) Permissions
android.permissions = INTERNET,WRITE_EXTERNAL_STORAGE
# (int) Target Android API, should be as high as possible.
android.api = 28
# (int) Minimum API your APK will support.
android.minapi = 21
# (str) Android NDK version to use
android.ndk = 17c
# (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 = True
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
[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 = 0
# (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
Здесь все понятно поэтому дополнительные комментарии излишни. Почитайте внимательно дефолтную спецификацию, в ней можно указать путь к иконке, пресплеш при загрузке приложения и многое другое. Я оставил лишь то, что нам сейчас нужно для сборки нашего тестового пакета. И, собственно, запускаем процесс сборки командой в терминале:
buildozer android debug
Можете смело идти на кухню и заваривать кофе, потому что в первый раз процесс загрузки и компиляции библиотек займет очень много времени. Все последующие сборки проходят за 10-20 секунд.
Кофе выпит и самое время заглянуть в терминал:
Вуаля! Наше приложение построено! Самое время закинуть его на смартфон и запустить:
Все работает! И оказывается не все так сложно, как казалось.
Также меня спрашивали:
Ни у Flutter ни у React Native нет преимуществ перед языком разметки Kivy Language, которая позволяет создавать и позиционировать лайоуты и виджеты. Как по мне, то, как строится UI во Flutter — это самое настоящее извращение. Придумать это мог только больной на голову человек. Чтобы не быть голословным, давайте посмотрим на код Flutter и код Kivy одного и того же простейшего приложения… Выглядеть оно будет следующим образом:
Ниже я привожу код из статьи Про Flutter, кратко: Основы:
import 'package:flutter/widgets.dart';
main() => runApp(
Directionality(
textDirection: TextDirection.ltr,
child: Container(
color: Color(0xFFFFFFFF),
child: App(),
),
),
);
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector( // используется как обычный виджет
onTap: () { // одно из свойств GestureDetector
// Этот метод будет вызван, когда дочерний элемент будет нажат
print('You pressed me');
},
child: Container( // нашей кнопкой будет контейнер
decoration: BoxDecoration( // стилизуем контейнер
shape: BoxShape.circle, // зададим ему круглую форму
color: Color(0xFF17A2B8), // и покрасим его в синий
),
width: 80.0,
height: 80.0,
),
),
);
}
}
class Counter extends StatefulWidget {
// Изменяемое состояние хранится не в виджете, а внутри объекта особого класса,
// создаваемого методом createState()
@override
State<Counter> createState() => _CounterState();
// Результатом функции является не просто объект класса State,
// а обязательно State<ИмяНашегоВиджета>
}
class _CounterState extends State<Counter> {
// Внутри него мы наконец-то можем объявить динамические переменные,
// в которых мы будем хранить состояние.
// В данном случае, это счетчик количества нажатий
int counter = 0;
// А дальше все очень просто, мы имплементируем точно такой же метод
// для отрисовки виджетов, который мы использовали в классе Stateless виджета.
@override
Widget build(BuildContext context) {
// И тут практически ничего не изменилось с нашего последнего примера,
// а то что изменилось — я прокомментировал:
return Center(
child: GestureDetector(
onTap: () {
// В момент, когда кнопка нажата, мы увеличиваем значение
// перменной counter.
setState(() {
// setState() необходим для того, чтобы вызвать методы
// жизненного цикла виджета и сказать ему, что пора обновится
++counter;
});
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFF17A2B8),
),
width: 80.0,
child: Center(
child: Text( // выводим значение свойства counter
'$counter', // чтобы следить за его изменением
style: TextStyle(fontSize: 30.0),
),
),
),
),
);
}
}
А вот абсолютно тоже самое, но с использованием Kivy и KivyMD:
from kivy.lang import Builder
from kivymd.app import MDApp
KV = """
#:import get_color_from_hex kivy.utils.get_color_from_hex
Screen:
MDCard:
MDLabel:
value: 0
text: str(self.value)
halign: "center"
on_touch_down: self.value += 1
canvas.before:
Color:
rgba: get_color_from_hex("#4eaabe")
Ellipse:
pos: self.center[0] - dp(25), self.center[1] - dp(25)
size: dp(50), dp(50)
"""
class HelloWorld(MDApp):
def build(self):
return Builder.load_string(KV)
HelloWorld().run()
По-моему, вывод очевиден и не нуждается в моем комментировании…
Надеюсь, был вам полезен. Оставляю опрос на тему «Удалось ли вам построить приложение для Андроид».
ookami_kb
Видимо, я еще более больной на голову человек, потому что считаю, что система построения UI во флаттере – это едва ли не лучшее решение на рынке: статическая типизация, автодополнения от IDE, все фишки языка + не надо отдельно изучать язык разметки, composition over inheritance. Ну и примеры для флаттера и для киви у Вас все-таки разные.
HeaTTheatR Автор
Абсолютно идентичные!
ookami_kb
Даже если не принимать во внимание такие вещи, как размер и цвет шрифта (которые Вы не указываете в примере с киви), код на флаттере можно безболезненно сократить, например, до такого:
HeaTTheatR Автор
Мое мнение останется неизменным в любом случае — это невозможно читать!
ookami_kb
Ок. Вот только подписывать всех людей с мнением, отличным от Вашего, под больных – так себе занятие.
HeaTTheatR Автор
Это не только мое мнение. Код на Flutter абсолютно не читаем. Умные люди не зря придумали DSL языки разметки UI, а пихать все в код — это удел не совсем умных людей.
HeaTTheatR Автор
Ну, и да — указал размер шрифта:
technic93
Фу, это не про это случайно была статья на хабре про "ошибка в проектировании за которую мне стыдно"
напоминает мне как я был в первом классе и рисовал в "Лого"
technic93
глянул на это дело и запахло лиспом :)
ookami_kb
Если от скобочек начинает рябить в глазах, то это хороший показатель, что надо выносить код в отдельные виджеты/функции. Этим и хороши разметка в коде и общий принцип composition over inheritance во флаттере – вы просто применяете те же самые принципы, что и в остальном коде. Например (утрированный пример, но сам принцип такой):
tmnhy
JSX это всё-таки смесь
ежа с ужомкода и разметки, в отличии от Flutter, где только код.ookami_kb
Ну как сказать – это ж просто синтаксический сахар, чтобы оно выглядело похоже на html: It is called JSX, and it is a syntax extension to JavaScript.
technic93
В целом хорошо, если было бы {} вместо () было бы вообще привычно почти как какой ни будь жсон. У меня когда то давно первое время после знакомства с питоном были проблемы с возвращением на языки с си подобными скобочками. Со временем понимаешь что это все не важно. К тому же без подсветки синтаксиса ваш пример не так хорошо смотрится как мог бы. К тому же есть и плагины для разноцветных скобочек, и для отображения древовидной структуры. Лучше скринте свое IDE.
kuznetsovin
Хотелось бы увидеть приложение и для ios так как и прошлая статья направлена на андроид… Попробуйте собрать приложение под ios на flutter и на kivy…
HeaTTheatR Автор
Да без проблем!
kuznetsovin
Скрины можно? То что это скомпилировалось это уже хорошо. Но как при этом будет выглядеть приложение на ios?
HeaTTheatR Автор
Я не занимаюсь этим по одной простой причине — у меня нет iOS. А гонять все это в эмуляторе в X-Code смысла не вижу.
HeaTTheatR Автор
Точно так же как на Андроид, если вы юзаете KivyMD.
kuznetsovin
Проблема именно в этом нативные виджеты android и ios визуально отличаются и flutter очень хорошо это сглаживает...
kuznetsovin
Также хотелось бы увидеть более живой пример, например с отображением геолокации на карте на флаттер и на киви. Вот это был бы реальный пример...
tmnhy
Правильно я понял, что и не flexbox-контейнеры и не grid, а что то своё? Есть ли пересечение в подходе с общепринятыми методами позиционирования?
Как будет выглядеть описание «типичного» элемента списка в KivyMD?
HeaTTheatR Автор
То есть, привести разметку данной карточки?
tmnhy
Да, если не трудно. Можно даже не всей, а например «шапки» — ширина 100%, внутри два блока прижатые к краям, левый из двух горизонтальных элементов, правый из двух вертикальных. Без стилей, просто разметка.
HeaTTheatR Автор
Это, как вы выразились, не "типичный" элемент в KivyMD! Но, хорошо, я покажу...
tmnhy
Имеется в виду «типичный» не применительно к конкретному фреймворку, а к реальному приложению, такие элементы списка довольно часто встречаются в мобильных приложениях.
HeaTTheatR Автор
У нас нет набора "типичных" карточек. Они все разные и индивидуальные для любого приложения. У нас есть базовый класс MDCard и набор классов MDCardPost:
Конкретно такой карты, как привели вы — нет.
HeaTTheatR Автор
tmnhy
Если брать Flutter, то такая карточка довольно просто описывается (схематично):
HeaTTheatR Автор
Да чего уж там, приведите полный пример этой карты. Я в свою очередь сейчас размечу данную карту...
tmnhy
Вы в том плане, что он будет плохо читаем? Я не буду спорить, код будет многострочный, от количества закрывающих скобочек будет кружиться голова. Но есть декомпозиция и т.п.
Но не суть, я не про красоту, простоту и читаемость, в данный момент мне интересно, как на KivyMD сделать разметку с главной и поперечной осями.
HeaTTheatR Автор
Готово! Так пойдет или стили меток выдержать?
tmnhy
Ох, ёшки-матрёшки, я ж вас не на слабо беру, типа «так не получится». Но за то что сделали, респект.
Просто покажите, как выглядит код разметки, а я буду смотреть и думать, сложно-не-сложно для меня, сравнивать, думать о том, стоит ли возвращаться к Kivy, в своё время совсем не зашёл, может появление KivyMD всё изменило.
HeaTTheatR Автор
tmnhy
Спасибо.
nbytes
Вот посмотрел я на ваш код, скажите пожалуйста, как обстоят дела с автокомплитом и поддержкой IDE? По сути это все один большой комментарий для меня на данный момент, было бы логичней делать что-то вроде шаблонов в Django/Jinja2 где файл шаблона лежит отдельно и наполняется контекстом. Сильно не пинайте, если говорю глупости. Просто как это рефакторить в случае чего, я не представляю.
alter_fritz
Для pycharm есть плагин для подсветки синтаксиса kv
HeaTTheatR Автор
kv-файлы и должны лежать отдельным файлом.
nonname
В VScode есть плагин для kv файлов, в которые можно разметку вынести.
HeaTTheatR Автор
В PyCharm тоже — KV4Jetbrains
HeaTTheatR Автор
Только самодостаточный: скомпилировал — запустил!
ookami_kb
HeaTTheatR Автор
Это просто вырвиглазной пи… ец!
ookami_kb
Это очень аргументированное мнение!
HeaTTheatR Автор
Даже у Java все логично построено в их xml-разметке и спорить с этим бесполезно, у Flutter просто ужасный код!
RevanScript
С релизом Jetpack Compose там будет так же, как и на флаттере
fougasse
В Java взрослые люди стараются писать UI декларативно, а не заниматься извращегиями с XML.
DonAgosto
Думаю будет полезно добавить в статью последовательность сборки с использованием Docker. Проще потом автоматизировать сборку, да и целую виртуалку тащить на линуксовый хост как-то не очень имхо
HeaTTheatR Автор
Я не работал с Docker да и времени, если честно, в этом разбираться как бы нету.
nikita_dol
1) Пример на Flutter не работает :/
2) Если смотреть по коду, то не особо то и больше получается на Flutter (Если не считать строки со скобками)
3) Давайте этот же пример, но вместо счётчика будем рандомить цвет круга и менять его с анимацией длительностью 1 секунда и кривой easeOut (Мне интересно, как обстоят дела с анимацией)
hssergey
Выглядит красиво и надо будет поковырять это самостоятельно. Несколько предложений на будущее:
— разметку вынести в отдельные файлы. В этом случае можно уже создать плагины для IDE, которые бы подсвечивали синтаксис разметки, позволяли автокомплит и рефакторинг и т.п. Ну и в целом удобнее, в этом случае версткой и кодом могли бы заниматься разные люди.
— создать докер-образ для сборки под андроид. Тогда действительно можно будет билдить одной командой, не ставя зависимостей.
Кстати, как с поддержкой нескольких активитей/окон приложения? Редкий проект ограничивается одним окном…
worldmind
Вот да, DSL для гуя это мечта, а описывать это кодом это мягко говоря странно.
Neikist
Это отлично. Работают все рефакторинги и автокомплиты, отличная гибкость, можно использовать те же способы декомпозиции что и в коде обычном. Сколько боли в той же нативной разработке под андроид из за использования xml? Жуть.
worldmind
Это называется костыль — вместо того чтобы допилить инструментарий для поддержки читамоего и удобного DSL, делать всё кодом и считать что это хорошо.
Neikist
А зачем? Если описание UI во flatter, jetpack compose, swift ui, все равно выглядит достаточно декларативно, но дает при желании гибкость которая многим DSL и не снилась.
worldmind
Если выглядит, то да, но в этом и суть проблемы.
HeaTTheatR Автор
KV Language поддерживает код Python и гибок настолько, насколько гибок мягкий пластилин. В коде должна быть только логика и ничего более!
mgis
Просветите пожалуйста, меня несмышленного, смогу ли при помощи Kivy написать приложение на ТСД, которое будет получать доступ к встроенному 2D-сканеру, терминала?
HeaTTheatR Автор
Kivy == Python. Почти все библиотеки, которые поддерживаются Python смогут работать и на мобильном устройстве. На десктопе — все!
uurg
Как раз kivy изучаю в данный момент, только ради приложения для себя. И тут как раз эта статья, спасибо вам.
kiff2007200
Все работает спасибо за инструкцию!!!
HeaTTheatR Автор
Установите Android Debug Bridge. Подключите устройство к компьютеру USB кабелем. Откройте терминал и введите команду:
Запустите приложение на устройстве и смотрите ошибку в терминале.
kiff2007200
Да я забыл kivymd подключить просто)
А за способ отладки спасибо пригодится!!!
alter_fritz
До недавнего времени сборка apk отлично собирались, но появилась ошибка File "/usr/local/lib/python3.6/dist-packages/buildozer-0.40.dev0-py3.6.egg/buildozer/targets/android.py", line 816, in get_dist_dir
matching_dirs = glob.glob(join(self._build_dir, 'dist', '{}*'.format(dist_name)))
AttributeError: 'function' object has no attribute 'glob'
HeaTTheatR Автор
По этой инструкции все делали?
alter_fritz
Да, только не на виртуалке, а на настоящей убунте. Причем буквально два дня назад apk собиралась без проблем. Видимо это связано с апдейтом python-for-android.
HeaTTheatR Автор
Сейчас попробую на своей машине...
HeaTTheatR Автор
Все прекрасно собирается в виртуальной машине!
alter_fritz
Ошибка была у меня в коде: откатился до предыдущей версии и всё заработало. Хотя если честно не понятно почему buidozer валился
HeaTTheatR Автор
Да, код с ошибкой не будет построен.
alter_fritz
Хотя на десктопе всё запускалось без ошибок. Или же buidozer анализирует все файлы в проекте на наличие ошибок?
Imperson
Выполнял по инструкции. Не могу разобраться в чём дело…
HeaTTheatR Автор
Изображения вашего нет...
HeaTTheatR Автор
Полностью лог дайте.
DethSpirit
Попробовал прям с примером из статьи, apk собралось. Запускаю на двух смартфонах, появляется Loading… и вылетает. Вечером попробую с Android Debug Bridge разобраться, может станет понятно в чём дело.
Cheshiriks
Символично, буквально недавно узнал про kivy и сделал неделю назад простенькую игрушку для андроида на нем)
play.google.com/store/apps/details?id=org.corgi.corgi
Приятный инструмент, здорово, что продолжаете над ним работу.
dominigato
100MB это не многовато для простенькой игрушки..? Или там видео файлы.
Cheshiriks
Переборщил, знаю. Я не знаю, как сжимать изображения, там все элементы отрисованы для 4к разрешения и в png формате, поэтому получилось, что каждая кнопка весит по 1 Мб. Я пробовал преобразовать их в gif формат, они сразу становились раза в три легче, но и сильно теряли в качестве и получались мыльными\
dominigato
ОК, просто думал может это киви так добавил.
HeaTTheatR Автор
PHG компрессор
Cheshiriks
Спасибо)
Качество все же падает от этого сжатия тоже, но на экране телефона этого почти не заметно. Обновил версию, теперь весит 35 Мб)
HeaTTheatR Автор
Пожалуйста!
RuslanShirkhanov
А как дела обстоят с KivyMD Studio?
Вроде выглядит приятно, очень хочется пощупать
HeaTTheatR Автор
Этот проект развивается очень медленно, так как я работаю над ним в одиночку. А поскольку это не основной мой проект, плюс основная работа занимает львиную часть времени, то о каких-либо сроках говорить бессмысленно.