This SDK gives you a ready-to-use chat UI + chat core for Android (Jetpack Compose).
Part of the Ethora SDK ecosystem — see all SDKs, tools, and sample apps. Follow cross-SDK updates in the Release Notes.
Looking for a runnable sample app? See ethora-sample-android — clone, run
npx @ethora/setup, and open in Android Studio.
JitPack build page (already available): https://jitpack.io/#dappros/ethora-sdk-android/v1.0.0
Project-level settings.gradle.kts:
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven(url = "https://jitpack.io") } }App module build.gradle.kts:
dependencies { implementation("com.github.dappros:ethora-sdk-android:1.0.19") }Replace 1.0.19 with the latest tag. You can also pin to a specific commit hash instead of a tag.
The SDK uses Java 8+ date/time APIs (java.time.*). Android SDK < 26 requires desugaring — skip this and you'll get a runtime crash on older devices.
In your app module build.gradle.kts:
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 isCoreLibraryDesugaringEnabled = true // ← add this } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") // ← add this implementation("com.github.dappros:ethora-sdk-android:1.0.19") }Note:
desugar_jdk_libsversion2.1.4is the minimum required. Always check the releases page for the latest.
Use this if you do not want JitPack and want SDK sources inside your app repo.
Copy these folders from this repo into your project root:
ethora-componentchat-corechat-ui
Keep this structure so ethora-component can find shared sources:
<your-project>/ethora-component<your-project>/chat-core<your-project>/chat-ui
In your app project settings.gradle.kts:
include(":ethora-component")In app module build.gradle.kts:
dependencies { implementation(project(":ethora-component")) }Same as the JitPack install path — the SDK uses java.time.* APIs and requires desugaring on API < 26:
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 isCoreLibraryDesugaringEnabled = true // ← add this } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") // ← add this implementation(project(":ethora-component")) }In AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />The most reliable integration flow is:
- Create
ChatConfiginremember(...). - Apply config with
ChatStore.setConfig(...). - Set API URL/token with
ApiClient.setBaseUrl(...). - Render
Chat(...).
import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.fillMaxSize import com.ethora.chat.Chat import com.ethora.chat.core.config.AppConfig import com.ethora.chat.core.config.ChatConfig import com.ethora.chat.core.config.ChatHeaderSettingsConfig import com.ethora.chat.core.config.JWTLoginConfig import com.ethora.chat.core.config.XMPPSettings import com.ethora.chat.core.networking.ApiClient import com.ethora.chat.core.store.ChatStore @Composable fun EthoraChatScreen( singleRoomJid: String, dnsOverrides: Map<String, String>?, loggedInUser: com.ethora.chat.core.models.User?, hasJwtToken: Boolean ) { val appConfig = remember(loggedInUser, dnsOverrides) { ChatConfig( appId = BuildConfig.APP_ID, baseUrl = BuildConfig.API_BASE_URL, disableRooms = true, chatHeaderSettings = ChatHeaderSettingsConfig( roomTitleOverrides = mapOf(singleRoomJid to "Playground Room 1"), chatInfoButtonDisabled = true, backButtonDisabled = true ), xmppSettings = XMPPSettings( xmppServerUrl = BuildConfig.XMPP_DEV_SERVER, host = BuildConfig.XMPP_HOST, conference = BuildConfig.XMPP_CONFERENCE ), dnsFallbackOverrides = dnsOverrides, // userLogin = loggedInUser?.let { user -> // UserLoginConfig(enabled = true, user = user) // }, jwtLogin = if (hasJwtToken) { JWTLoginConfig( token = BuildConfig.USER_TOKEN, enabled = true ) } else null, defaultLogin = false, customAppToken = BuildConfig.API_TOKEN ) } ChatStore.setConfig(appConfig) ApiClient.setBaseUrl(appConfig.baseUrl ?: AppConfig.defaultBaseURL, appConfig.customAppToken) Chat( config = appConfig, roomJID = singleRoomJid, modifier = Modifier.fillMaxSize() ) }disableRooms = true+roomJIDinChat(...)gives you single-room mode.jwtLoginis used whenenabled = trueand token is provided.chatHeaderSettings.roomTitleOverrideslets you replace raw JID in header.dnsFallbackOverrideshelps when emulator DNS cannot resolve your hosts.customAppTokenis forwarded viaApiClient.setBaseUrl(...).
SDK exports a composable hook:
useUnread(maxCount: Int = 10): UnreadStateUnreadState.totalCount(Int)UnreadState.displayCount(String, for example10+)
import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox import androidx.compose.material3.Text import androidx.compose.runtime.Composable import com.ethora.chat.useUnread @Composable fun ChatTabBadge() { val unread = useUnread(maxCount = 99) BadgedBox( badge = { if (unread.totalCount > 0) { Badge { Text(unread.displayCount) } } } ) { Text("Chat") } }- The unread state comes from SDK
RoomStore. - Unread values are meaningful after chat data is loaded (the
Chat(...)flow has initialized rooms/session). - If chat is not initialized yet, unread will be
0.
./gradlew :ethora-component:assembleThis repo includes jitpack.yml and is configured for:
groupId:com.github.dapprosartifactId:ethora-sdk-android
Client dependency format:
implementation("com.github.dappros:ethora-sdk-android:1.0.19")