Перейти к основному содержимому
Версия: 5.21.0

Интеграция в Jetpack Compose

ChatCenterUI основан на Fragment/Activity и не имеет нативного Compose-API. Однако его можно встроить в Compose-граф через interop API (androidx.compose.ui.viewinterop) и AndroidFragment (требует androidx.fragment:fragment-compose 1.8+).

Холдер ChatCenterUI

Создайте singleton ChatCenterUI в Application.onCreate() или через DI (Hilt/Koin/Dagger). Один экземпляр на весь жизненный цикл приложения.

Конструктор ChatCenterUI(appContext, logger?) имеет опциональный второй параметр logger: ChatLoggerConfig? (по умолчанию null) — см. Logger config.

init() — ровно один раз

init(...) и initAsync(...) нужно вызывать ровно один раз за жизнь процесса и до любых других вызовов SDK. Повторный вызов не поддерживается и приводит к непредсказуемому состоянию SDK; см. init.md. Если вы регистрируете SDK в Application.onCreate(), оборачивайте инициализацию вашим guard'ом.

ChatCenterUI предоставляет 4 публичные перегрузки инициализации (init(provider, config), init(provider, appMarker, config), initAsync(...) × 2). Полный справочник — в init.md. Ниже в примерах используется 3-аргументная синхронная форма init(provider, appMarker, config) (как в Быстром старте).

Где взять chatConfig

Объект chatConfig (тип ChatConfig) собирается из ChatTransportConfig и ChatNetworkConfig. См. полный пример в Быстром старте и описание полей в ChatConfig. В сниппетах ниже подразумевается, что chatConfig уже создан.

import android.app.Application
import edna.chatcenter.ui.visual.core.ChatCenterUI

class MyApp : Application() {
lateinit var chatCenterUI: ChatCenterUI
private set

override fun onCreate() {
super.onCreate()
chatCenterUI = ChatCenterUI(applicationContext).apply {
init("YOUR_PROVIDER_UID", "YOUR_APP_MARKER", chatConfig)
}
}
}

Доступ из Composable — через хелпер rememberChatCenterUI().

Хелпер для получения ChatCenterUI из произвольного Composable (универсален для обоих DI-вариантов выше):

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import edna.chatcenter.ui.visual.core.ChatCenterUI

@Composable
fun rememberChatCenterUI(): ChatCenterUI {
val app = LocalContext.current.applicationContext as MyApp
return remember(app) { app.chatCenterUI }
}

Асинхронная инициализация

В Compose-приложениях, где старт SDK не должен блокировать main thread (например, конфигурация догружается с сервера), используйте initAsync. По умолчанию coroutineContext = Dispatchers.MainonInitComplete приходит на main thread, и из него безопасно вызывать UI-операции и authorize(...):

import kotlinx.coroutines.Dispatchers

override fun onCreate() {
super.onCreate()
chatCenterUI = ChatCenterUI(applicationContext)
chatCenterUI.initAsync(
providerUid = "YOUR_PROVIDER_UID",
appMarker = "YOUR_APP_MARKER",
config = chatConfig
// coroutineContext = Dispatchers.Main по умолчанию
) {
// SDK готов — можно открывать чат, вызывать authorize(...)
}
}

Если выбираете другой dispatcher (Dispatchers.Default/Dispatchers.IO) — помните: onInitComplete выполнится на том же контексте, и обращаться к UI без withContext(Dispatchers.Main) { ... } нельзя.

Полная сигнатура и параметры — init.md#асинхронная-инициализация-initasync.

Встраивание ChatFragment в Compose

Если вы используете Navigation-Compose и ChatFragment должен быть частью Compose-графа, используйте AndroidFragment (требует androidx.fragment:fragment-compose 1.8+):

Требует androidx.fragment:fragment-compose 1.8+

AndroidFragment поставляется в отдельной библиотеке androidx.fragment:fragment-compose и не входит в зависимости SDK. Добавьте её в build.gradle вашего приложения:

dependencies {
implementation "androidx.fragment:fragment-compose:1.8.0"
}

Без этой зависимости пример ниже не скомпилируется (Unresolved reference: AndroidFragment).

import androidx.compose.ui.Modifier
import androidx.fragment.compose.AndroidFragment
import edna.chatcenter.ui.visual.fragments.ChatFragment

@Composable
fun ChatComposable(modifier: Modifier = Modifier) {
AndroidFragment<ChatFragment>(modifier = modifier)
}
Получить уже созданный ChatFragment

AndroidFragment<ChatFragment> сам создаёт фрагмент в текущем FragmentManager. Для доступа к экземпляру из Composable используйте FragmentLifecycleCallbacks (см. раздел Ротация и пересоздание ниже) — это устойчиво к ротации.

Для старых версий Compose (1.7-) или если нужно встроить View-иерархию напрямую — используйте AndroidViewBinding с FragmentContainerView:

import androidx.compose.ui.viewinterop.AndroidViewBinding
import com.example.app.databinding.ChatContainerBinding

@Composable
fun ChatComposable(modifier: Modifier = Modifier) {
AndroidViewBinding(ChatContainerBinding::inflate, modifier = modifier) {
// в layout: <androidx.fragment.app.FragmentContainerView
// android:name="edna.chatcenter.ui.visual.fragments.ChatFragment"
// android:id="@+id/chat_fragment"/>
}
}
ProGuard / R8 для FragmentContainerView-варианта

FragmentContainerView инстанциирует ChatFragment рефлексивно по android:name. При включённой минификации (minifyEnabled true) R8 может переименовать класс и сломать инстанцирование. Добавьте правило, сохраняющее ChatFragment:

