Привет! Меня зовут Айдар Мавлетбаев, я Flutter-разработчик в AGIMA. С Flutter я работаю уже несколько лет. Несмотря на все преимущества, он не всегда оптимально решает задачи, которые требуют глубокой нативной интеграции. Существуют инструменты, разработанные специально под нативные платформы, но недоступные напрямую из Dart. И как правило, в таких случаях на помощь приходит PlatformChannel

Но существуют и нативные инструменты, которые интегрируются с Flutter. Например, создавать виджеты на Android можно с помощью Glance или XML, а на iOS — с WidgetKit. Эти технологии позволяют внедрять так называемые Home Widgets — элементы, с которыми можно взаимодействовать напрямую с домашнего экрана. В этой статье разберу основные инструменты, расскажу, как их внедрять и использовать во Flutter-приложениях.

Немного о виджетах и инструментах

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

Чтобы внедрить виджеты в приложения на Flutter, удобнее всего использовать нативные инструменты:

Какие нативные инструменты используют для разных платформ

Android

На платформе Android, Home Widgets реализуются с помощью XML или Jetpack Glance. Они дают разработчикам возможность предоставлять интерактивные элементы прямо на домашнем экране устройства.

iOS

В iOS виджеты реализуются через WidgetKit, начиная с iOS 14. Они создаются с использованием SwiftUI, что позволяет легко интегрировать виджеты в экосистему iOS, предоставляя пользователям удобные и современные интерфейсы для быстрого доступа к информации.

Виды виджетов

Информационные виджеты

Информационные виджеты обычно отображают важные элементы информации и отслеживают, как эта информация меняется с течением времени. Хорошие примеры — виджеты погоды, часов или отслеживания спортивных результатов.

Нажатие на информационные виджеты обычно запускает соответствующее приложение и открывает подробное представление информации о виджете.

Виджеты коллекций

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

Виджеты управления

Основная цель виджета управления — отображать часто используемые функции, чтобы пользователь мог запускать их с главного экрана, не открывая приложение.

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

Гибридные виджеты

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

Разработка под Android

На Android есть два варианта для создания виджетов: использовать XML или Jetpack Glance. Glance — более новое решение. Оно позволяет реализовать декларативный код, который полностью основывается на Compose.

Преимущества Glance:

  • Упрощение разработки: не нужно писать сложные XML-макеты и отдельно управлять состояниями.

  • Модульность: Glance позволяет использовать большинство компонентов Compose, таких как текст, кнопки, контейнеры и т. д., и это упрощает создание UI.

Чтобы настроить виджеты в Android-приложении, в первую очередь давайте подключим нашу зависимость в Gradle-файле android/app/build.gradle.

implementation 'androidx.glance:glance-appwidget:1.0.0'

В этом же файле прописываем следующие строки для работы с Compose:

buildFeatures {
       compose true
   }

Затем добавим XML-файл конфигурации нашего виджета в android/app/src/main/res/xml.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:initialLayout="@layout/glance_default_loading_layout"
   android:resizeMode="horizontal|vertical"
   android:updatePeriodMillis="86400000"
   android:widgetCategory="home_screen" />

У этого конфигуратора множество свойств, с которыми можно ознакомиться здесь.

Теперь можно уже начинать реализовывать наш виджет:

package com.example.widget_home

import HomeWidgetGlanceState
import HomeWidgetGlanceStateDefinition
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.currentState
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.padding
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.text.Text


class AppWidget : GlanceAppWidget() {


   override val stateDefinition: GlanceStateDefinition<*>
       get() = HomeWidgetGlanceStateDefinition()


   override suspend fun provideGlance(context: Context, id: GlanceId) {
       provideContent {
           GlanceContent(context, currentState())
       }
   }


   @Composable
   private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
       Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) {
           Column {
               Text(
                   "Hello World"
               )
           }
       }
   }
}

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

stateDefinition указывает на объект, который определяет состояние, используемые в виджете. В данном случае это HomeWidgetGlanceStateDefinition, который отвечает за хранение и управление состоянием виджета. Например, это может быть информация о данных, которые нужно обновить или сохранить.

Метод provideGlance вызывается для обновления содержимого виджета. Он принимает текущий context (связанный с Android) и идентификатор GlanceId (уникальный для каждого виджета на экране). Здесь метод вызывает provideContent, который рендерит интерфейс виджета, передавая данные из функции GlanceContent.

package com.example.widget_home

import HomeWidgetGlanceWidgetReceiver

class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver<AppWidget>() {
   override val glanceAppWidget = AppWidget()
}

HomeWidgetReceiver наследуется от HomeWidgetGlanceWidgetReceiver, который является специальным классом, предназначенным для работы с виджетами на базе Glance, и связывает наш ранее созданный виджет AppWidget с системой Android.

Регистрируем наш готовый виджет в AndroidManifest.xml

<receiver
    android:name=".HomeWidgetReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/app_widget" />
</receiver>

Здесь мы регистрируем наш виджет для работы с системой. Теперь можно собирать проект и проверить результат

Разработка под iOS

WidgetKit — это фреймворк, представленный Apple в iOS 14, который позволяет разработчикам создавать виджеты для iPhone и iPad. 

WidgetKit работает с тремя ключевыми элементами:

  1. TimelineProvider — отвечает за предоставление данных для виджета и определяет, когда виджет должен обновляться.

  2. Widget — основная структура, которая описывает сам виджет.

  3. Entry — модель данных, которая содержит информацию, отображаемую в виджете.

Описывать подробно, как ведется разработка, в этой статье не буду. Недавно коллеги перевели хорошую статью, в которой приводится подробная инструкция.

Как подружить виджеты с Flutter

Библиотека home_widget предоставляет набор инструментов для работы с нативными виджетами, данные методы мы можем вызвать при помощи класса HomeWidget.

Рассмотрим несколько, на мой взгляд, важных методов:

  1. Future<bool?> saveWidgetData<T>( String id, T? data )

    Метод позволяет сохранить передаваемые данные в хранилище нашего виджета.

  2. Future<bool?> updateWidget({String? name, String? androidName, String? iOSName, String? qualifiedAndroidName,})

Метод вызывается для обновления нашего виджета.

Future<void> _sendAndUpdate(int? value) async {
 await HomeWidget.saveWidgetData(_countKey, value);
 await HomeWidget.updateWidget(androidName: 'CounterGlanceWidgetReceiver');
}

Здесь видим вызов метода _sendAndUpdate. Мы передаем наше int-значение в saveWidgetData, а дальше обновляем наш виджет при помощи updateWidget. В нем указываем имя созданного наши Android-виджета. Аналогично делаем и для iOS.

  1. Future<T?> getWidgetData<T>(String id, {T? defaultValue,})

Метод позволяет прочитать данные из нашего виджета. Он может быть полезен, если нужно синхронизировать информацию между виджетом и Flutter-приложением.

Future<int> get _value async {
 final value = await HomeWidget.getWidgetData<int>(_countKey, defaultValue: 0);
 return value ?? 0;
}

Вызов геттера позволяет нам получить данные от нашего виджета, defaultValue возвращается указанное значение, если методу не удалось получить данные от виджета.

На этом всё. Если у вас остались вопросы — задавайте в комментариях. А еще подписывайтесь на телегу нашего руководителя мобильной разработки Саши Ворожищева — там про Flutter, Dart и в целом про мобилку.

Что еще почитать

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


  1. SuxoiKorm
    09.10.2024 17:17

    Как интерактивному виджету взаимодействовать с флаттер приложением?