Во вчерашней статье Python в Mobile development, в которой речь шла о библиотеке KivyMD (коллекции виджетов в стиле Material Design для использования их в кроссплатформенном фреймворке Kivy), в комментариях меня попросили рассказать о процессе сборки пакета для платформы Android. Для многих этот процесс, к сожалению, был и остается чем-то из ряда магического шаманства и не подъёмным для новичков делом. Что ж, давайте разбираться, так ли на самом деле все сложно и действительно ли я маг и волшебник...



Конечно, мог бы! Итак, вы написали свой код на Python и Kivy. Что нужно для того, чтобы это можно было запустить на Android устройствах? Перейдите в репозиторий KivyMD и вы увидите, что в этой инструкции уже давно прописаны шаги, которые позволят вам собрать APK пакет:

  1. Загрузите 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()

По-моему, вывод очевиден и не нуждается в моем комментировании…

Надеюсь, был вам полезен. Оставляю опрос на тему «Удалось ли вам построить приложение для Андроид».

Комментарии (83)


  1. ookami_kb
    08.12.2019 19:55
    +3

    Как по мне, то, как строится UI во Flutter — это самое настоящее извращение. Придумать это мог только больной на голову человек.

    Видимо, я еще более больной на голову человек, потому что считаю, что система построения UI во флаттере – это едва ли не лучшее решение на рынке: статическая типизация, автодополнения от IDE, все фишки языка + не надо отдельно изучать язык разметки, composition over inheritance. Ну и примеры для флаттера и для киви у Вас все-таки разные.


    1. HeaTTheatR Автор
      08.12.2019 19:56

      Абсолютно идентичные!


      1. ookami_kb
        08.12.2019 20:08
        +2

        Даже если не принимать во внимание такие вещи, как размер и цвет шрифта (которые Вы не указываете в примере с киви), код на флаттере можно безболезненно сократить, например, до такого:


        import 'package:flutter/material.dart';
        import 'package:flutter/widgets.dart';
        
        main() => runApp(MaterialApp(home: Scaffold(body: Counter())));
        
        class Counter extends StatefulWidget {
          @override
          State<Counter> createState() => _CounterState();
        }
        
        class _CounterState extends State<Counter> {
          int _counter = 0;
        
          @override
          Widget build(BuildContext context) => Center(
                child: GestureDetector(
                  onTap: () => setState(() => _counter++),
                  child: Container(
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: Color(0xFF4eaabe),
                    ),
                    width: 80.0,
                    child: Center(
                      child: Text(
                        _counter.toString(),
                        style: TextStyle(fontSize: 30.0, color: Colors.white),
                      ),
                    ),
                  ),
                ),
              );
        }


        1. HeaTTheatR Автор
          08.12.2019 20:10

          Мое мнение останется неизменным в любом случае — это невозможно читать!


          1. ookami_kb
            08.12.2019 20:11
            +1

            Ок. Вот только подписывать всех людей с мнением, отличным от Вашего, под больных – так себе занятие.


            1. HeaTTheatR Автор
              08.12.2019 20:13
              -4

              Это не только мое мнение. Код на Flutter абсолютно не читаем. Умные люди не зря придумали DSL языки разметки UI, а пихать все в код — это удел не совсем умных людей.


        1. HeaTTheatR Автор
          08.12.2019 20:12

          Ну, и да — указал размер шрифта:


          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
                  font_size: "30sp"
          
                  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()


          1. technic93
            09.12.2019 10:54

            pos: self.center[0] — dp(25), self.center[1] — dp(25)

            Фу, это не про это случайно была статья на хабре про "ошибка в проектировании за которую мне стыдно"


            напоминает мне как я был в первом классе и рисовал в "Лого"


        1. technic93
          08.12.2019 22:49

          глянул на это дело и запахло лиспом :)


          1. ookami_kb
            08.12.2019 23:07
            +1

            Если от скобочек начинает рябить в глазах, то это хороший показатель, что надо выносить код в отдельные виджеты/функции. Этим и хороши разметка в коде и общий принцип composition over inheritance во флаттере – вы просто применяете те же самые принципы, что и в остальном коде. Например (утрированный пример, но сам принцип такой):


            Код
            import 'package:flutter/material.dart';
            import 'package:flutter/widgets.dart';
            
            main() => runApp(MaterialApp(home: Scaffold(body: Counter())));
            
            class Counter extends StatefulWidget {
              @override
              State<Counter> createState() => _CounterState();
            }
            
            class _CounterState extends State<Counter> {
              int _counter = 0;
            
              @override
              Widget build(BuildContext context) => Button(
                    onTap: () => setState(() => _counter++),
                    child: ButtonText(text: _counter.toString()),
                  );
            }
            
            class Button extends StatelessWidget {
              const Button({Key key, this.onTap, this.child}) : super(key: key);
            
              final VoidCallback onTap;
              final Widget child;
            
              @override
              Widget build(BuildContext context) => GestureDetector(
                    onTap: onTap,
                    child: Container(
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: Color(0xFF4eaabe),
                      ),
                      width: 80.0,
                      child: child,
                    ),
                  );
            }
            
            class ButtonText extends StatelessWidget {
              const ButtonText({Key key, this.text}) : super(key: key);
            
              final String text;
            
              @override
              Widget build(BuildContext context) => Center(
                    child: Text(
                      text,
                      style: TextStyle(fontSize: 30.0, color: Colors.white),
                    ),
                  );
            }


            1. tmnhy
              08.12.2019 23:12

              тот же React и JSX

              JSX это всё-таки смесь ежа с ужом кода и разметки, в отличии от Flutter, где только код.


              1. ookami_kb
                08.12.2019 23:16

                Ну как сказать – это ж просто синтаксический сахар, чтобы оно выглядело похоже на html: It is called JSX, and it is a syntax extension to JavaScript.


            1. technic93
              09.12.2019 11:10

              В целом хорошо, если было бы {} вместо () было бы вообще привычно почти как какой ни будь жсон. У меня когда то давно первое время после знакомства с питоном были проблемы с возвращением на языки с си подобными скобочками. Со временем понимаешь что это все не важно. К тому же без подсветки синтаксиса ваш пример не так хорошо смотрится как мог бы. К тому же есть и плагины для разноцветных скобочек, и для отображения древовидной структуры. Лучше скринте свое IDE.


  1. kuznetsovin
    08.12.2019 20:04

    Хотелось бы увидеть приложение и для ios так как и прошлая статья направлена на андроид… Попробуйте собрать приложение под ios на flutter и на kivy…


    1. HeaTTheatR Автор
      08.12.2019 20:07

      Да без проблем!


      image


      1. kuznetsovin
        08.12.2019 21:15

        Скрины можно? То что это скомпилировалось это уже хорошо. Но как при этом будет выглядеть приложение на ios?


        1. HeaTTheatR Автор
          08.12.2019 21:47
          +1

          Я не занимаюсь этим по одной простой причине — у меня нет iOS. А гонять все это в эмуляторе в X-Code смысла не вижу.


        1. HeaTTheatR Автор
          08.12.2019 21:48
          +1

          Но как при этом будет выглядеть приложение на ios?

          Точно так же как на Андроид, если вы юзаете KivyMD.


          1. kuznetsovin
            08.12.2019 22:31

            Проблема именно в этом нативные виджеты android и ios визуально отличаются и flutter очень хорошо это сглаживает...


  1. kuznetsovin
    08.12.2019 20:07
    +1

    Также хотелось бы увидеть более живой пример, например с отображением геолокации на карте на флаттер и на киви. Вот это был бы реальный пример...


  1. tmnhy
    08.12.2019 20:19

    Правильно я понял, что и не flexbox-контейнеры и не grid, а что то своё? Есть ли пересечение в подходе с общепринятыми методами позиционирования?

    Как будет выглядеть описание «типичного» элемента списка в KivyMD?
    image


    1. HeaTTheatR Автор
      08.12.2019 20:23

      То есть, привести разметку данной карточки?


      1. tmnhy
        08.12.2019 20:29

        Да, если не трудно. Можно даже не всей, а например «шапки» — ширина 100%, внутри два блока прижатые к краям, левый из двух горизонтальных элементов, правый из двух вертикальных. Без стилей, просто разметка.


  1. HeaTTheatR Автор
    08.12.2019 20:34

    Это, как вы выразились, не "типичный" элемент в KivyMD! Но, хорошо, я покажу...


    1. tmnhy
      08.12.2019 20:36

      Имеется в виду «типичный» не применительно к конкретному фреймворку, а к реальному приложению, такие элементы списка довольно часто встречаются в мобильных приложениях.


      1. HeaTTheatR Автор
        08.12.2019 20:41

        У нас нет набора "типичных" карточек. Они все разные и индивидуальные для любого приложения. У нас есть базовый класс MDCard и набор классов MDCardPost:


        image


        Конкретно такой карты, как привели вы — нет.


      1. HeaTTheatR Автор
        08.12.2019 20:44

        image


    1. tmnhy
      08.12.2019 20:52
      +1

      Если брать Flutter, то такая карточка довольно просто описывается (схематично):

      Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Row(
                children: [
                  Icon,
                  Text
                ]
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text,
                  Text
                ]
              ),
            ]
          ),
          Text,
          Text,
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Row(
                children: [ Chip, Chip, Chip ]
              ),
              Row(
                children: [
                  Icon,
                  Text
                ]
              ),
            ]
          ),
        ]
      )


      1. HeaTTheatR Автор
        08.12.2019 20:57

        Да чего уж там, приведите полный пример этой карты. Я в свою очередь сейчас размечу данную карту...


        1. tmnhy
          08.12.2019 21:07

          Вы в том плане, что он будет плохо читаем? Я не буду спорить, код будет многострочный, от количества закрывающих скобочек будет кружиться голова. Но есть декомпозиция и т.п.

          Но не суть, я не про красоту, простоту и читаемость, в данный момент мне интересно, как на KivyMD сделать разметку с главной и поперечной осями.


          1. HeaTTheatR Автор
            08.12.2019 21:16

            Готово! Так пойдет или стили меток выдержать?


            image


            1. tmnhy
              08.12.2019 21:23

              Ох, ёшки-матрёшки, я ж вас не на слабо беру, типа «так не получится». Но за то что сделали, респект.
              Просто покажите, как выглядит код разметки, а я буду смотреть и думать, сложно-не-сложно для меня, сравнивать, думать о том, стоит ли возвращаться к Kivy, в своё время совсем не зашёл, может появление KivyMD всё изменило.


              1. HeaTTheatR Автор
                08.12.2019 21:29
                +1

                image


                from kivy.lang import Builder
                from kivymd.app import MDApp
                
                KV = """
                <MDLabel>:
                    size_hint_y: None
                    height: self.texture_size[1]
                    pos_hint: {"center_y": .5}
                
                <MDChip>:
                    pos_hint: {"center_y": .5} 
                
                Screen:
                
                    MDCard:
                        orientation: "vertical"
                        padding: "5dp"
                        spacing: "10dp"
                        size_hint_y: None
                        height: self.minimum_height
                        pos_hint: {"center_y": .5}
                
                        BoxLayout:
                            size_hint_y: None
                            height: self.minimum_height
                            spacing: "10dp"
                            padding: "10dp"
                
                            Image:
                                size_hint: None, None
                                size: "40dp", "40dp"
                                source: "data/logo/kivy-icon-128.png"
                
                            MDLabel:
                                text: "User 123"
                                font_style: "H6" 
                                bold: True               
                
                            Widget:
                
                            BoxLayout:
                                orientation: "vertical"
                                spacing: "5dp"
                
                                MDLabel:
                                    text: "2019-12.08 20:55"
                                    font_style: "Button"
                                    halign: "right"
                
                                MDLabel:
                                    text: "Development"
                                    font_style: "H6"
                                    halign: "right"
                                    bold: True 
                
                        MDLabel:
                            text: "Как перенести Linux из embedded"
                            font_style: "H4"
                            bold: True 
                
                        MDLabel:
                            text:
                                "Да, если не трудно. Можно даже не всей, а например «шапки» — "                 "ширина 100%, внутри два блока прижатые к краям, левый из "                 "двух горизонтальных элементов, правый из двух вертикальных. "                 "Без стилей, просто разметка."
                
                        BoxLayout:
                            size_hint_y: None
                            height: self.minimum_height
                            spacing: "5dp"
                
                            MDChip:
                                label: "arm"
                                icon: ''
                
                            MDChip:
                                label: "linux"
                                icon: ''
                
                            MDChip:
                                label: "qume"
                                icon: ''
                
                            Widget:
                
                            BoxLayout:
                                size_hint: None, None
                                size: self.minimum_size
                                padding: "10dp"
                
                                MDIconButton:
                                    user_font_size: "18sp"
                                    icon: "mail"
                
                                Label:
                                    text: "13"
                                    color: 0, 0, 0, 1
                                    size_hint: None, None
                                    size: self.texture_size
                                    pos_hint: {"center_y": .5}
                """
                
                class Card(MDApp):
                    def build(self):
                        return Builder.load_string(KV)
                
                Card().run()


                1. tmnhy
                  08.12.2019 21:30

                  Спасибо.


                1. nbytes
                  09.12.2019 03:16

                  Вот посмотрел я на ваш код, скажите пожалуйста, как обстоят дела с автокомплитом и поддержкой IDE? По сути это все один большой комментарий для меня на данный момент, было бы логичней делать что-то вроде шаблонов в Django/Jinja2 где файл шаблона лежит отдельно и наполняется контекстом. Сильно не пинайте, если говорю глупости. Просто как это рефакторить в случае чего, я не представляю.


                  1. alter_fritz
                    09.12.2019 11:30

                    Для pycharm есть плагин для подсветки синтаксиса kv


                  1. HeaTTheatR Автор
                    09.12.2019 12:03

                    kv-файлы и должны лежать отдельным файлом.


                  1. nonname
                    09.12.2019 14:24

                    В VScode есть плагин для kv файлов, в которые можно разметку вынести.


                    1. HeaTTheatR Автор
                      09.12.2019 14:29

                      В PyCharm тоже — KV4Jetbrains
                      image


      1. HeaTTheatR Автор
        08.12.2019 20:58

        Только самодостаточный: скомпилировал — запустил!


        1. ookami_kb
          08.12.2019 22:31

          Как вариант:
          import 'package:flutter/material.dart';
          import 'package:flutter/widgets.dart';
          
          main() => runApp(MaterialApp(home: Scaffold(body: MyCard())));
          
          class MyCard extends StatelessWidget {
            @override
            Widget build(BuildContext context) => Center(
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(8),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          _buildHeader(),
                          _buildTitle(context),
                          _buildContent(),
                          _buildFooter(),
                        ],
                      ),
                    ),
                  ),
                );
          
            Widget _buildHeader() => Row(
                  children: <Widget>[
                    Icon(Icons.person, size: 48),
                    Expanded(child: Text('User 123')),
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: <Widget>[
                        Text('2019-12-08 20:55'),
                        Text('User 123'),
                      ],
                    ),
                  ],
                );
          
            Widget _buildTitle(BuildContext context) => Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8),
                  child: Text(
                    'Как перенести Linux из embedded',
                    style: Theme.of(context).textTheme.title,
                  ),
                );
          
            Widget _buildContent() => Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: Text(text),
                );
          
            Widget _buildFooter() => Row(
                  children: <Widget>[
                    Expanded(
                      child: Wrap(
                        spacing: 5,
                        children: <Widget>[
                          Chip(label: Text('arm')),
                          Chip(label: Text('linux')),
                          Chip(label: Text('qemu')),
                        ],
                      ),
                    ),
                    Icon(Icons.comment),
                    Text('13'),
                  ],
                );
          
            String get text =>
                'Да, если не трудно. Можно даже не всей, а например «шапки» — '
                'ширина 100%, внутри два блока прижатые к краям, левый из двух '
                'горизонтальных элементов, правый из двух вертикальных. '
                'Без стилей, просто разметка.';
          }


          1. HeaTTheatR Автор
            08.12.2019 22:46
            -3

            Это просто вырвиглазной пи… ец!


            1. ookami_kb
              08.12.2019 22:47

              Это очень аргументированное мнение!


              1. HeaTTheatR Автор
                08.12.2019 22:50
                -1

                Даже у Java все логично построено в их xml-разметке и спорить с этим бесполезно, у Flutter просто ужасный код!


                1. RevanScript
                  08.12.2019 23:22

                  С релизом Jetpack Compose там будет так же, как и на флаттере


                1. fougasse
                  09.12.2019 13:16
                  -2

                  В Java взрослые люди стараются писать UI декларативно, а не заниматься извращегиями с XML.


  1. DonAgosto
    08.12.2019 21:46

    Думаю будет полезно добавить в статью последовательность сборки с использованием Docker. Проще потом автоматизировать сборку, да и целую виртуалку тащить на линуксовый хост как-то не очень имхо


    1. HeaTTheatR Автор
      08.12.2019 21:50

      Я не работал с Docker да и времени, если честно, в этом разбираться как бы нету.


  1. nikita_dol
    08.12.2019 23:04

    1) Пример на Flutter не работает :/
    2) Если смотреть по коду, то не особо то и больше получается на Flutter (Если не считать строки со скобками)
    3) Давайте этот же пример, но вместо счётчика будем рандомить цвет круга и менять его с анимацией длительностью 1 секунда и кривой easeOut (Мне интересно, как обстоят дела с анимацией)


  1. hssergey
    09.12.2019 07:59

    Выглядит красиво и надо будет поковырять это самостоятельно. Несколько предложений на будущее:
    — разметку вынести в отдельные файлы. В этом случае можно уже создать плагины для IDE, которые бы подсвечивали синтаксис разметки, позволяли автокомплит и рефакторинг и т.п. Ну и в целом удобнее, в этом случае версткой и кодом могли бы заниматься разные люди.
    — создать докер-образ для сборки под андроид. Тогда действительно можно будет билдить одной командой, не ставя зависимостей.

    Кстати, как с поддержкой нескольких активитей/окон приложения? Редкий проект ограничивается одним окном…


  1. worldmind
    09.12.2019 09:57

    Вот да, DSL для гуя это мечта, а описывать это кодом это мягко говоря странно.


    1. Neikist
      09.12.2019 10:41

      Это отлично. Работают все рефакторинги и автокомплиты, отличная гибкость, можно использовать те же способы декомпозиции что и в коде обычном. Сколько боли в той же нативной разработке под андроид из за использования xml? Жуть.


      1. worldmind
        09.12.2019 10:44

        Это называется костыль — вместо того чтобы допилить инструментарий для поддержки читамоего и удобного DSL, делать всё кодом и считать что это хорошо.


        1. Neikist
          09.12.2019 11:02

          А зачем? Если описание UI во flatter, jetpack compose, swift ui, все равно выглядит достаточно декларативно, но дает при желании гибкость которая многим DSL и не снилась.


          1. worldmind
            09.12.2019 11:06

            Если выглядит, то да, но в этом и суть проблемы.


          1. HeaTTheatR Автор
            09.12.2019 12:06

            KV Language поддерживает код Python и гибок настолько, насколько гибок мягкий пластилин. В коде должна быть только логика и ничего более!


  1. mgis
    09.12.2019 11:32

    Просветите пожалуйста, меня несмышленного, смогу ли при помощи Kivy написать приложение на ТСД, которое будет получать доступ к встроенному 2D-сканеру, терминала?


    1. HeaTTheatR Автор
      09.12.2019 12:08

      Kivy == Python. Почти все библиотеки, которые поддерживаются Python смогут работать и на мобильном устройстве. На десктопе — все!


  1. uurg
    09.12.2019 12:07

    Как раз kivy изучаю в данный момент, только ради приложения для себя. И тут как раз эта статья, спасибо вам.


  1. kiff2007200
    09.12.2019 15:28

    Все работает спасибо за инструкцию!!!


    1. HeaTTheatR Автор
      09.12.2019 15:31

      Установите Android Debug Bridge. Подключите устройство к компьютеру USB кабелем. Откройте терминал и введите команду:


      adb logcat | grep python

      Запустите приложение на устройстве и смотрите ошибку в терминале.


      1. kiff2007200
        09.12.2019 15:57

        Да я забыл kivymd подключить просто)
        А за способ отладки спасибо пригодится!!!


  1. alter_fritz
    09.12.2019 21:52

    До недавнего времени сборка 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'


    1. HeaTTheatR Автор
      09.12.2019 22:40

      По этой инструкции все делали?


      1. alter_fritz
        09.12.2019 22:53

        Да, только не на виртуалке, а на настоящей убунте. Причем буквально два дня назад apk собиралась без проблем. Видимо это связано с апдейтом python-for-android.


        1. HeaTTheatR Автор
          09.12.2019 23:02

          Сейчас попробую на своей машине...


        1. HeaTTheatR Автор
          09.12.2019 23:26
          +1

          image


          Все прекрасно собирается в виртуальной машине!


          1. alter_fritz
            10.12.2019 10:37

            Ошибка была у меня в коде: откатился до предыдущей версии и всё заработало. Хотя если честно не понятно почему buidozer валился


            1. HeaTTheatR Автор
              10.12.2019 11:09

              Да, код с ошибкой не будет построен.


              1. alter_fritz
                10.12.2019 11:39

                Хотя на десктопе всё запускалось без ошибок. Или же buidozer анализирует все файлы в проекте на наличие ошибок?


          1. Imperson
            10.12.2019 11:13

            Выполнял по инструкции. Не могу разобраться в чём дело…
            image


            1. HeaTTheatR Автор
              10.12.2019 11:13

              Изображения вашего нет...


            1. HeaTTheatR Автор
              10.12.2019 11:52

              Полностью лог дайте.


  1. DethSpirit
    10.12.2019 11:49

    Попробовал прям с примером из статьи, apk собралось. Запускаю на двух смартфонах, появляется Loading… и вылетает. Вечером попробую с Android Debug Bridge разобраться, может станет понятно в чём дело.


  1. Cheshiriks
    10.12.2019 15:31

    Символично, буквально недавно узнал про kivy и сделал неделю назад простенькую игрушку для андроида на нем)
    play.google.com/store/apps/details?id=org.corgi.corgi
    Приятный инструмент, здорово, что продолжаете над ним работу.


    1. dominigato
      10.12.2019 18:03

      100MB это не многовато для простенькой игрушки..? Или там видео файлы.


      1. Cheshiriks
        10.12.2019 18:08

        Переборщил, знаю. Я не знаю, как сжимать изображения, там все элементы отрисованы для 4к разрешения и в png формате, поэтому получилось, что каждая кнопка весит по 1 Мб. Я пробовал преобразовать их в gif формат, они сразу становились раза в три легче, но и сильно теряли в качестве и получались мыльными\


        1. dominigato
          10.12.2019 18:31

          ОК, просто думал может это киви так добавил.


        1. HeaTTheatR Автор
          10.12.2019 19:00

          1. Cheshiriks
            11.12.2019 22:05

            Спасибо)
            Качество все же падает от этого сжатия тоже, но на экране телефона этого почти не заметно. Обновил версию, теперь весит 35 Мб)


            1. HeaTTheatR Автор
              11.12.2019 22:22

              Пожалуйста!


  1. RuslanShirkhanov
    11.12.2019 15:22

    А как дела обстоят с KivyMD Studio?
    Вроде выглядит приятно, очень хочется пощупать


    1. HeaTTheatR Автор
      11.12.2019 15:29

      Этот проект развивается очень медленно, так как я работаю над ним в одиночку. А поскольку это не основной мой проект, плюс основная работа занимает львиную часть времени, то о каких-либо сроках говорить бессмысленно.