Плагин активити#

Activity Plugin — это автономный модуль, который встраивается прямо в GsBaseActivity, добавляя ей новую функцию (NFC-сканер, BLE-монитор, WebRTC-звонки и т.п.) без изменений в бизнес-логике экранов и без пересборки ядра GMF.

К плагинам активити можно получать доступ из главного потока.

Для периодической работы независимо от окна приложения используется фоновая служба Результаты фоновой работы можно передавать текущему экрану через внешние события

Базовые плагины (камера, NFC, QR-сканер) включены «из коробки» и будут пополняться; разработчик может добавить собственный, реализовав тот же интерфейс.

Мотивация#

  • Изолировать платформенные API: весь код работы с NFC, камерой или сенсорами живёт в плагине, а приложение общается только через колбэк-интерфейсы.

  • Повторно использовать логику: один плагин можно подключить сразу во многие приложения.

  • Синхронизировать жизненный цикл: плагин получает те же события (onCreate/Resume/Pause…), что и Activity, и не нарушает транзакции State Manager’а.

Контракт#

interface ActivityPlugin : IPkg {

    /* ───────────── 1. Инициализация ───────────── */

    /** Один раз до показа UI. Можно выполнять работу в IO-диспетчере. */
    suspend fun initializeAsync(activity: GsBaseActivity<*>) {}

    /** Вызывается сразу после инициализации UI. */
    fun onInitializeUi(activity: GsBaseActivity<*>, navigator: Navigator) {}


    /* ───────────── 2. Жизненный цикл ───────────── */

    fun onCreate (activity: GsBaseActivity<*>) {}
    fun onStart  (activity: GsBaseActivity<*>) {}
    fun onResume (activity: GsBaseActivity<*>) {}
    fun onPause  (activity: GsBaseActivity<*>) {}
    fun onStop   (activity: GsBaseActivity<*>) {}
    fun onDestroy(activity: GsBaseActivity<*>) {}

    /** Activity получила новый Intent (например, NFC-метку). */
    fun onNewIntent(activity: GsBaseActivity<*>, intent: Intent) {}
}

Подключение плагина в Activity#

class MainActivity : GsBaseActivity<AppNavigator>() {

    override fun provideModules(): List<KClass<out ActivityPlugin>> =
        listOf(MyAnalyticsPlugin::class)
}
  • Тип-список — передаёте KClass; экземпляр будет лениво создан через session.getSimplePkg().
    Так удобнее, если плагин в разных модулях и нужен DI-контейнер.

Порядок инициализации#

  1. GsBaseActivity собирает StateManager и Navigator.

  2. Вызывает plugin.initializeAsync() параллельно с инициализацией StateManager’а; здесь выполняются тяжёлые операции (запуск CameraX, чтение лицензий и т.п.).

  3. UI создан — onInitializeUi() даёт плагину объект Navigator.

  4. Далее события onCreate/Start/Resume… приходят в том же порядке, что и в Activity.

Обмен данными с экраном#

  • Плагин хранит публичные MutableStateFlow / Callback-свойства — ViewModel или VCI подписывается и получает данные.

  • Если нужно уведомить только текущий экран, проще всего передать лямбду-делегат при вызове (в afterEnter), как показано в NFC-примере, либо получить плагин через сессию.

Пример NFC-плагина#

class NfcActivityPlugin : ActivityPlugin {

    private val isWriteNow = AtomicBoolean(false)
    private var adapter: NfcAdapter? = null
    var onRead: (String) -> Unit = {}

    override suspend fun initializeAsync(activity: GsBaseActivity<*>) {
        adapter = activity.getSystemService(NfcManager::class.java).defaultAdapter
        setupForegroundDispatch(activity)
    }

    override fun onResume(activity: GsBaseActivity<*>) {
        adapter?.enableForegroundDispatch(activity, pendingIntent, filters, null)
    }

    override fun onPause(activity: GsBaseActivity<*>) {
        adapter?.disableForegroundDispatch(activity)
    }

    override fun onNewIntent(activity: GsBaseActivity<*>, intent: Intent) {
        if (!isWriteNow.get()) {
            val payload = readNdef(intent)
            onRead(payload)
        }
    }

    /* остальной код — чтение, запись, шифрование */
}
  • Плагин полностью управляет NfcAdapter, не нагружая Activity.

  • В onRead передаёт данные обратно в экран (VCI) или State.

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