android: stabilize DI graph and production api config
Some checks are pending
CI / test (push) Has started running

This commit is contained in:
Codex
2026-03-08 23:02:16 +03:00
parent 9d842c1d88
commit 4939754de8
10 changed files with 48 additions and 21 deletions

View File

@@ -57,3 +57,10 @@
- Added unit test for realtime event parsing (`RealtimeEventParserTest`).
- Added DAO test (`ChatDaoTest`) using in-memory Room + Robolectric.
- Updated Android checklist status in `docs/android-checklist.md`.
### Step 10 - Build stabilization fixes
- Switched Android API base URL to `https://chat.daemonlord.ru/`.
- Added cleartext traffic flag in manifest for local/dev compatibility.
- Fixed Hilt dependency cycle by separating refresh `AuthApiService` with a dedicated qualifier.
- Added `CoroutineDispatcher` DI provider and qualifier for repositories.
- Fixed Material3 experimental API opt-in and removed deprecated `StateFlow.distinctUntilChanged()` usage.

View File

@@ -17,7 +17,7 @@ android {
targetSdk = 35
versionCode = 1
versionName = "0.1.0"
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:8000/\"")
buildConfigField("String", "API_BASE_URL", "\"https://chat.daemonlord.ru/\"")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -1,22 +1,23 @@
<?xml version=1.0 encoding=utf-8?>
<manifest xmlns:android=http://schemas.android.com/apk/res/android>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name=android.permission.INTERNET />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=.MessengerApplication
android:allowBackup=true
android:icon=@android:drawable/sym_def_app_icon
android:label=@string/app_name
android:roundIcon=@android:drawable/sym_def_app_icon
android:supportsRtl=true
android:theme=@android:style/Theme.Material.Light.NoActionBar>
android:name=".MessengerApplication"
android:allowBackup="true"
android:icon="@android:drawable/sym_def_app_icon"
android:label="@string/app_name"
android:roundIcon="@android:drawable/sym_def_app_icon"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity
android:name=.MainActivity
android:exported=true>
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name=android.intent.action.MAIN />
<category android:name=android.intent.category.LAUNCHER />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

View File

@@ -9,6 +9,7 @@ import ru.daemonlord.messenger.core.token.TokenBundle
import ru.daemonlord.messenger.core.token.TokenRepository
import ru.daemonlord.messenger.data.auth.api.AuthApiService
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
import ru.daemonlord.messenger.di.RefreshAuthApi
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
@@ -16,6 +17,7 @@ import javax.inject.Singleton
@Singleton
class TokenRefreshAuthenticator @Inject constructor(
private val tokenRepository: TokenRepository,
@RefreshAuthApi
private val refreshAuthApiService: AuthApiService,
) : Authenticator {

View File

@@ -1,7 +1,6 @@
package ru.daemonlord.messenger.data.auth.repository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import ru.daemonlord.messenger.core.token.TokenBundle
@@ -10,6 +9,7 @@ import ru.daemonlord.messenger.data.auth.api.AuthApiService
import ru.daemonlord.messenger.data.auth.dto.AuthUserDto
import ru.daemonlord.messenger.data.auth.dto.LoginRequestDto
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
import ru.daemonlord.messenger.di.IoDispatcher
import ru.daemonlord.messenger.domain.auth.model.AuthUser
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
import ru.daemonlord.messenger.domain.common.AppError
@@ -22,7 +22,7 @@ import javax.inject.Singleton
class NetworkAuthRepository @Inject constructor(
private val authApiService: AuthApiService,
private val tokenRepository: TokenRepository,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : AuthRepository {
override suspend fun login(email: String, password: String): AppResult<AuthUser> = withContext(ioDispatcher) {

View File

@@ -1,7 +1,6 @@
package ru.daemonlord.messenger.data.chat.repository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.channelFlow
@@ -14,6 +13,7 @@ import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
import ru.daemonlord.messenger.data.chat.mapper.toDomain
import ru.daemonlord.messenger.data.chat.mapper.toUserShortEntityOrNull
import ru.daemonlord.messenger.di.IoDispatcher
import ru.daemonlord.messenger.domain.chat.model.ChatItem
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
import ru.daemonlord.messenger.domain.common.AppError
@@ -26,7 +26,7 @@ import javax.inject.Singleton
class NetworkChatRepository @Inject constructor(
private val chatApiService: ChatApiService,
private val chatDao: ChatDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ChatRepository {
override fun observeChats(archived: Boolean): Flow<List<ChatItem>> {

View File

@@ -4,6 +4,8 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@@ -60,6 +62,7 @@ object NetworkModule {
@Provides
@Singleton
@RefreshAuthApi
fun provideRefreshApiService(
@RefreshClient refreshClient: OkHttpClient,
json: Json,
@@ -73,6 +76,11 @@ object NetworkModule {
.create(AuthApiService::class.java)
}
@Provides
@Singleton
@IoDispatcher
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
@Singleton
fun provideApiClient(

View File

@@ -5,3 +5,11 @@ import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RefreshClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RefreshAuthApi
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher

View File

@@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.AssistChip
import androidx.compose.material3.AssistChipDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Tab
@@ -45,6 +46,7 @@ fun ChatListRoute(
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun ChatListScreen(
state: ChatListUiState,
onTabSelected: (ChatTab) -> Unit,

View File

@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -59,7 +58,7 @@ class ChatListViewModel @Inject constructor(
.flatMapLatest { tab ->
observeChatsUseCase(archived = tab == ChatTab.ARCHIVED)
}
.combine(searchQuery.distinctUntilChanged()) { chats, query ->
.combine(searchQuery) { chats, query ->
chats.filterByQuery(query)
}
.collectLatest { filtered ->