TestDispatcherの調査
This commit is contained in:
parent
60bb5f7e7a
commit
b2b47e730a
|
@ -1,5 +1,5 @@
|
|||
apply plugin: 'java-library'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: "java-library"
|
||||
apply plugin: "kotlin"
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
@ -18,7 +18,7 @@ compileKotlin {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
//noinspection DifferentStdlibGradleVersion
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
|
@ -20,7 +20,7 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,6 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api project(':apng')
|
||||
api project(":apng")
|
||||
implementation project(":base")
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ apply plugin: "io.gitlab.arturbosch.detekt"
|
|||
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import jp.juggler.util.log.LogCategory
|
|||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.network.toFormRequestBody
|
||||
import jp.juggler.util.network.toPost
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val log = LogCategory("Action_Tag")
|
||||
|
|
|
@ -43,6 +43,7 @@ import jp.juggler.util.log.showToast
|
|||
import jp.juggler.util.ui.activity
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.createColoredDrawable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.anko.backgroundColor
|
||||
import java.lang.ref.WeakReference
|
||||
|
|
|
@ -84,7 +84,7 @@ class PollingWorker2(
|
|||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
).await()
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import jp.juggler.util.network.toPostRequestBuilder
|
|||
import jp.juggler.util.network.toPut
|
||||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
|
@ -21,6 +21,7 @@ import jp.juggler.util.network.MEDIA_TYPE_JSON
|
|||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Request
|
||||
|
|
|
@ -113,16 +113,16 @@
|
|||
<color name="Mastodon_colorConversationMainTootBg">#2000a2ff</color>
|
||||
|
||||
<color name="Mastodon_colorLink">#FE4E92D6</color>
|
||||
<color name="Mastodon_colorListItemDrag">#AA444444</color>
|
||||
<color name="Mastodon_colorListItemDrag">#AA2F6091</color>
|
||||
<color name="Mastodon_colorPostFormBackground">#222</color>
|
||||
<color name="Mastodon_colorActionBarBg">#333</color>
|
||||
<color name="Mastodon_colorActionBarBgStacked">#222</color>
|
||||
<color name="Mastodon_colorActionBarBg">#444B5D</color>
|
||||
<color name="Mastodon_colorActionBarBgStacked">#444B5D</color>
|
||||
<color name="Mastodon_colorStatusBarBg">#444B5D</color>
|
||||
<color name="Mastodon_colorProfileBackgroundMask">#C0000000</color>
|
||||
<color name="Mastodon_colorRefreshErrorBg">#D222</color>
|
||||
<color name="Mastodon_colorRegexFilterError">#f00</color>
|
||||
<color name="Mastodon_colorReplyBackground">#333</color>
|
||||
<color name="Mastodon_colorRippleEffect">#777</color>
|
||||
<color name="Mastodon_colorRippleEffect">#C1607ECC</color>
|
||||
<color name="Mastodon_colorSearchFormBackground">#333370</color>
|
||||
<color name="Mastodon_colorSettingDivider">#66FFFFFF</color><!-- ダイアログ背景が#424242なので、それより明るくないといけない -->
|
||||
<color name="Mastodon_colorShowMediaBackground">#222</color>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id "com.android.library"
|
||||
id "org.jetbrains.kotlin.android"
|
||||
|
||||
// apply plugin: 'kotlin-android'
|
||||
// apply plugin: 'kotlin-kapt'
|
||||
// apply plugin: "kotlin-android"
|
||||
// apply plugin: "kotlin-kapt"
|
||||
// import java.text.SimpleDateFormat
|
||||
// apply plugin: 'com.android.application'
|
||||
// apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
||||
// apply plugin: 'com.google.gms.google-services'
|
||||
// apply plugin: "com.android.application"
|
||||
// apply plugin: "org.jetbrains.kotlin.plugin.serialization"
|
||||
// apply plugin: "com.google.gms.google-services"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'jp.juggler.base'
|
||||
namespace "jp.juggler.base"
|
||||
compileSdk compile_sdk_version
|
||||
|
||||
defaultConfig {
|
||||
|
@ -25,7 +25,7 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,132 +46,52 @@ dependencies {
|
|||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
|
||||
def emoji2Version = "1.2.0"
|
||||
api "androidx.emoji2:emoji2:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views-helper:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-bundled:$emoji2Version"
|
||||
|
||||
api "androidx.appcompat:appcompat:$appcompat_version"
|
||||
|
||||
api 'androidx.core:core-ktx:1.9.0'
|
||||
api 'com.google.android.material:material:1.7.0'
|
||||
|
||||
|
||||
api "androidx.core:core-ktx:1.9.0"
|
||||
|
||||
|
||||
// DrawerLayout
|
||||
api "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
|
||||
// NavigationView
|
||||
api "com.google.android.material:material:1.7.0"
|
||||
|
||||
// CustomTabs
|
||||
api "androidx.browser:browser:1.4.0"
|
||||
|
||||
// Recyclerview
|
||||
api "androidx.recyclerview:recyclerview:1.2.1"
|
||||
|
||||
|
||||
api "androidx.exifinterface:exifinterface:1.3.5"
|
||||
|
||||
|
||||
api "androidx.core:core-ktx:1.9.0"
|
||||
|
||||
api "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
api "androidx.emoji2:emoji2-bundled:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views-helper:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2:$emoji2Version"
|
||||
api "androidx.exifinterface:exifinterface:1.3.5"
|
||||
api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-service:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
api "androidx.recyclerview:recyclerview:1.2.1"
|
||||
api "androidx.room:room-ktx:$roomVersion"
|
||||
api "androidx.room:room-runtime:$roomVersion"
|
||||
api "androidx.startup:startup-runtime:$startup_version"
|
||||
api "androidx.work:work-runtime-ktx:$workVersion"
|
||||
api "androidx.work:work-runtime:$workVersion"
|
||||
api "com.astuetz:pagerslidingtabstrip:1.0.1"
|
||||
api "com.caverock:androidsvg-aar:1.4"
|
||||
api "com.github.hadilq:live-event:1.3.0"
|
||||
api "com.github.kenglxn.QRGen:android:2.5.0"
|
||||
api "com.github.omadahealth:swipy:1.2.3@aar"
|
||||
api "com.github.woxthebox:draglistview:1.6.6"
|
||||
api "com.google.android.exoplayer:exoplayer:2.18.2"
|
||||
api "com.google.android.flexbox:flexbox:3.0.0"
|
||||
api "com.google.android.material:material:1.7.0"
|
||||
api "com.google.firebase:firebase-messaging:23.1.1"
|
||||
api "com.otaliastudios:transcoder:0.10.4"
|
||||
api "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
|
||||
api "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||
api "io.github.inflationx:calligraphy3:3.1.1"
|
||||
api "io.github.inflationx:viewpump:2.0.3"
|
||||
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
||||
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
|
||||
// ViewModel
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
|
||||
// LiveData
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// Saved state module for ViewModel
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
// if using Java8, use the following instead of lifecycle-compiler
|
||||
api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
// optional - helpers for implementing LifecycleOwner in a Service
|
||||
api "androidx.lifecycle:lifecycle-service:$lifecycle_version"
|
||||
|
||||
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
|
||||
api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
|
||||
// optional - ReactiveStreams support for LiveData
|
||||
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
|
||||
|
||||
|
||||
api "androidx.startup:startup-runtime:$startup_version"
|
||||
|
||||
// video transcoder https://github.com/natario1/Transcoder
|
||||
api "com.otaliastudios:transcoder:0.10.4"
|
||||
|
||||
api 'io.github.inflationx:calligraphy3:3.1.1'
|
||||
api 'io.github.inflationx:viewpump:2.0.3'
|
||||
|
||||
api "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||
api "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
|
||||
api "ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0"
|
||||
|
||||
|
||||
api 'com.github.woxthebox:draglistview:1.6.6'
|
||||
api 'com.github.omadahealth:swipy:1.2.3@aar'
|
||||
api 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||
api "com.google.android.flexbox:flexbox:3.0.0"
|
||||
|
||||
api 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
|
||||
api 'com.caverock:androidsvg-aar:1.4'
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
// ViewModel
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
|
||||
//noinspection KtxExtensionAvailable
|
||||
//api "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
|
||||
|
||||
// LiveData
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// Saved state module for ViewModel
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
// if using Java8, use the following instead of lifecycle-compiler
|
||||
api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
// optional - helpers for implementing LifecycleOwner in a Service
|
||||
api "androidx.lifecycle:lifecycle-service:$lifecycle_version"
|
||||
|
||||
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
|
||||
api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
|
||||
// optional - ReactiveStreams support for LiveData
|
||||
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
|
||||
|
||||
api 'androidx.work:work-runtime-ktx:2.8.0-rc01'
|
||||
api "androidx.room:room-runtime:$roomVersion"
|
||||
api "androidx.room:room-ktx:$roomVersion"
|
||||
|
||||
api 'com.google.android.exoplayer:exoplayer:2.18.2'
|
||||
/*
|
||||
WARNING: [Processor] Library '…\exoplayer-ui-2.12.0.aar' contains references to both AndroidX and old support library. This seems like the library is partially migrated. Jetifier will try to rewrite the library anyway.
|
||||
Example of androidX reference: 'androidx/core/app/NotificationCompat$Builder'
|
||||
Example of support library reference: 'android/support/v4/media/session/MediaSessionCompat$Token'
|
||||
…expPlayerも苦労してるんだなあ…
|
||||
*/
|
||||
// LiveEvent
|
||||
api "com.github.hadilq:live-event:1.3.0"
|
||||
|
||||
|
||||
api "androidx.work:work-runtime:$workVersion"
|
||||
api "androidx.work:work-runtime-ktx:$workVersion"
|
||||
api "androidx.startup:startup-runtime:$startup_version"
|
||||
|
||||
// Koin main features for Android
|
||||
api "io.insert-koin:koin-android:$koin_version"
|
||||
api "io.insert-koin:koin-android-compat:$koin_version"
|
||||
|
@ -179,54 +99,35 @@ dependencies {
|
|||
// api "io.insert-koin:koin-androidx-navigation:$koin_version"
|
||||
// api "io.insert-koin:koin-androidx-compose:$koin_version"
|
||||
|
||||
|
||||
// https://firebase.google.com/support/release-notes/android
|
||||
api "com.google.firebase:firebase-messaging:23.1.1"
|
||||
|
||||
api "com.github.bumptech.glide:glide:$glideVersion"
|
||||
api "com.github.bumptech.glide:annotations:$glideVersion"
|
||||
api("com.github.bumptech.glide:okhttp3-integration:$glideVersion") {
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
exclude group: "com.squareup.okhttp3", module: "okhttp"
|
||||
}
|
||||
|
||||
// しばらくはkotlin-testとjunitを併用
|
||||
testApi "androidx.arch.core:core-testing:$arch_version"
|
||||
testApi "junit:junit:$junit_version"
|
||||
testApi "org.jetbrains.kotlin:kotlin-test"
|
||||
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
|
||||
// optional - Test helpers for LiveData
|
||||
testApi "androidx.arch.core:core-testing:$arch_version"
|
||||
|
||||
// optional - Test helpers for LiveData
|
||||
testApi "androidx.arch.core:core-testing:$arch_version"
|
||||
androidTestApi "androidx.test.espresso:espresso-core:3.5.1"
|
||||
androidTestApi "androidx.test.ext:junit:1.1.5"
|
||||
androidTestApi "androidx.test:core:$androidx_test_version"
|
||||
androidTestApi "org.jetbrains.kotlin:kotlin-test"
|
||||
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
|
||||
testApi("com.squareup.okhttp3:mockwebserver:$okhttpVersion") {
|
||||
exclude group: 'com.squareup.okio', module: 'okio'
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
|
||||
exclude group: "com.squareup.okio", module: "okio"
|
||||
exclude group: "com.squareup.okhttp3", module: "okhttp"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-common"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
|
||||
}
|
||||
|
||||
androidTestApi 'androidx.test.ext:junit:1.1.5'
|
||||
|
||||
// targetSdkVersion 31 で androidTest 時に android:exported 云々で怒られる問題の対策
|
||||
// https://github.com/android/android-test/issues/1022
|
||||
androidTestApi "androidx.test:core:$androidx_test_version"
|
||||
|
||||
|
||||
androidTestApi 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
// androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-alpha4', {
|
||||
// exclude group: 'com.android.support', module: 'support-annotations'
|
||||
// })
|
||||
// androidTestApi('androidx.test.espresso:espresso-core:3.1.0-alpha4', {
|
||||
// exclude group: 'com.android.support', module: 'support-annotations'
|
||||
// })
|
||||
|
||||
androidTestApi("com.squareup.okhttp3:mockwebserver:$okhttpVersion") {
|
||||
exclude group: 'com.squareup.okio', module: 'okio'
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
|
||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
|
||||
exclude group: "com.squareup.okio", module: "okio"
|
||||
exclude group: "com.squareup.okhttp3", module: "okhttp"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-common"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package jp.juggler.base
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* kotlinx.coroutines.test の使い方の説明
|
||||
* https://developer.android.com/kotlin/coroutines/test?hl=ja#testdispatchers
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DispatchersTest {
|
||||
|
||||
// 単純なリポジトリ
|
||||
private class UserRepository {
|
||||
val names = ArrayList<String>()
|
||||
fun register(name: String) = names.add(name)
|
||||
fun getAllUsers(): List<String> = names
|
||||
}
|
||||
|
||||
// Dispatcherを受け取るリポジトリ
|
||||
private class Repository(
|
||||
private val ioDispatcher: CoroutineDispatcher = AppDispatchers.io,
|
||||
) {
|
||||
private val ioScope = CoroutineScope(ioDispatcher)
|
||||
val initialized = AtomicBoolean(false)
|
||||
|
||||
// A function that starts a new coroutine on the IO dispatcher
|
||||
fun initializeAsync() = ioScope.async {
|
||||
delay(100L)
|
||||
initialized.set(true)
|
||||
}
|
||||
|
||||
// A suspending function that switches to the IO dispatcher
|
||||
suspend fun fetchData(): String = withContext(ioDispatcher) {
|
||||
require(initialized.get()) { "Repository should be initialized first" }
|
||||
delay(500L)
|
||||
"Hello world"
|
||||
}
|
||||
}
|
||||
|
||||
//================================================================
|
||||
|
||||
// テスト毎に書くと複数テストで衝突するので、MainDispatcherRuleに任せる
|
||||
// プロパティは記述順に初期化されることに注意
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
// スケジューラを共有するリポジトリ
|
||||
private val repository = Repository(mainDispatcherRule.testDispatcher)
|
||||
|
||||
//====================================================
|
||||
// テストでの suspend 関数の呼び出し
|
||||
// runTestを使う
|
||||
|
||||
private suspend fun fetchData(): String {
|
||||
delay(1000L)
|
||||
return "Hello world"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun useRunTest() = runTest {
|
||||
assertEquals("Hello world", fetchData())
|
||||
}
|
||||
|
||||
//====================================================
|
||||
// launch内部の処理を待つテストコード
|
||||
|
||||
@Test
|
||||
fun useAdvanceUntilIdle() = runTest {
|
||||
val userRepo = UserRepository()
|
||||
launch { userRepo.register("Alice") }
|
||||
launch { userRepo.register("Bob") }
|
||||
advanceUntilIdle() // Yields to perform the registrations
|
||||
assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
|
||||
}
|
||||
|
||||
//==============================================
|
||||
// UnconfinedTestDispatcher を使うとlaunch内部が先に実行開始する
|
||||
// ただしlaunch内部で非同期待機が入ると外側の実行が再開される
|
||||
|
||||
@Test
|
||||
fun useUnconfinedTestDispatcher() = runTest(UnconfinedTestDispatcher()) {
|
||||
val userRepo = UserRepository()
|
||||
launch { userRepo.register("Alice") }
|
||||
launch { userRepo.register("Bob") }
|
||||
assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// viewModelScopeなどが使うディスパッチャーを差し替える
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
private val _message = MutableStateFlow("")
|
||||
val message: StateFlow<String> get() = _message
|
||||
|
||||
fun loadMessage() {
|
||||
viewModelScope.launch {
|
||||
_message.value = "Greetings!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun useDispatchersSetMain() = runTest {
|
||||
// MainDispatcherRule を指定しているので、viewModelが使う Dispatcher が変わる
|
||||
val viewModel = HomeViewModel()
|
||||
viewModel.loadMessage()
|
||||
assertEquals("Greetings!", viewModel.message.value)
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// リポジトリクラスにDispatcherを渡せるようにする
|
||||
|
||||
@Test
|
||||
fun useRepoWithTestDispatcher() = runTest {
|
||||
val repository = Repository(
|
||||
ioDispatcher = StandardTestDispatcher(testScheduler)
|
||||
)
|
||||
repository.initializeAsync().await()
|
||||
assertEquals(true, repository.initialized.get())
|
||||
assertEquals("Hello world", repository.fetchData())
|
||||
}
|
||||
|
||||
//=======================================================
|
||||
// プロパティ間でスケジューラを共有する
|
||||
|
||||
@Test
|
||||
fun someRepositoryTest() = runTest {
|
||||
// Takes scheduler from Main
|
||||
|
||||
// Any TestDispatcher created here also takes the scheduler from Main
|
||||
// val newTestDispatcher = StandardTestDispatcher()
|
||||
|
||||
// これもStandardTestDispatcher を作成する
|
||||
// 注意: 独自の TestScope を作成する場合は、テスト内のそのスコープで runTest を呼び出す必要があります。
|
||||
// テストには TestScope インスタンスを 1 つだけ含めることができます。
|
||||
// val testScope = TestScope()
|
||||
|
||||
repository.initializeAsync().await()
|
||||
assertEquals(true, repository.initialized.get())
|
||||
assertEquals("Hello world", repository.fetchData())
|
||||
}
|
||||
|
||||
//=======================================================
|
||||
// DI
|
||||
// クラス内に以下のようなプロパティを定義しておくこともできる。
|
||||
// DIする際は参考になるかもしれない。
|
||||
// val testScheduler = TestCoroutineScheduler()
|
||||
// val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
// val testScope = TestScope(testDispatcher)
|
||||
//
|
||||
// fun xxx() = testScope.runTest{ ... }
|
||||
}
|
|
@ -2,6 +2,9 @@ package jp.juggler.base
|
|||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import jp.juggler.base.JugglerBase.Companion.jugglerBase
|
||||
import jp.juggler.base.JugglerBase.Companion.jugglerBaseNullable
|
||||
import jp.juggler.base.JugglerBase.Companion.prepareJugglerBase
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -14,11 +17,12 @@ import org.junit.Assert.*
|
|||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
class JugglerBaseTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
fun initializeJubblerBase() {
|
||||
assertNotNull("JubblerBase is initialized for a test.", jugglerBaseNullable)
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("jp.juggler.base.test", appContext.packageName)
|
||||
appContext.prepareJugglerBase
|
||||
assertNotNull( "JubblerBase is initialized after prepare.",jugglerBase)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package jp.juggler.base
|
||||
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
|
||||
/**
|
||||
* Dispatchers.Main のテスト中の置き換えを複数テストで衝突しないようにルール化する
|
||||
* https://developer.android.com/kotlin/coroutines/test?hl=ja
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class MainDispatcherRule(
|
||||
/**
|
||||
* UnconfinedTestDispatcher か StandardTestDispatcher のどちらかを指定する
|
||||
*/
|
||||
val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
|
||||
) : TestWatcher() {
|
||||
override fun starting(description: Description) {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
AppDispatchers.setTest(testDispatcher)
|
||||
}
|
||||
|
||||
override fun finished(description: Description) {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package jp.juggler.base
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.startup.AppInitializer
|
||||
import androidx.startup.Initializer
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
||||
/**
|
||||
* AndroidManifest.xml の指定により、
|
||||
* ApplicationのonCreate()より前に実行される。
|
||||
*/
|
||||
class JugglerBaseInitializer : Initializer<JugglerBase> {
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
override fun create(context: Context) =
|
||||
JugglerBase(context.applicationContext)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class JugglerBase(
|
||||
var context: Context,
|
||||
) {
|
||||
companion object {
|
||||
private val log = LogCategory("JugglerBase")
|
||||
|
||||
/**
|
||||
* 最後に作成したインスタンス
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
var jugglerBaseNullable: JugglerBase? = null
|
||||
|
||||
val jugglerBase get() = jugglerBaseNullable!!
|
||||
|
||||
/**
|
||||
* JugglerBaseのインスタンスを androidx.startup.AppInitializer から取得する
|
||||
* 遅延初期化を行う場合、Contextが必要になる
|
||||
*/
|
||||
val Context.prepareJugglerBase: JugglerBase
|
||||
get() = jugglerBaseNullable
|
||||
?: AppInitializer.getInstance(applicationContext)
|
||||
.initializeComponent(JugglerBaseInitializer::class.java)
|
||||
}
|
||||
|
||||
init {
|
||||
jugglerBaseNullable = this
|
||||
log.i("ctor")
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package jp.juggler.base
|
||||
|
||||
import android.content.Context
|
||||
import androidx.startup.Initializer
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
||||
class JugglerBaseInitializer : Initializer<Boolean> {
|
||||
companion object {
|
||||
private val log = LogCategory("JugglerBaseInitializer")
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
|
||||
override fun create(context: Context): Boolean {
|
||||
log.i("create")
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -3,10 +3,34 @@ package jp.juggler.util.coroutine
|
|||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* Test時にdispatcherを差し替えられるようにする
|
||||
*
|
||||
* https://developer.android.com/kotlin/coroutines/test?hl=ja#testdispatchers
|
||||
* - TestDispatcher やrunTest を使う
|
||||
* - Dispatchers.setMain(testDispatcher) や Dispatchers.resetMain() でMainを切り替えられる
|
||||
* - viewModelScope.launch{} などが使うMainを切り替えられる
|
||||
*
|
||||
* リポジトリクラスの引数に CoroutineDispatcherを渡すとかもある
|
||||
*/
|
||||
object AppDispatchers {
|
||||
|
||||
// Main と Main.immediate は Dispatchers.setMain 差し替えられる
|
||||
val mainImmediate get() = Dispatchers.Main.immediate
|
||||
|
||||
var unconfined: CoroutineDispatcher = Dispatchers.Unconfined
|
||||
var default: CoroutineDispatcher = Dispatchers.Default
|
||||
var io: CoroutineDispatcher = Dispatchers.IO
|
||||
var main: CoroutineDispatcher = Dispatchers.Main
|
||||
var mainImmediate: CoroutineDispatcher = Dispatchers.Main.immediate
|
||||
|
||||
fun reset() {
|
||||
unconfined = Dispatchers.Unconfined
|
||||
default = Dispatchers.Default
|
||||
io = Dispatchers.IO
|
||||
}
|
||||
|
||||
fun setTest(testDispatcher: CoroutineDispatcher) {
|
||||
unconfined = testDispatcher
|
||||
default = testDispatcher
|
||||
io = testDispatcher
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.util.coroutine
|
|||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
|
38
build.gradle
38
build.gradle
|
@ -1,5 +1,3 @@
|
|||
import io.gitlab.arturbosch.detekt.Detekt
|
||||
|
||||
buildscript {
|
||||
|
||||
ext.jvm_target = "1.8"
|
||||
|
@ -13,7 +11,7 @@ buildscript {
|
|||
ext.startup_version = "1.1.1"
|
||||
ext.roomVersion = "2.5.0"
|
||||
ext.workVersion = "2.7.1"
|
||||
ext.glideVersion = '4.13.2'
|
||||
ext.glideVersion = "4.13.2"
|
||||
|
||||
ext.appcompat_version = "1.6.0"
|
||||
ext.lifecycle_version = "2.5.1"
|
||||
|
@ -21,20 +19,20 @@ buildscript {
|
|||
|
||||
ext.okhttpVersion = "4.10.0"
|
||||
|
||||
ext.kotlin_version = '1.7.21'
|
||||
ext.kotlinx_coroutines_version = '1.6.4'
|
||||
ext.kotlin_version = "1.7.21"
|
||||
ext.kotlinx_coroutines_version = "1.6.4"
|
||||
|
||||
ext.anko_version = '0.10.8'
|
||||
ext.anko_version = "0.10.8"
|
||||
|
||||
ext.junit_version = '4.13.2'
|
||||
ext.junit_version = "4.13.2"
|
||||
|
||||
ext.detekt_version = '1.22.0'
|
||||
ext.detekt_version = "1.22.0"
|
||||
|
||||
ext.compose_version = '1.0.5'
|
||||
ext.compose_version = "1.0.5"
|
||||
|
||||
ext.koin_version = '3.1.3'
|
||||
ext.koin_version = "3.1.3"
|
||||
|
||||
ext.androidx_test_version = '1.5.0'
|
||||
ext.androidx_test_version = "1.5.0"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
@ -42,10 +40,10 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath "com.android.tools.build:gradle:7.3.1"
|
||||
|
||||
// room のバージョンの影響で google-services を上げられない場合がある
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath "com.google.gms:google-services:4.3.14"
|
||||
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
|
@ -59,13 +57,13 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
maven { url 'https://maven.google.com' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "https://maven.google.com" }
|
||||
maven { url "https://jitpack.io" }
|
||||
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://dl.bintray.com/google/exoplayer/' }
|
||||
maven { url 'https://dl.bintray.com/google/flexbox-layout/' }
|
||||
maven { url "https://dl.bintray.com/google/exoplayer/" }
|
||||
maven { url "https://dl.bintray.com/google/flexbox-layout/" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +71,7 @@ task clean(type: Delete) {
|
|||
delete rootProject.buildDir
|
||||
}
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
options.compilerArgs << '-Xlint:deprecation'
|
||||
options.compilerArgs << '-Xlint:divzero'
|
||||
options.compilerArgs << "-Xlint:unchecked"
|
||||
options.compilerArgs << "-Xlint:deprecation"
|
||||
options.compilerArgs << "-Xlint:divzero"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
|
@ -19,7 +19,7 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id "com.android.library"
|
||||
id "org.jetbrains.kotlin.android"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'jp.juggler.icon_material_symbols'
|
||||
namespace "jp.juggler.icon_material_symbols"
|
||||
compileSdk compile_sdk_version
|
||||
defaultConfig {
|
||||
minSdk min_sdk_version
|
||||
|
@ -16,7 +16,7 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
@ -24,6 +24,6 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
|
@ -25,7 +25,7 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,5 +49,5 @@ android {
|
|||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
implementation project(":base")
|
||||
implementation project(':apng_android')
|
||||
implementation project(":apng_android")
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class ActList : AppCompatActivity(), CoroutineScope {
|
|||
private lateinit var activityJob: Job
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = AppDispatchers.mainImmediate + activityJob
|
||||
get() = activityJob + AppDispatchers.mainImmediate
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
|
@ -86,7 +86,7 @@ class ActList : AppCompatActivity(), CoroutineScope {
|
|||
}
|
||||
|
||||
private fun load() = launch {
|
||||
val list = withContext(Dispatchers.IO) {
|
||||
val list = withContext(AppDispatchers.io) {
|
||||
// RawリソースのIDと名前の一覧
|
||||
R.raw::class.java.fields
|
||||
.mapNotNull { it.get(null) as? Int }
|
||||
|
@ -179,7 +179,7 @@ class ActList : AppCompatActivity(), CoroutineScope {
|
|||
try {
|
||||
lastJob?.cancelAndJoin()
|
||||
|
||||
val job = async(Dispatchers.IO) {
|
||||
val job = async(AppDispatchers.io) {
|
||||
try {
|
||||
ApngFrames.parse(128) { resources?.openRawResource(resId) }
|
||||
} catch (ex: Throwable) {
|
||||
|
|
|
@ -7,16 +7,17 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.apng.ApngFrames
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.AsyncActivity
|
||||
import jp.juggler.util.int
|
||||
import jp.juggler.util.string
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ActViewer : AppCompatActivity(), CoroutineScope {
|
||||
class ActViewer : AsyncActivity() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "ActViewer"
|
||||
|
@ -34,13 +35,7 @@ class ActViewer : AppCompatActivity(), CoroutineScope {
|
|||
private lateinit var apngView: ApngView
|
||||
private lateinit var tvError: TextView
|
||||
|
||||
private lateinit var activityJob: Job
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + activityJob
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
activityJob = Job()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val resId = intent.int(EXTRA_RES_ID) ?: 0
|
||||
|
@ -64,7 +59,7 @@ class ActViewer : AppCompatActivity(), CoroutineScope {
|
|||
launch {
|
||||
var apngFrames: ApngFrames? = null
|
||||
try {
|
||||
apngFrames = withContext(Dispatchers.IO) {
|
||||
apngFrames = withContext(AppDispatchers.io) {
|
||||
try {
|
||||
ApngFrames.parse(
|
||||
1024,
|
||||
|
@ -99,13 +94,12 @@ class ActViewer : AppCompatActivity(), CoroutineScope {
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
apngView.apngFrames?.dispose()
|
||||
activityJob.cancel()
|
||||
}
|
||||
|
||||
private fun save(apngFrames: ApngFrames) {
|
||||
val title = this.title
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
launch(AppDispatchers.io) {
|
||||
|
||||
//deprecated in Android 10 (API level 29)
|
||||
//val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
|
@ -139,4 +133,4 @@ class ActViewer : AppCompatActivity(), CoroutineScope {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue