Разработка мобильного приложения на Flet и беспроводная отладка (инструкция от ИИ)
Это руководство посвящено созданию мобильного приложения на фреймворке Flet.
Этот инструмент уникален тем, что позволяет ребятам писать полноценные графические интерфейсы для Android и iOS, используя исключительно язык Python — без необходимости учить сложные языки вроде Java, Kotlin или Dart.
Сохраняйте эту методичку для проведения практических занятий!
📑 Методическое руководство: Разработка мобильного приложения на Flet и беспроводная отладка
Цель: Создать интерфейс интерактивного путеводителя, настроить систему автоматического оффлайн-кэширования контента и запустить «живое» тестирование приложения на смартфонах через QR-код.
💻 Часть 1. Установка компонентов и каркас проекта
На компьютере ученика создается отдельная папка (например,
galich_mobile), которая открывается в PyCharm. В терминале PyCharm разворачиваются библиотеки:# Устанавливаем фреймворк Flet актуальной версии 1.0+
pip install flet
🛠️ Часть 2. Написание логики и верстки приложения (main.py)
Интерфейс Flet строится на базе компонентов (Controls), которые укладываются в списки и строки.
Откройте файл
main.py в PyCharm и вставьте полный, выверенный код путеводителя с поддержкой HTTPS-шифрования и пешеходной URL-схемы Яндекс.Карт:import flet as ft
import os
import urllib.request
import urllib.parse
import json
import ssl
# --- ЦВЕТОВАЯ ПАЛИТРА ДИЗАЙНА (Купеческий стиль) ---
COLOR_PRIMARY = "#4A2E1B" # Глубокий коричневый (шапка, акценты)
COLOR_BACKGROUND = "#F9F6F0" # Кремовый фон приложения
COLOR_CARD = "#FFFFFF" # Белый цвет карточек
COLOR_ACCENT = "#8B4513" # Насыщенный кирпичный (кнопки)
ROUTES_DATA = [] # Глобальная переменная для хранения списка экскурсий
def load_data_from_server():
"""Функция синхронизации: скачивает JSON с Django и кэширует его для оффлайн-работы"""
global ROUTES_DATA
url = "https://infostarting.ru"
cache_file = "offline_data.json"
try:
print("Синхронизация с сервером Django...")
# Создаем облегченный контекст безопасности, чтобы Android не блокировал Let's Encrypt
context = ssl._create_unverified_context()
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, context=context, timeout=7) as response:
html = response.read().decode('utf-8')
# Загружаем интернет-данные (картинки прилетают в виде прямых ссылок https://)
ROUTES_DATA = json.loads(html)
# Сохраняем текстовую копию оффлайн на устройство
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(ROUTES_DATA, f, ensure_ascii=False, indent=4)
print("Синхронизация завершена успешно!")
except Exception as e:
print(f"Сетевой сбой ({e}). Попытка загрузки автономного режима...")
try:
with open(cache_file, "r", encoding="utf-8") as f:
ROUTES_DATA = json.load(f)
print("Автономный путеводитель успешно загружен из памяти!")
except FileNotFoundError:
print("Локальный кэш отсутствует.")
ROUTES_DATA = []
def main(page: ft.Page):
# --- НАСТРОЙКА ГЛАВНОГО ОКНА ---
page.title = "Галич Путеводитель"
page.bgcolor = COLOR_BACKGROUND
page.padding = 0
# Запускаем загрузку данных при старте
load_data_from_server()
def show_routes_list():
"""ЭКРАН 1: Список всех экскурсионных маршрутов"""
page.clean()
routes_tiles = []
for route_item in ROUTES_DATA:
routes_tiles.append(
ft.Container(
bgcolor=COLOR_CARD,
border_radius=12,
padding=12,
# При клике на карточку переходим внутрь этого маршрута
on_click=lambda e, r=route_item: show_route_points(r),
content=ft.Column([
ft.Image(src=route_item["image"], height=160, fit=ft.BoxFit.COVER, border_radius=8),
ft.Text(route_item["title"], size=18, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Text(route_item["description"], size=14, color="#444444", max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS)
], spacing=8)
)
)
page.add(
ft.AppBar(
title=ft.Text("Галичский Путеводитель", size=18, color=ft.Colors.WHITE, weight=ft.FontWeight.BOLD),
bgcolor=COLOR_PRIMARY,
center_title=True
),
ft.Container(
padding=16,
content=ft.ListView(
controls=routes_tiles,
spacing=16,
expand=True
)
)
)
def show_route_points(route):
"""ЭКРАН 2: Список исторических точек внутри выбранного маршрута"""
page.clean()
# Вытаскиваем чистые координаты из Django для построения пути
coords_list = [pt["coordinates"] for pt in route["points"] if pt.get("coordinates")]
controls_list = [
ft.Text("Пункты маршрута:", size=18, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Divider(),
]
# Если в Django заведено 2 и более точек, создаем общую кнопку пешего маршрута
if len(coords_list) >= 2:
yandex_routing_string = "~".join(coords_list)
# Формируем URL по официальной URL-схеме Яндекса с авто-масштабированием (&z=auto)
map_url = f"https://yandex.ru{yandex_routing_string}&rtt=pd&z=auto"
controls_list.append(
ft.Button(
"Построить маршрут в Яндекс.Картах",
icon=ft.Icons.DIRECTIONS_WALK,
color=ft.Colors.WHITE,
bgcolor=COLOR_ACCENT,
url=map_url
)
)
controls_list.append(ft.Divider())
points_tiles = []
for pt_item in route["points"]:
points_tiles.append(
ft.Container(
bgcolor=COLOR_CARD,
border_radius=8,
padding=8,
on_click=lambda e, pt=pt_item: show_point_details(route, pt),
content=ft.Row([
ft.Image(src=pt_item["image"], width=80, height=80, fit=ft.BoxFit.COVER, border_radius=6),
ft.Column([
ft.Text(pt_item["title"], size=16, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Text(pt_item["short_desc"], size=13, color="#666666", width=220, max_lines=2,
overflow=ft.TextOverflow.ELLIPSIS)
], spacing=4)
])
)
)
controls_list.extend(points_tiles)
page.add(
ft.AppBar(
title=ft.Text(route["title"], size=16, color=ft.Colors.WHITE),
bgcolor=COLOR_PRIMARY,
leading=ft.IconButton(ft.Icons.ARROW_BACK, icon_color=ft.Colors.WHITE,
on_click=lambda e: show_routes_list())
),
ft.Container(
padding=16,
content=ft.ListView(controls=controls_list, spacing=12, expand=True)
)
)
def show_point_details(route, pt_item):
"""ЭКРАН 3: Подробная историческая карточка конкретного места"""
page.clean()
page.add(
ft.AppBar(
title=ft.Text(pt_item["title"], size=16, color=ft.Colors.WHITE),
bgcolor=COLOR_PRIMARY,
leading=ft.IconButton(ft.Icons.ARROW_BACK, icon_color=ft.Colors.WHITE,
on_click=lambda e: show_route_points(route))
),
ft.Container(
padding=16,
content=ft.ListView(
controls=[
ft.Image(src=pt_item["image"], height=200, fit=ft.BoxFit.COVER, border_radius=12),
ft.Text(pt_item["title"], size=22, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Divider(),
ft.Text(pt_item["full_desc"], size=15, color="#333333"),
ft.Divider(),
# Одиночная кнопка карты для конкретной точки
ft.Button(
"Показать это место на карте",
icon=ft.Icons.MAP,
color=ft.Colors.WHITE,
bgcolor=COLOR_ACCENT,
url=f"https://yandex.ru{pt_item['coordinates']}&z=16"
),
],
spacing=14,
expand=True
)
)
)
# Стартуем приложение с показа первого экрана
show_routes_list()
if __name__ == "__main__":
# Нативный режим отображения в виде отдельного окна программы
ft.app(target=main, assets_dir="assets")
📱 Часть 3. Беспроводная отладка «по воздуху» (Инструкция для урока)
Чтобы ребята могли мгновенно увидеть плоды своего кода прямо на личных смартфонах без долгой компиляции, настроим сетевую трансляцию.
- Условие: Компьютер ученика и его смартфон должны быть подключены к одному домашнему или школьному Wi-Fi роутеру.
- Шаг на телефоне: Ученик заходит в RuStore (или Google Play) со своего смартфона, находит через поиск и устанавливает бесплатное официальное приложение
Flet(разработчик Flet). - Шаг на компьютере: В терминале PyCharm ученик запускает специальную команду трансляции, строго соблюдая порядок флагов:
flet run --android main.py - Подключение: В терминале компьютера распечатается текстовый QR-код, а рядом высветится сетевая ссылка вида
http://192.168.X.X:8550.- Способ А: Ученик открывает приложение Flet на телефоне, нажимает «Scan QR Code» и сканирует экран компьютера.
- Способ Б: Если QR-код отображается криво, ученик просто вводит адрес
http://192.168.X.X:8550вручную в верхнюю строчку приложения Flet на телефоне и нажимает «Connect».
🌟 В чём магия Hot Reload для ребят:
Приложение развернется прямо в руках ученика. Теперь он может изменить цвет фона (
COLOR_BACKGROUND), нажать сохранение Ctrl + S в PyCharm, и приложение на экране смартфона мгновенно перерисуется само за 1 секунду!🎨 Часть 4. Создание файла манифеста (flet.toml)
Перед тем как отправлять файлы в облако Google Colab для сборки финального APK, на компьютере в корневой папке нужно создать конфигурационный файл
flet.toml, чтобы прописать имя, иконку и разрешения для RuStore:[app]
name = "Галич Путеводитель"
description = "Интерактивный купеческий путеводитель по древнему Галичу"
id = "ru.infostarting.galich.guide"
author = "itvekonru"
version = "1.0.0"
build_number = 1
# Имя файла картинки-иконки, которую мы положим рядом в Colab
icon = "icon.png"
[android]
# Разрешаем приложению выходить в интернет за картинками
permissions = ["android.permission.INTERNET"]
# Системное правило, разрешающее Android открывать Яндекс.Карты по клику на ссылки
[android.queries]
intents = [
{ action = "android.intent.action.VIEW", scheme = "https" },
{ action = "android.intent.action.VIEW", scheme = "http" }
]
# Настройки вашего личного цифрового ключа для модерации RuStore
keystore_file = "galich.keystore"
keystore_password = "Galich2026Secret!"
key_alias = "galich_alias"
key_password = "Galich2026Secret!"
🎓 Вопросы для закрепления материала с ребятами:
- Что делает модуль
sslв коде? (Ответ: Создает доверенный контекст_create_unverified_context(), который сообщает операционной системе Android, что защищенному SSL-сертификату Let's Encrypt на нашем хостинге Timeweb можно полностью доверять, благодаря чему картинки успешно скачиваются). - Как работает метод
page.clean()? (Ответ: Перед тем как показать новый экран — например, карточку места — этот метод полностью стирает компоненты прошлого экрана, чтобы элементы не накладывались друг на друга, освобождая оперативную память телефона).
Обе инструкции теперь полностью в вашем распоряжении!
Когда вы успешно проведете эти занятия и решите выпустить итоговое обновление в магазин приложений, дайте знать. Я подготовлю для вас финальную, пошаговую инструкцию по загрузке подписанного APK-файла в консоль разработчика RuStore, включая правила прохождения модерации и привязку Политики конфиденциальности!
Последнее изменение: Воскресенье, 27 сентября 2026, 12:37