-keep class edna.chatcenter.ui.visual.fragments.ChatFragment {
public <init>();
}

Минимально-достаточное правило: сохраняет no-arg-конструктор, нужный FragmentContainerView для рефлексивного инстанцирования. Если ваш сценарий хранит back-stack между процессами, добавьте также <init>(android.os.Bundle);. Для AndroidFragment<ChatFragment>() без XML android:name правило не требуется — R8 видит прямую ссылку на класс.

Открытие ChatActivity из Composable

Если чат должен открываться отдельным экраном — запустите встроенную ChatActivity стандартным Intent:

import android.content.Intent
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import edna.chatcenter.ui.visual.activities.ChatActivity

@Composable
fun OpenChatButton() {
val context = LocalContext.current

Button(onClick = {
val intent = Intent(context, ChatActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
}) {
Text("Открыть чат")
}
}

Получить уже созданный экземпляр (если открыт) — через парный метод chatCenterUI.getChatActivity(): ChatActivity?. В отличие от getChatFragment() (см. предупреждение в разделе Ротация и пересоздание), getChatActivity() устойчив к системному пересозданию: внутренняя ссылка обновляется в ChatActivity.onCreate(...), который Android вызывает при каждом восстановлении Activity.

Кастомный переход

Чтобы изменить целевой экран при тапе по push-уведомлению, передайте свой PendingIntentCreator в ChatConfig — см. Расширенные настройки → Настройка перехода по Push-уведомлению.

BackHandler в Compose Navigation

ChatFragment имеет внутреннюю навигацию (поиск, просмотр изображений). При встраивании в Compose Navigation добавьте BackHandler, чтобы сначала вызвать chatFragment.onBackPressed(), и только если он вернёт true — закрыть экран:

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.compose.AndroidFragment
import edna.chatcenter.ui.visual.fragments.ChatFragment

@Composable
fun ChatScreen(onClose: () -> Unit) {
val chatFragment by rememberChatFragment()

BackHandler {
// KDoc ChatFragment.onBackPressed():
// true — чат должен быть закрыт (SDK сообщает «закрывай экран»)
// false — SDK обработал нажатие внутренне (например, закрыл поиск/preview)
val shouldClose = chatFragment?.onBackPressed() == true
if (shouldClose) onClose()
}

AndroidFragment<ChatFragment>(modifier = Modifier.fillMaxSize())
}

rememberChatFragment() определён в секции Ротация и пересоздание ниже — он использует FragmentLifecycleCallbacks и устойчив к пересозданию.

Ротация и пересоздание

AndroidFragment<ChatFragment> при ротации/config change пересоздаёт ChatFragment через no-arg конструктор.

chatCenterUI.getChatFragment() ненадёжен после ротации

В текущей версии SDK ссылка, которую возвращает getChatFragment(), обновляется только при первичном создании фрагмента через фабричный метод ChatFragment.newInstance(). При системном пересоздании фрагмента (ротация, restore) newInstance() не вызывается, поэтому getChatFragment() либо вернёт устаревшую ссылку на уничтоженный экземпляр, либо null после GC. Не полагайтесь на этот метод в Compose-сценарии с ротацией — получайте свежий экземпляр через FragmentManager.

Подпишитесь на FragmentManager.FragmentLifecycleCallbacks и держите ссылку в Compose-State:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import edna.chatcenter.ui.visual.fragments.ChatFragment

@Composable
fun rememberChatFragment(): State<ChatFragment?> {
val context = LocalContext.current
val state = remember { mutableStateOf<ChatFragment?>(null) }

DisposableEffect(context) {
val fm = (context as FragmentActivity).supportFragmentManager

// Сразу подхватываем уже-приаттаченный фрагмент (если есть)
state.value = fm.fragments.filterIsInstance<ChatFragment>().firstOrNull()

val cb = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, ctx: android.content.Context) {
if (f is ChatFragment) state.value = f
}

override fun onFragmentDetached(fm: FragmentManager, f: Fragment) {
if (f is ChatFragment) state.value = null
}
}
// recursive = true — захватывает фрагменты во вложенных FragmentManager'ах (NavHost)
fm.registerFragmentLifecycleCallbacks(cb, /* recursive = */ true)

onDispose { fm.unregisterFragmentLifecycleCallbacks(cb) }
}

return state
}

Получив State<ChatFragment?>, используйте его в BackHandler — полный пример приведён в разделе BackHandler в Compose Navigation выше.

Темы: ChatTheme vs MaterialTheme

ChatFragment стилизует свои View runtime-механизмом SDK (ChatTheme — экземпляр Kotlin-класса, который SDK читает при отрисовке вью; это не XML-android:theme и не Compose-MaterialTheme). При встраивании в Compose-граф, обёрнутый в androidx.compose.material3.MaterialTheme (M3) или androidx.compose.material.MaterialTheme (M2), цвета и типографика Compose не наследуются во вью SDK — переопределите токены SDK явно через ChatComponents/ChatFlows (см. Дизайн-система → Темы).

Обратное тоже верно: токены SDK не утекают в окружающий Compose-граф.

Известные непокрытые сценарии

Поведение SDK в следующих сценариях не проходило целевого тестирования в Compose-окружении: split-screen / multi-window, picture-in-picture, вложенные FragmentManager при работе с AndroidFragment<ChatFragment> внутри nested NavHost. При интеграции в эти сценарии запрашивайте отдельное согласование с поддержкой edna.

Связанные разделы