{"id":473567,"date":"2025-09-02T16:56:36","date_gmt":"2025-09-02T16:56:36","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=473567"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=473567","title":{"rendered":"<span>Compose Multiplatform \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 c MVI<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u043e\u0431 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 Compose \u0441 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 beta \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a<\/p>\n<h2>Gradle<\/h2>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0432 build.gradle.kts<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\">androidMain<\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">Android<\/p>\n<\/td>\n<td>\n<p align=\"left\">\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><em>commonMain<\/em><\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">\u041e\u0431\u0449\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><em>iosMain<\/em><\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">ios<\/p>\n<\/td>\n<td>\n<p align=\"left\">\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<details class=\"spoiler\">\n<summary>sourceSets<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">    sourceSets {         androidMain.dependencies {             implementation(compose.preview)             implementation(libs.androidx.activity.compose)              implementation(libs.ktor.client.android)             implementation(libs.koin.androidx.compose)              \/\/ Koin             implementation(libs.koin.android)             implementation(libs.koin.androidx.compose)          }         commonMain.dependencies {             implementation(compose.runtime)             implementation(compose.foundation)             implementation(compose.material3)             implementation(compose.ui)              implementation(compose.components.resources)             implementation(compose.components.uiToolingPreview)             implementation(compose.materialIconsExtended)              implementation(libs.androidx.lifecycle.viewmodelCompose)             implementation(libs.androidx.lifecycle.runtimeCompose)              implementation(libs.androidx.data.store.core)              implementation(libs.ktor.client.core)             implementation(libs.ktor.client.content.negotiation)             implementation(libs.ktor.serialization.kotlinx.json)             implementation(libs.androidx.room.runtime)              implementation(libs.sqlite.bundled)             implementation(libs.coil)             implementation(libs.coil.compose)             implementation(libs.coil.network)             implementation(libs.navigation.compose) \/\/            implementation(libs.screen.size)                \/\/ Koin             api(libs.koin.core)             implementation(libs.koin.compose)             implementation(libs.koin.composeVM)             implementation(libs.ktor.logging)             implementation(\"org.jetbrains.compose.ui:ui-backhandler:1.8.2\")          }          iosMain.dependencies {             implementation(libs.ktor.client.darwin)         }     } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>\u0422\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430\u0445<\/h2>\n<details class=\"spoiler\">\n<summary>MainActivity &#8212; Android<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ \u0444\u0430\u0439\u043b MainActivity.kt class MainActivity : ComponentActivity() {     override fun onCreate(savedInstanceState: Bundle?) {         enableEdgeToEdge()         super.onCreate(savedInstanceState)          setContent {             App()         }     } }   \/\/ \u0444\u0430\u0439\u043b MyApplication.kt class MyApplication : Application() {     override fun onCreate() {         super.onCreate()         initKoin {             androidContext(this@MyApplication)         }     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>iOSApp &#8212; iOs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">\/\/ \u0444\u0430\u0439\u043b App.kt  @main struct iOSApp: App {     var body: some Scene {         WindowGroup {             ContentView()         }     } }  \/\/ \u0444\u0430\u0439\u043b ContentView.swift  struct ComposeView: UIViewControllerRepresentable {     func makeUIViewController(context: Context) -&gt; UIViewController {         MainViewControllerKt.MainViewController()     }      func updateUIViewController(_ uiViewController: UIViewController, context: Context) {     } }  struct ContentView: View {     var body: some View {         ComposeView()             .ignoresSafeArea()     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u042d\u0442\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0431\u0449\u0435\u0433\u043e \u043a\u043e\u0434\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 commonMain. <\/p>\n<p>\u041e\u0447\u0435\u043d\u044c \u043f\u043e\u0440\u0430\u0434\u043e\u0432\u0430\u043b\u043e \u0447\u0442\u043e koin \u0441 \u0432\u0435\u0440\u0441\u0438\u0438 4 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 <strong>ViewModel<\/strong> \u0432 <strong>commonMain<\/strong> \u043a\u0440\u043e\u0441\u0441\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u0431\u0435\u0437 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043e\u043a iOs\/Android \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0433\u043e \u043a\u043e\u0434\u0430<\/p>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c-\u0442\u043e  \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0441\u0435\u0442\u044c \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a\u0438\u0445 \u043b\u0438\u0431\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b<\/p>\n<p>\u041d\u043e \u0434\u043b\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 <strong>Room<\/strong> \u0438 <strong>DataStore<\/strong> \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0410\u0434\u0430\u043f\u0442\u0435\u0440 Expect\/Actual<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u043e\u0441\u0442 \u043a \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f <strong>Room<\/strong> \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 <strong>expect\/actual <\/strong>\u0442\u0430\u043a:<\/p>\n<pre><code class=\"kotlin\">expect fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt;<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>RoomDatabase actual<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ iOs actual fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt; {     val dbFilePath = documentDirectory() + \"\/$DB_Name\"     return Room.databaseBuilder&lt;AppDatabase&gt;(         name = dbFilePath,     ) }  @OptIn(ExperimentalForeignApi::class) private fun documentDirectory(): String {     val documentDirectory = NSFileManager.defaultManager.URLForDirectory(         directory = NSDocumentDirectory,         inDomain = NSUserDomainMask,         appropriateForURL = null,         create = false,         error = null,     )     return requireNotNull(documentDirectory?.path) }   \/\/ Android actual fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt; {     val appContext = KoinPlatform.getKoin().get&lt;Application&gt;()     val dbFile = appContext.getDatabasePath(DB_Name)     return Room.databaseBuilder&lt;AppDatabase&gt;(         context = appContext,         name = dbFile.absolutePath     ) } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u043e\u0434\u0443\u043b\u044c koin DI \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432 commonMain <\/p>\n<details class=\"spoiler\">\n<summary>databaseModule<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">val databaseModule = module {      \/\/ database     single {         getRoomDatabase(getDatabaseBuilder())     }  }   fun getRoomDatabase(     builder: RoomDatabase.Builder&lt;AppDatabase&gt; ): AppDatabase {     return builder         .setDriver(BundledSQLiteDriver())         .setQueryCoroutineContext(DispatchersRepository.io())         .fallbackToDestructiveMigration(             dropAllTables = true         )         .build() } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0415\u0441\u0442\u044c \u043e\u0434\u0438\u043d \u043d\u044e\u0430\u043d\u0441  \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 iOs.  Dao interface \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c Flow \u0438\u043b\u0438 \u0431\u044b\u0442\u044c suspend \u0438\u043d\u0430\u0447\u0435 \u043f\u043e\u0434 iOs \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0434\u0430\u0435\u0442. <\/p>\n<details class=\"spoiler\">\n<summary>@Dao<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">@Dao interface PasswordsDao {      @Query(\"SELECT * FROM Passwords ORDER BY id\")     fun getAllPasswords(): Flow&lt;List&lt;PasswordsEntity&gt;&gt;      @Query(\"SELECT * FROM Passwords WHERE name LIKE '%' || :filter || '%' ORDER BY id\")     fun getFilteredPasswords(filter: String): Flow&lt;List&lt;PasswordsEntity&gt;&gt;      @Query(\"SELECT * FROM Passwords WHERE id = :id\")     suspend fun getPasswords(id: String): PasswordsEntity      @Query(\"SELECT COUNT(*) as count FROM Passwords\")     suspend fun count(): Int      @Query(\"SELECT id FROM Passwords ORDER BY id DESC\")     suspend fun getMaxId(): String      @Insert(onConflict = OnConflictStrategy.REPLACE)     suspend fun insertAllPasswords(passwords: List&lt;PasswordsEntity&gt;)      @Insert(onConflict = OnConflictStrategy.REPLACE)    suspend fun insertPassword(password: PasswordsEntity)      @Update(onConflict = OnConflictStrategy.IGNORE)     suspend fun updatePassword(password: PasswordsEntity)      @Delete     suspend fun deletePassword(password: PasswordsEntity)  } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f <strong>DataStore<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f AppPreferences. \u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f Theme.<\/p>\n<details class=\"spoiler\">\n<summary>AppPreferences<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class AppPreferences(     private val dataStore: DataStore&lt;Preferences&gt; ) {      private val themeKey = stringPreferencesKey(\"com.spacex\/theme\")       suspend fun getTheme() = dataStore.data.map { preferences -&gt;         preferences[themeKey] ?: Const.Theme.DARK_MODE.name     }.first()      suspend fun changeThemeMode(value: String) = dataStore.edit { preferences -&gt;         preferences[themeKey] = value     }  } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>Clean Architecture<\/h2>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/498\/e4d\/522\/498e4d5228047e84443b307e198fc3d1.png\" alt=\"Clean Architecture\" title=\"Clean Architecture\" width=\"482\" height=\"680\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/498\/e4d\/522\/498e4d5228047e84443b307e198fc3d1.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/498\/e4d\/522\/498e4d5228047e84443b307e198fc3d1.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Clean Architecture<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0430\u043f\u043e\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 commonMain \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a. \u0415\u0441\u043b\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 module \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437 \u043c\u0435\u043d\u044e File-&gt;New-&gt;Module&#8230;-&gt;Android-&gt;Kotlin multiplatform shared module<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0432\u044f\u0437\u044c \u043c\u043e\u0434\u0443\u043b\u044f \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c Android \u0438 iOS<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\"># Android  dependencies {     ...     implementation(project(\":shared\")) }  # iOS  val xcfName = \"sharedKit\"  iosX64 {   binaries.framework {     baseName = xcfName   } }  iosArm64 {   binaries.framework {     baseName = xcfName   } }  iosSimulatorArm64 {   binaries.framework {     baseName = xcfName   } }<\/code><\/pre>\n<p><a href=\"https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate<\/a><\/p>\n<\/div>\n<\/details>\n<h2>Settings<\/h2>\n<figure class=\"bordered full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f97\/592\/343\/f975923430a52009e81a9a7726c3af58.png\" alt=\"Theme\" title=\"Theme\" width=\"2160\" height=\"800\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/f97\/592\/343\/f975923430a52009e81a9a7726c3af58.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f97\/592\/343\/f975923430a52009e81a9a7726c3af58.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Theme<\/figcaption><\/div>\n<\/figure>\n<p>Theme \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 Settings. Koin \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043f\u0438\u0439 viewmodel, \u0430 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0442\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u0447\u0435\u043a\u0431\u043e\u043a\u0441 \u0432 Settings. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f SettingsViewModel \u0432 App() \u0438 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0435\u0435 \u043d\u0438\u0436\u0435 \u0434\u043e \u0441\u0430\u043c\u043e\u0433\u043e SettingsScreen. \u0422\u0430\u043c \u0436\u0435 \u0432 App() \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043c\u0430 \u0441\u043c. PasswordsTheme-&gt;MaterialTheme \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0438\u043b\u0438 \u043f\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e \u0447\u0435\u043a\u0431\u043e\u043a\u0441 \u0432 \u044d\u043a\u0440\u0430\u043d\u0435 Settings<\/p>\n<details class=\"spoiler\">\n<summary>fun App()<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">fun App() {      val settingViewModel = koinViewModel&lt;SettingsViewModel&gt;()     val currentTheme by settingViewModel.viewState.collectAsStateWithLifecycle()     PasswordsTheme(currentTheme.currentTheme) {         NavigationApplication(settingViewModel)     }  }  @Composable fun PasswordsTheme(     appTheme: String?,     darkTheme: Boolean = isSystemInDarkTheme(),     content: @Composable () -&gt; Unit ) {     val colorScheme = when (appTheme) {         Const.Theme.LIGHT_MODE.name -&gt; {             LightColorScheme         }          Const.Theme.DARK_MODE.name -&gt; {             DarkColorScheme         }          else -&gt; {             if (darkTheme) {                 DarkColorScheme             } else {                 LightColorScheme             }         }     }      MaterialTheme(         colorScheme = colorScheme,         content = content,         typography = CustomTypography()     ) } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>MVI<\/h2>\n<p><a href=\"https:\/\/developer.android.com\/topic\/architecture\/ui-layer\/events#handle-viewmodel-events\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f<\/a> <strong>MVVM<\/strong> \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 val uiState: StateFlow \u0432\u043e viewModel \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432\u043e View (Activity, Fragment, Compose fun). \u0418\u0437 \u044d\u0442\u043e\u0433\u043e View \u0434\u0435\u0440\u0433\u0430\u044e\u0442\u0441\u044f \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b viewModel. View \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043a\u043e\u043b\u043b\u0435\u043a\u0442\u0438\u0442 \u044d\u0442\u043e\u0442 uiState<\/p>\n<p><strong>MVI  <\/strong>\u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0412\u0441\u0435 \u043c\u0435\u0442\u043e\u0434\u044b viewModel \u043d\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435, \u043a\u0440\u043e\u043c\u0435 \u043e\u0434\u043d\u043e\u0433\u043e <strong>handleEvent(Event)<\/strong>. \u041e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a viewModel \u0438\u0434\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u0442\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0435 Events. \u0415\u0449\u0435 \u0438\u0445 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 Intents (\u041d\u0430\u043c\u0435\u0440\u0435\u043d\u0438\u044f) \u0442\u043e \u0447\u0442\u043e \u043c\u044b \u043d\u0430\u043c\u0435\u0440\u0435\u0432\u0430\u0435\u043c\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u043e viewModel. \u042d\u0442\u043e \u0437\u0430\u043c\u0435\u043d\u0430 \u0434\u0435\u0440\u0433\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434 \u0432 viewModel. switch\/case \u043a\u0430\u043a \u0440\u0430\u0437 \u0438 \u0434\u0435\u0440\u043d\u0435\u0442 \u044d\u0442\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043e\u0433\u0434\u0430 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u0442\/\u0440\u0430\u0437\u0431\u0435\u0440\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 Event<\/p>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f Effect \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0442\u0430\u043a \u0436\u0435 \u043a\u0430\u043a \u0438 uiState \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d View. Effect \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043d\u044f\u0442\u0438\u044f \u0422\u043e\u0441\u0442\u043e\u0432 \u0438 \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438, \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 uiState. \u041f\u0440\u0438 \u0447\u0435\u043c \u043e\u043d <em>SharedFlow<\/em><\/p>\n<p>\u0418\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u0432\u0435 &#171;\u0442\u0440\u0443\u0431\u044b&#187; \u0438\u0437 viewModel \u043a View (uiState \u0438 Effects) \u0438 \u043e\u0434\u0438\u043d  \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 <strong>handleEvent(Event)<\/strong> \u0432\u043e viewModel<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 BaseViewModel. \u0412 \u043d\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c uiState, Event \u0438 Effect<\/p>\n<pre><code class=\"kotlin\">abstract class BaseViewModel&lt;Event : ViewEvent, UiState : ViewState, Effect : ViewSideEffect&gt;     (initUiState: UiState) : ViewModel() <\/code><\/pre>\n<details class=\"spoiler\">\n<summary>BaseViewModel<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">package com.storage.passwords.utils  import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.spacex.utils.UiText import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import passwords.composeapp.generated.resources.Res import passwords.composeapp.generated.resources.unknown_error   interface ViewEvent  interface ViewState  interface ViewSideEffect  abstract class BaseViewModel&lt;Event : ViewEvent, UiState : ViewState, Effect : ViewSideEffect&gt;     (initUiState: UiState) : ViewModel() {      val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception: Throwable -&gt;         viewModelScope.launch {             onCoroutineException(                 if (exception.message != null)                     UiText.StaticString(exception.message!!)                 else                     UiText.StringResource(Res.string.unknown_error)             )         }     }      abstract fun onCoroutineException(message: UiText)      val defaultViewModelScope = CoroutineScope(SupervisorJob() + coroutineExceptionHandler)      abstract fun runInitialEvent()     abstract fun handleEvents(event: Event)      private val _viewState: MutableStateFlow&lt;UiState&gt; = MutableStateFlow(initUiState)      val viewState = _viewState         .onStart {             runInitialEvent()         }         .stateIn(             scope = viewModelScope,             started = SharingStarted.WhileSubscribed(5000),             initialValue = _viewState.value         )      private val _event: MutableSharedFlow&lt;Event&gt; = MutableSharedFlow()      private val _effect = MutableSharedFlow&lt;Effect&gt;()     val effect = _effect.asSharedFlow()       init {         subscribeToEvents()     }      private fun subscribeToEvents() {         defaultViewModelScope.launch {             _event.collect {                 handleEvents(it)             }         }     }      fun setEvent(event: Event) {         defaultViewModelScope.launch { _event.emit(event) }     }      protected fun setState(reducer: UiState.() -&gt; UiState) {         val newState = viewState.value.reducer()         _viewState.value = newState     }      protected fun setEffect(builder: () -&gt; Effect) {         val effectValue = builder()         defaultViewModelScope.launch { _effect.emit(effectValue) }     }   }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412\u0437\u044f\u0442\u043e \u043e\u0442\u0441\u044e\u0434\u0430 <a href=\"https:\/\/github.com\/wellingtoncabral\/android-compose-mvi-navigation\" rel=\"noopener noreferrer nofollow\">android-compose-mvi-navigation<\/a>. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e uiState \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043b <strong>\u0421oroutineExceptionHandler<\/strong><\/p>\n<pre><code class=\"kotlin\">    val viewState = _viewState         .onStart {             runInitialEvent()         }         .stateIn(             scope = viewModelScope,             started = SharingStarted.WhileSubscribed(5000),             initialValue = _viewState.value         ) <\/code><\/pre>\n<p>\u0412\u043e \u043f\u0435\u0440\u0432\u044b\u0445 runInitialEvent() \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 5 \u0441\u0435\u043a \u0435\u0441\u043b\u0438 \u0431\u044b\u043b\u0430 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437-\u0437\u0430 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c\u0443 \u0446\u0438\u043a\u043b\u0443 \u0441\u043c. \u043d\u0438\u0436\u0435 <strong>Lifecycle.State.STARTED<\/strong><\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0448\u043b\u043e \u0432 \u0444\u043e\u043d \u0438 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u0447\u0435\u0440\u0435\u0437 5 \u0441\u0435\u043a \u0432\u0435\u0440\u043d\u0443\u043b\u043e\u0441\u044c. \u0412\u043e \u0432\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430  \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u044d\u043a\u0440\u0430\u043d, \u0447\u0435\u0440\u0435\u0437 5 \u0441\u0435\u043a \u0442\u0430\u043a \u0436\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0435\u0442 \u043f\u0440\u0438\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 flow, \u0430 \u043f\u0440\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d runInitialEvent(). \u042d\u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u043a\u043e\u0433\u0434\u0430 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0441 \u044d\u043a\u0440\u0430\u043d\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0435\u0440\u0435\u0448\u043b\u0438 \u043d\u0430  \u044d\u043a\u0440\u0430\u043d \u0433\u0434\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043d\u0430\u0434\u043e \u0447\u0442\u043e\u0431\u044b \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043f\u0440\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0438. init {}  \u0432\u043e viewModel \u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442! \u041d\u043e \u043e\u043f\u044f\u0442\u044c \u0436\u0435 \u043d\u0430\u0434\u043e \u043f\u0440\u043e\u0431\u044b\u0442\u044c \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f\/\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 5 \u0441\u0435\u043a \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 WhileSubscribed(t)<\/p>\n<h2>Collect<\/h2>\n<p>val state = viewModel.viewState.collectAsStateWithLifecycle()<\/p>\n<p><strong>StateFlow<\/strong> compose multiplatform \u0443\u043c\u0435\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441 \u0443\u0447\u0435\u0442\u043e\u043c \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438. \u0410 <strong>SharedFlow<\/strong> \u043d\u0435\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f Efects \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434 \u0434\u043b\u044f \u0443\u0447\u0435\u0442\u0430 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430<\/p>\n<pre><code class=\"kotlin\">    val effect = viewModel.effect         .flowWithLifecycle(             localLifecycleOwner.lifecycle,             Lifecycle.State.STARTED         )      LaunchedEffect(key1 = localLifecycleOwner.lifecycle) {         effect.collect { ...<\/code><\/pre>\n<\/p>\n<h2>UiText<\/h2>\n<p>\u0415\u0449\u0435 \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043e\u0434\u043d\u043e \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u043e. \u0427\u0430\u0441\u0442\u043e \u0438\u0437 viewModel \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 \u0438\u043b\u0438 \u0441\u0430\u043c\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 \u0432\u043e View. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u0435\u0440\u0442\u043a\u0443 \u043f\u0440\u0438\u0447\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u0441\u044f \u043c\u043e\u0436\u0435\u0442 \u043a\u0430\u043a \u0432 <strong>coroutine<\/strong> \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0442\u0430\u043a \u0438 \u043d\u0435\u0442<\/p>\n<details class=\"spoiler\">\n<summary>UiText<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">sealed interface UiText {     data class StaticString(val value: String) : UiText     class StringResource(         val resId: org.jetbrains.compose.resources.StringResource,         vararg val args: Any     ) : UiText      @Composable     fun asString(): String {         return when (this) {             is StaticString -&gt; value             is StringResource -&gt; stringResource(resId, *args)         }     }     suspend fun asStringForSuspend(): String {         return when (this) {             is StaticString -&gt; value             is StringResource -&gt; getString(resId, *args)         }     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<h2>Server<\/h2>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 IntelliJ IDEA \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043e\u043f\u0446\u0438\u044e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<details class=\"spoiler\">\n<summary>embeddedServer<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">package com.storage.passwords  import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.request.receive import io.ktor.server.response.* import io.ktor.server.routing.*  fun main() {     embeddedServer(Netty, port = SERVER_PORT, host = \"0.0.0.0\", module = Application::module)         .start(wait = true) }  fun Application.module() {     routing {         get(\"\/\") {             call.respondText(\"Ktor: ${Greeting().greet()}\")         }          get(\"\/passwords\") {             call.response.headers.append(                 HttpHeaders.ContentType,                 ContentType.Application.Json.toString()             )             call.respondText(                 \"\"\"                     [                      { \"id\":\"1\", \"name\":\"password1\", \"password\":\"ADAD%ADAD\", \"note\":\"I note it\"},                     { \"id\":\"2\", \"name\":\"password2\", \"password\":\"1ADAD%ADAD\", \"note\":\"I note it eee\"},                     { \"id\":\"3\", \"name\":\"password3\", \"password\":\"2ADAD%ADAD\", \"note\":\"I note it fff\"}                     ]                     \"\"\".trimMargin()             )         }          get(\"\/submit\") {             val receivedData = call.receive&lt;String&gt;() \/\/ Assuming plain text data              \/\/ Process the received data             println(\"Received POST data: $receivedData\")              \/\/ Send a response back to the client             call.respondText(\"Data received successfully!\")         }          post(\"\/submit-password\") {             \/\/ Receive the data from the request body             val receivedData = call.receive&lt;String&gt;() \/\/ Assuming plain text data              \/\/ Process the received data             println(\"Received POST data: $receivedData\")              \/\/ Send a response back to the client             call.respondText(\"Data received successfully!\")         }      } }<\/code><\/pre>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/564\/241\/997\/564241997b6fbdcdda71da7ea91ce33e.png\" alt=\"feed\" title=\"feed\" width=\"818\" height=\"792\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/564\/241\/997\/564241997b6fbdcdda71da7ea91ce33e.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/564\/241\/997\/564241997b6fbdcdda71da7ea91ce33e.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>feed<\/figcaption><\/div>\n<\/figure>\n<\/div>\n<\/details>\n<p> \u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0440\u043e\u0443\u0442\u043e\u0432 get \u0438 post \u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c<\/p>\n<pre><code class=\"kotlin\">get(\"\/submit\") {           get(\"\/passwords\") {             call.response.headers.append(                 HttpHeaders.ContentType,                 ContentType.Application.Json.toString()             )             call.respondText(\"....\")   ...  post(\"\/submit-password\") {   ...<\/code><\/pre>\n<p>Android \u042d\u043c\u0443\u043b\u044f\u0442\u043e\u0440 \u043d\u0435 \u0432\u0438\u0434\u0438\u0442 \u0430\u0434\u0440\u0435\u0441 <a href=\"http:\/\/0.0.0.0:8080\/\" rel=\"noopener noreferrer nofollow\">http:\/\/0.0.0.0:8080\/<\/a> \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043a\u0430\u043a \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c ifconfig \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0432\u0438\u0434\u0430 192.168.1.100<\/p>\n<p>\u041f\u0440\u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0432 ConfigRepository. BuildKonfig.Is_Debug_Server \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u0438 \u0441\u0431\u043e\u0440\u043a\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/p>\n<details class=\"spoiler\">\n<summary>ConfigRepository<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class ConfigRepository {      private val isDebugBuild = BuildKonfig.Is_Debug_Server      fun getBaseUrl(): String {         return if (isDebugBuild)             BASE_URL_DEBUG         else             BASE_URL_RELEASE     }      companion object Companion {         private const val BASE_URL_RELEASE = \"https:\/\/0.0.0.0:8080\"         private const val BASE_URL_DEBUG = \"http:\/\/192.168.1.215:8080\"  \/\/        private const val BASE_URL_DEBUG = \"http:\/\/192.168.231.7:8080\"     }  }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0434 iOs \u0442\u0430\u043a \u0436\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c. \u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0443\u0431\u0440\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d NSExceptionAllowsInsecureHTTPLoads<\/p>\n<details class=\"spoiler\">\n<summary>ios plist file for Netty Server<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"xml\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt; &lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST 1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt; &lt;plist version=\"1.0\"&gt;     &lt;dict&gt;         &lt;key&gt;CADisableMinimumFrameDurationOnPhone&lt;\/key&gt;         &lt;true\/&gt;         &lt;key&gt;NSAppTransportSecurity&lt;\/key&gt;         &lt;dict&gt;             &lt;key&gt;NSExceptionDomains&lt;\/key&gt;             &lt;dict&gt;                 &lt;key&gt;localhost&lt;\/key&gt;                 &lt;dict&gt;                     &lt;key&gt;NSExceptionAllowsInsecureHTTPLoads&lt;\/key&gt;                     &lt;true\/&gt;                 &lt;\/dict&gt;             &lt;\/dict&gt;         &lt;\/dict&gt;     &lt;\/dict&gt; &lt;\/plist&gt;<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u043b\u044f iOs \u0442\u0430\u043a \u0436\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c xcode \u0438 \u0432\u044b\u0431\u0440\u0430\u0442\u044c team<\/p>\n<blockquote>\n<p>In Xcode, under the &#171;Signing &amp; Capabilities&#187; tab of an app target, a specific development team must be selected<\/p>\n<\/blockquote>\n<h2>\u0413\u043b\u0430\u0437<\/h2>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/01b\/75a\/0a9\/01b75a0a9c56d0682d4e5168c326dee0.png\" width=\"2160\" height=\"800\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/01b\/75a\/0a9\/01b75a0a9c56d0682d4e5168c326dee0.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/01b\/75a\/0a9\/01b75a0a9c56d0682d4e5168c326dee0.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0415\u0449\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0445\u0430\u043a \u043f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435 \u043f\u043e\u043b\u044f \u043f\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044e \u043d\u0430 \u0433\u043b\u0430\u0437. \u041f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u0433\u043b\u0430\u0437\u0430 \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0432\u0430\u0435\u0448\u044c\u0441\u044f \u043d\u0430 Detail. \u041f\u0440\u0438\u0447\u0435\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0442 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b<\/p>\n<pre><code class=\"kotlin\">.clickable {     if (timeLeft &lt;= 0) {               onClick.invoke(passwordItem)    \/\/ \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 Detail     }},<\/code><\/pre>\n<p>\u0418 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043a\u0430\u043a-\u0442\u043e \u043f\u0440\u043e\u0449\u0435?<\/p>\n<details class=\"spoiler\">\n<summary>delay(100)<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">    var showPassword by remember { mutableStateOf(false) }       val navAllow = derivedStateOf { !showPassword }      LaunchedEffect(key1 = navAllow.value) {         while (timeLeft &gt; 0) {             delay(100)             timeLeft--         }     }    ...                 \/\/ \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0438\u043b\u0438 \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043a\u0438                     Text(                         modifier = Modifier                             .padding(top = 4.dp, start = 24.dp),                         text = if (showPassword) passwordItem.password else \"*********\"                     )    ...                  \/\/ \u041f\u043e\u043a\u0430 \u0436\u043c\u0435\u043c \u043d\u0430 \u0438\u043a\u043e\u043d\u043a\u0443 \u0432\u0438\u0434\u0438\u043c \u043f\u0430\u0440\u043e\u043b\u044c                    Icon(                     modifier = Modifier.pointerInput(Unit) {                         awaitEachGesture {                             val down = awaitFirstDown()                             \/\/ Handle the down event                             showPassword = true                              do {                                 val event = awaitPointerEvent()                             } while (event.changes.any { it.pressed })                              showPassword = false                             timeLeft = 1                         }                     },                     imageVector = if (showPassword) Icons.Filled.Visibility else Icons.Outlined.Visibility,                     contentDescription = \"\"                 )  <\/code><\/pre>\n<\/div>\n<\/details>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/35e\/c24\/d6f\/35ec24d6fefa0bb62e28786e38bff8a6.gif\" alt=\"\u0413\u043b\u0430\u0437\" title=\"\u0413\u043b\u0430\u0437\" width=\"400\" height=\"889\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/35e\/c24\/d6f\/35ec24d6fefa0bb62e28786e38bff8a6.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/35e\/c24\/d6f\/35ec24d6fefa0bb62e28786e38bff8a6.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0413\u043b\u0430\u0437<\/figcaption><\/div>\n<\/figure>\n<h2>\u041c\u0435\u043d\u044e<\/h2>\n<p>\u041c\u0435\u043d\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0431\u0443\u0440\u0433\u0435\u0440 \u043c\u0435\u043d\u044e. <strong>BurgerMenu<\/strong> \u044d\u0442\u043e \u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 <strong>ModalNavigationDrawer<\/strong><\/p>\n<details class=\"spoiler\">\n<summary>BurgerMenu<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">    BurgerMenu(         drawerState = drawerState,         onAddItem = {             navController.currentBackStackEntry?.savedStateHandle?.apply {                 val jsonFalconInfo = Json.encodeToString(\"-1\")                 set(PASSWORD_ID_PARAM, jsonFalconInfo)             }             navController.navigate(Screen.Detail.route)         },         onAboutItem = {             navController.navigate(Screen.About.route)         },         onSettingsItem = {             navController.navigate(Screen.Settings.route)         }     ) {           NavHost(           ...   @Composable fun BurgerMenu(     onAboutItem: () -&gt; Unit,     onAddItem: () -&gt; Unit,     onSettingsItem: () -&gt; Unit,     drawerState: DrawerState,     content: @Composable () -&gt; Unit ) {      val scope = rememberCoroutineScope()      ModalNavigationDrawer(         drawerState = drawerState, \/\/        gesturesEnabled = drawerState.isOpen,         drawerContent = {             ModalDrawerSheet {                 Text(\"Menu\", modifier = Modifier.padding(16.dp))                 HorizontalDivider()                 NavigationDrawerItem(                     label = {                         Text(text = stringResource(Res.string.about))                     },                     selected = false,                     onClick = {                         onAboutItem.invoke()                         scope.launch { drawerState.close() }                     }                 )                 NavigationDrawerItem(                     label = {                         Text(text = stringResource(Res.string.title_settings))                     },                     selected = false,                     onClick = {                         onSettingsItem.invoke()                         scope.launch { drawerState.close() }                     }                 )                 NavigationDrawerItem(                     label = {                         Text(text = stringResource(Res.string.add_password))                     },                     selected = false,                     onClick = {                         onAddItem.invoke()                         scope.launch { drawerState.close() }                     }                 )             }         }     ) {             \/\/ Main screen content             content()     } }            <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>Navigation<\/h2>\n<p>\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 <strong>NavHost<\/strong> org.jetbrains.androidx.navigation:navigation-compose navigationCompose = &#171;<em>2.9.0-beta05<\/em>&#171;. \u041d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f navigationCompose = &#171;<em>2.9.0-rc01<\/em>&#187; \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u0430\u0433\u043e\u0432<\/p>\n<details class=\"spoiler\">\n<summary>routes<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">sealed class Screen(val route: String) {      object Home : Screen(\"home\")     object Detail : Screen(\"detail\")     object About : Screen(\"about\")     object Settings : Screen(\"settings\") }  ...           NavHost(             navController = navController,             startDestination = Screen.Home.route         ) {             composable(Screen.Home.route) {                 PasswordsScreen(                     drawerState = drawerState,                     currentItem = { password_id -&gt;                         navController.currentBackStackEntry?.savedStateHandle?.apply {                             val jsonFalconInfo = Json.encodeToString(password_id)                             set(PASSWORD_ID_PARAM, jsonFalconInfo)                         }                         navController.navigate(Screen.Detail.route)                     }                  )             }             composable(Screen.About.route) {                 AboutScreen(                     onStartClick = {                         navController.popBackStack()                     }                 )             }             composable(                 route = Screen.Detail.route,             ) {                 navController.previousBackStackEntry?.savedStateHandle?.get&lt;String&gt;(PASSWORD_ID_PARAM)                     ?.let { jsonId -&gt;                         val password_id = Json.decodeFromString&lt;String&gt;(jsonId)                         DetailScreen(                             password_id = password_id,                             onBackHandler = {                                 navController.popBackStack()                             }                         )                     }             }             composable(Screen.Settings.route) {                 SettingsScreen(                     viewModel = settingViewModel,                     {                         navController.popBackStack()                     }                 )             }             <\/code><\/pre>\n<\/div>\n<\/details>\n<p>P.S.<\/p>\n<p>OutlinedTextField &#8212; \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 iOs. \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0434\u0430\u0435\u0442 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u044f. \u0415\u0441\u0442\u044c \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e \u0437\u0430\u043c\u0435\u043d\u0435 \u043d\u0430 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u0438\u043b\u0438 \u0436\u0434\u0430\u0442\u044c \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 Compose<\/p>\n<details class=\"spoiler\">\n<summary>\u0415\u0449\u0435 \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440 \u043d\u043e \u043f\u0438\u0448\u0443\u0442 \u0447\u0442\u043e \u0440\u0435\u0448\u0438\u043b\u0438<\/summary>\n<div class=\"spoiler__content\">\n<p><a href=\"https:\/\/youtrack.jetbrains.com\/projects\/CMP\/issues\/CMP-8764\/iOS-Application-crashed-when-Touch-the-OutlinedTextField\" rel=\"noopener noreferrer nofollow\">https:\/\/youtrack.jetbrains.com\/projects\/CMP\/issues\/CMP-8764\/iOS-Application-crashed-when-Touch-the-OutlinedTextField<\/a><\/p>\n<p>Can confirm I no longer replicate this issue with<\/p>\n<pre><code class=\"cpp\">androidx-lifecycle = \"2.9.3\" androidx-navigation = \"2.9.0-rc01\"<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0411\u044b\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0435\u0439 \u043d\u0430\u0437\u0430\u0434 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Gesture, \u043d\u043e \u0432\u0440\u043e\u0434\u0435 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0443\u0431\u0440\u0430\u0442\u044c \u0445\u0430\u043a BackHandler<\/p>\n<details class=\"spoiler\">\n<summary>BackHandler<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">    BackHandler(enabled = true) {         println(\"BackHandler\")         viewModel.setEvent(DetailEvent.NavigationBack)     } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u043a\u043e\u0434 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043d\u0430 Github<\/p>\n<p><a href=\"https:\/\/github.com\/app-z\/Passwords\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/app-z\/Passwords<\/a><\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0441\u044b\u043b\u043a\u0438 \u043f\u043e \u0442\u0435\u043c\u0435<\/summary>\n<div class=\"spoiler__content\">\n<p><a href=\"https:\/\/developer.android.com\/topic\/architecture\/ui-layer\/events#handle-viewmodel-events\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/topic\/architecture\/ui-layer\/events#handle-viewmodel-events<\/a><\/p>\n<p><a href=\"https:\/\/developer.android.com\/topic\/architecture\/recommendations\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/topic\/architecture\/recommendations<\/a><\/p>\n<p><a href=\"https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate<\/a><\/p>\n<p><a href=\"https:\/\/developer.android.com\/kotlin\/multiplatform\/datastore\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/kotlin\/multiplatform\/datastore<\/a><\/p>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/941196\/\"> https:\/\/habr.com\/ru\/articles\/941196\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u043e\u0431 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 Compose \u0441 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 beta \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a<\/p>\n<h2>Gradle<\/h2>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0432 build.gradle.kts<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\">androidMain<\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">Android<\/p>\n<\/td>\n<td>\n<p align=\"left\">\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><em>commonMain<\/em><\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">\u041e\u0431\u0449\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><em>iosMain<\/em><\/p>\n<\/td>\n<td data-colwidth=\"234\" width=\"234\">\n<p align=\"left\">ios<\/p>\n<\/td>\n<td>\n<p align=\"left\">\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<details class=\"spoiler\">\n<summary>sourceSets<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">    sourceSets {         androidMain.dependencies {             implementation(compose.preview)             implementation(libs.androidx.activity.compose)              implementation(libs.ktor.client.android)             implementation(libs.koin.androidx.compose)              \/\/ Koin             implementation(libs.koin.android)             implementation(libs.koin.androidx.compose)          }         commonMain.dependencies {             implementation(compose.runtime)             implementation(compose.foundation)             implementation(compose.material3)             implementation(compose.ui)              implementation(compose.components.resources)             implementation(compose.components.uiToolingPreview)             implementation(compose.materialIconsExtended)              implementation(libs.androidx.lifecycle.viewmodelCompose)             implementation(libs.androidx.lifecycle.runtimeCompose)              implementation(libs.androidx.data.store.core)              implementation(libs.ktor.client.core)             implementation(libs.ktor.client.content.negotiation)             implementation(libs.ktor.serialization.kotlinx.json)             implementation(libs.androidx.room.runtime)              implementation(libs.sqlite.bundled)             implementation(libs.coil)             implementation(libs.coil.compose)             implementation(libs.coil.network)             implementation(libs.navigation.compose) \/\/            implementation(libs.screen.size)                \/\/ Koin             api(libs.koin.core)             implementation(libs.koin.compose)             implementation(libs.koin.composeVM)             implementation(libs.ktor.logging)             implementation(\"org.jetbrains.compose.ui:ui-backhandler:1.8.2\")          }          iosMain.dependencies {             implementation(libs.ktor.client.darwin)         }     } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>\u0422\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430\u0445<\/h2>\n<details class=\"spoiler\">\n<summary>MainActivity &#8212; Android<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ \u0444\u0430\u0439\u043b MainActivity.kt class MainActivity : ComponentActivity() {     override fun onCreate(savedInstanceState: Bundle?) {         enableEdgeToEdge()         super.onCreate(savedInstanceState)          setContent {             App()         }     } }   \/\/ \u0444\u0430\u0439\u043b MyApplication.kt class MyApplication : Application() {     override fun onCreate() {         super.onCreate()         initKoin {             androidContext(this@MyApplication)         }     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>iOSApp &#8212; iOs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">\/\/ \u0444\u0430\u0439\u043b App.kt  @main struct iOSApp: App {     var body: some Scene {         WindowGroup {             ContentView()         }     } }  \/\/ \u0444\u0430\u0439\u043b ContentView.swift  struct ComposeView: UIViewControllerRepresentable {     func makeUIViewController(context: Context) -&gt; UIViewController {         MainViewControllerKt.MainViewController()     }      func updateUIViewController(_ uiViewController: UIViewController, context: Context) {     } }  struct ContentView: View {     var body: some View {         ComposeView()             .ignoresSafeArea()     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u042d\u0442\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0431\u0449\u0435\u0433\u043e \u043a\u043e\u0434\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 commonMain. <\/p>\n<p>\u041e\u0447\u0435\u043d\u044c \u043f\u043e\u0440\u0430\u0434\u043e\u0432\u0430\u043b\u043e \u0447\u0442\u043e koin \u0441 \u0432\u0435\u0440\u0441\u0438\u0438 4 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 <strong>ViewModel<\/strong> \u0432 <strong>commonMain<\/strong> \u043a\u0440\u043e\u0441\u0441\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u0431\u0435\u0437 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043e\u043a iOs\/Android \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0433\u043e \u043a\u043e\u0434\u0430<\/p>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c-\u0442\u043e  \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0441\u0435\u0442\u044c \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a\u0438\u0445 \u043b\u0438\u0431\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b<\/p>\n<p>\u041d\u043e \u0434\u043b\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 <strong>Room<\/strong> \u0438 <strong>DataStore<\/strong> \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0410\u0434\u0430\u043f\u0442\u0435\u0440 Expect\/Actual<\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u043e\u0441\u0442 \u043a \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f <strong>Room<\/strong> \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 <strong>expect\/actual <\/strong>\u0442\u0430\u043a:<\/p>\n<pre><code class=\"kotlin\">expect fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt;<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>RoomDatabase actual<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ iOs actual fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt; {     val dbFilePath = documentDirectory() + \"\/$DB_Name\"     return Room.databaseBuilder&lt;AppDatabase&gt;(         name = dbFilePath,     ) }  @OptIn(ExperimentalForeignApi::class) private fun documentDirectory(): String {     val documentDirectory = NSFileManager.defaultManager.URLForDirectory(         directory = NSDocumentDirectory,         inDomain = NSUserDomainMask,         appropriateForURL = null,         create = false,         error = null,     )     return requireNotNull(documentDirectory?.path) }   \/\/ Android actual fun getDatabaseBuilder(): RoomDatabase.Builder&lt;AppDatabase&gt; {     val appContext = KoinPlatform.getKoin().get&lt;Application&gt;()     val dbFile = appContext.getDatabasePath(DB_Name)     return Room.databaseBuilder&lt;AppDatabase&gt;(         context = appContext,         name = dbFile.absolutePath     ) } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u043e\u0434\u0443\u043b\u044c koin DI \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432 commonMain <\/p>\n<details class=\"spoiler\">\n<summary>databaseModule<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">val databaseModule = module {      \/\/ database     single {         getRoomDatabase(getDatabaseBuilder())     }  }   fun getRoomDatabase(     builder: RoomDatabase.Builder&lt;AppDatabase&gt; ): AppDatabase {     return builder         .setDriver(BundledSQLiteDriver())         .setQueryCoroutineContext(DispatchersRepository.io())         .fallbackToDestructiveMigration(             dropAllTables = true         )         .build() } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0415\u0441\u0442\u044c \u043e\u0434\u0438\u043d \u043d\u044e\u0430\u043d\u0441  \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 iOs.  Dao interface \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c Flow \u0438\u043b\u0438 \u0431\u044b\u0442\u044c suspend \u0438\u043d\u0430\u0447\u0435 \u043f\u043e\u0434 iOs \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0430\u0434\u0430\u0435\u0442. <\/p>\n<details class=\"spoiler\">\n<summary>@Dao<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">@Dao interface PasswordsDao {      @Query(\"SELECT * FROM Passwords ORDER BY id\")     fun getAllPasswords(): Flow&lt;List&lt;PasswordsEntity&gt;&gt;      @Query(\"SELECT * FROM Passwords WHERE name LIKE '%' || :filter || '%' ORDER BY id\")     fun getFilteredPasswords(filter: String): Flow&lt;List&lt;PasswordsEntity&gt;&gt;      @Query(\"SELECT * FROM Passwords WHERE id = :id\")     suspend fun getPasswords(id: String): PasswordsEntity      @Query(\"SELECT COUNT(*) as count FROM Passwords\")     suspend fun count(): Int      @Query(\"SELECT id FROM Passwords ORDER BY id DESC\")     suspend fun getMaxId(): String      @Insert(onConflict = OnConflictStrategy.REPLACE)     suspend fun insertAllPasswords(passwords: List&lt;PasswordsEntity&gt;)      @Insert(onConflict = OnConflictStrategy.REPLACE)    suspend fun insertPassword(password: PasswordsEntity)      @Update(onConflict = OnConflictStrategy.IGNORE)     suspend fun updatePassword(password: PasswordsEntity)      @Delete     suspend fun deletePassword(password: PasswordsEntity)  } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f <strong>DataStore<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f AppPreferences. \u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f Theme.<\/p>\n<details class=\"spoiler\">\n<summary>AppPreferences<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class AppPreferences(     private val dataStore: DataStore&lt;Preferences&gt; ) {      private val themeKey = stringPreferencesKey(\"com.spacex\/theme\")       suspend fun getTheme() = dataStore.data.map { preferences -&gt;         preferences[themeKey] ?: Const.Theme.DARK_MODE.name     }.first()      suspend fun changeThemeMode(value: String) = dataStore.edit { preferences -&gt;         preferences[themeKey] = value     }  } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>Clean Architecture<\/h2>\n<figure class=\"\">\n<div><figcaption>Clean Architecture<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0430\u043f\u043e\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 commonMain \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a. \u0415\u0441\u043b\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 module \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437 \u043c\u0435\u043d\u044e File-&gt;New-&gt;Module&#8230;-&gt;Android-&gt;Kotlin multiplatform shared module<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0432\u044f\u0437\u044c \u043c\u043e\u0434\u0443\u043b\u044f \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c Android \u0438 iOS<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\"># Android  dependencies {     ...     implementation(project(\":shared\")) }  # iOS  val xcfName = \"sharedKit\"  iosX64 {   binaries.framework {     baseName = xcfName   } }  iosArm64 {   binaries.framework {     baseName = xcfName   } }  iosSimulatorArm64 {   binaries.framework {     baseName = xcfName   } }<\/code><\/pre>\n<p><a href=\"https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate\" rel=\"noopener noreferrer nofollow\">https:\/\/developer.android.com\/kotlin\/multiplatform\/migrate<\/a><\/p>\n<\/div>\n<\/details>\n<h2>Settings<\/h2>\n<figure class=\"bordered full-width\">\n<div><figcaption>Theme<\/figcaption><\/div>\n<\/figure>\n<p>Theme \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 Settings. Koin \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043f\u0438\u0439 viewmodel, \u0430 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0442\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u0447\u0435\u043a\u0431\u043e\u043a\u0441 \u0432 Settings. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f SettingsViewModel \u0432 App() \u0438 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0435\u0435 \u043d\u0438\u0436\u0435 \u0434\u043e \u0441\u0430\u043c\u043e\u0433\u043e SettingsScreen. \u0422\u0430\u043c \u0436\u0435 \u0432 App() \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043c\u0430 \u0441\u043c. PasswordsTheme-&gt;MaterialTheme \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0438\u043b\u0438 \u043f\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e \u0447\u0435\u043a\u0431\u043e\u043a\u0441 \u0432 \u044d\u043a\u0440\u0430\u043d\u0435 Settings<\/p>\n<details class=\"spoiler\">\n<summary>fun App()<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">fun App() {      val settingViewModel = koinViewModel&lt;SettingsViewModel&gt;()     val currentTheme by settingViewModel.viewState.collectAsStateWithLifecycle()     PasswordsTheme(currentTheme.currentTheme) {         NavigationApplication(settingViewModel)     }  }  @Composable fun PasswordsTheme(     appTheme: String?,     darkTheme: Boolean = isSystemInDarkTheme(),     content: @Composable () -&gt; Unit ) {     val colorScheme = when (appTheme) {         Const.Theme.LIGHT_MODE.name -&gt; {             LightColorScheme         }          Const.Theme.DARK_MODE.name -&gt; {             DarkColorScheme         }          else -&gt; {             if (darkTheme) {                 DarkColorScheme             } else {                 LightColorScheme             }         }     }      MaterialTheme(         colorScheme = colorScheme,         content = content,         typography = CustomTypography()     ) } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>MVI<\/h2>\n<p><a href=\"https:\/\/developer.android.com\/topic\/architecture\/ui-layer\/events#handle-viewmodel-events\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f<\/a> <strong>MVVM<\/strong> \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 val uiState: StateFlow \u0432\u043e viewModel \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432\u043e View (Activity, Fragment, Compose fun). \u0418\u0437 \u044d\u0442\u043e\u0433\u043e View \u0434\u0435\u0440\u0433\u0430\u044e\u0442\u0441\u044f \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b viewModel. View \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043a\u043e\u043b\u043b\u0435\u043a\u0442\u0438\u0442 \u044d\u0442\u043e\u0442 uiState<\/p>\n<p><strong>MVI  <\/strong>\u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0412\u0441\u0435 \u043c\u0435\u0442\u043e\u0434\u044b viewModel \u043d\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435, \u043a\u0440\u043e\u043c\u0435 \u043e\u0434\u043d\u043e\u0433\u043e <strong>handleEvent(Event)<\/strong>. \u041e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a viewModel \u0438\u0434\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u0442\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0435 Events. \u0415\u0449\u0435 \u0438\u0445 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 Intents (\u041d\u0430\u043c\u0435\u0440\u0435\u043d\u0438\u044f) \u0442\u043e \u0447\u0442\u043e \u043c\u044b \u043d\u0430\u043c\u0435\u0440\u0435\u0432\u0430\u0435\u043c\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u043e viewModel. \u042d\u0442\u043e \u0437\u0430\u043c\u0435\u043d\u0430 \u0434\u0435\u0440\u0433\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434 \u0432 viewModel. switch\/case \u043a\u0430\u043a \u0440\u0430\u0437 \u0438 \u0434\u0435\u0440\u043d\u0435\u0442 \u044d\u0442\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043e\u0433\u0434\u0430 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u0442\/\u0440\u0430\u0437\u0431\u0435\u0440\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 Event<\/p>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f Effect \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0442\u0430\u043a \u0436\u0435 \u043a\u0430\u043a \u0438 uiState \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d View. Effect \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043d\u044f\u0442\u0438\u044f \u0422\u043e\u0441\u0442\u043e\u0432 \u0438 \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438, \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 uiState. \u041f\u0440\u0438 \u0447\u0435\u043c \u043e\u043d <em>SharedFlow<\/em><\/p>\n<p>\u0418\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u0432\u0435 &#171;\u0442\u0440\u0443\u0431\u044b&#187; \u0438\u0437 viewModel \u043a View (uiState \u0438 Effects) \u0438 \u043e\u0434\u0438\u043d  \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 <strong>handleEvent(Event)<\/strong> \u0432\u043e viewModel<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 BaseViewModel. \u0412 \u043d\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c uiState, Event \u0438 Effect<\/p>\n<pre><code class=\"kotlin\">abstract class BaseViewModel&lt;Event : ViewEvent, UiState : ViewState, Effect : ViewSideEffect&gt;<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-473567","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/473567","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=473567"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/473567\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=473567"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=473567"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=473567"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}