From 4939754de864c19baa0cb067a5f6098dbd087b37 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 8 Mar 2026 23:02:16 +0300 Subject: [PATCH] android: stabilize DI graph and production api config --- android/CHANGELOG.md | 7 +++++ android/app/build.gradle.kts | 2 +- android/app/src/main/AndroidManifest.xml | 29 ++++++++++--------- .../core/network/TokenRefreshAuthenticator.kt | 2 ++ .../auth/repository/NetworkAuthRepository.kt | 4 +-- .../chat/repository/NetworkChatRepository.kt | 4 +-- .../daemonlord/messenger/di/NetworkModule.kt | 8 +++++ .../ru/daemonlord/messenger/di/Qualifiers.kt | 8 +++++ .../messenger/ui/chats/ChatListScreen.kt | 2 ++ .../messenger/ui/chats/ChatListViewModel.kt | 3 +- 10 files changed, 48 insertions(+), 21 deletions(-) diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 2314b6f..38cc4d6 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -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. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index d39b509..2da328d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -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 { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4d6c41d..0ae81aa 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,22 +1,23 @@ - - + + - + + 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"> + android:name=".MainActivity" + android:exported="true"> - - + + diff --git a/android/app/src/main/java/ru/daemonlord/messenger/core/network/TokenRefreshAuthenticator.kt b/android/app/src/main/java/ru/daemonlord/messenger/core/network/TokenRefreshAuthenticator.kt index e245324..d7bddb9 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/core/network/TokenRefreshAuthenticator.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/core/network/TokenRefreshAuthenticator.kt @@ -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 { diff --git a/android/app/src/main/java/ru/daemonlord/messenger/data/auth/repository/NetworkAuthRepository.kt b/android/app/src/main/java/ru/daemonlord/messenger/data/auth/repository/NetworkAuthRepository.kt index e19ad2b..fe83643 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/data/auth/repository/NetworkAuthRepository.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/data/auth/repository/NetworkAuthRepository.kt @@ -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 = withContext(ioDispatcher) { diff --git a/android/app/src/main/java/ru/daemonlord/messenger/data/chat/repository/NetworkChatRepository.kt b/android/app/src/main/java/ru/daemonlord/messenger/data/chat/repository/NetworkChatRepository.kt index f1f3aa0..e3d24ca 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/data/chat/repository/NetworkChatRepository.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/data/chat/repository/NetworkChatRepository.kt @@ -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> { diff --git a/android/app/src/main/java/ru/daemonlord/messenger/di/NetworkModule.kt b/android/app/src/main/java/ru/daemonlord/messenger/di/NetworkModule.kt index e036a2e..c788f74 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/di/NetworkModule.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/di/NetworkModule.kt @@ -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( diff --git a/android/app/src/main/java/ru/daemonlord/messenger/di/Qualifiers.kt b/android/app/src/main/java/ru/daemonlord/messenger/di/Qualifiers.kt index 80c848a..288b676 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/di/Qualifiers.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/di/Qualifiers.kt @@ -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 diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt index 4e0e570..ee19c4f 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt @@ -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, diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListViewModel.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListViewModel.kt index 18688cf..d501ad2 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListViewModel.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListViewModel.kt @@ -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 ->