mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
remove androidx.datastore dependency
commit_hash:e54c4e6529a08d4ee2f7a2e5afa225c939424563
This commit is contained in:
@@ -1480,6 +1480,9 @@
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/spannable/PositionAwareReplacementSpan.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/spannable/PositionAwareReplacementSpan.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/spannable/TextColorSpan.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/spannable/TextColorSpan.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/spannable/TypefaceSpan.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/spannable/TypefaceSpan.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/storage/DataEditor.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/storage/DataEditor.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/storage/DataState.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/storage/DataState.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/storage/DataStorage.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/storage/DataStorage.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/util/Position.java":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/util/Position.java",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/util/ViewGroups.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/util/ViewGroups.kt",
|
||||
@@ -1493,6 +1496,7 @@
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPool.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPool.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPoolProfiler.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPoolProfiler.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPreCreationProfile.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPreCreationProfile.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPreCreationProfileParser.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/ViewPreCreationProfileParser.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/PerformanceDependentSession.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/PerformanceDependentSession.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/PerformanceDependentSessionProfiler.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/PerformanceDependentSessionProfiler.kt",
|
||||
"client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/ViewPreCreationProfileOptimizer.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/internal/viewpool/optimization/ViewPreCreationProfileOptimizer.kt",
|
||||
@@ -1672,6 +1676,7 @@
|
||||
"client/android/div/src/test/java/com/yandex/div/interactive/IntegrationMultiplatformTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationMultiplatformTest.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestCase.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestCase.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/internal/storage/DataStorageTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/storage/DataStorageTest.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/internal/viewpool/ProfilingSessionExtensionTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/viewpool/ProfilingSessionExtensionTest.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/internal/widget/AutoEllipsizeHelperTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/widget/AutoEllipsizeHelperTest.kt",
|
||||
"client/android/div/src/test/java/com/yandex/div/internal/widget/TransientViewTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/widget/TransientViewTest.kt",
|
||||
|
||||
@@ -32,7 +32,6 @@ buildscript {
|
||||
classpath libs.dokka
|
||||
classpath libs.kotlin.allopen
|
||||
classpath libs.kotlin.gradle.plugin
|
||||
classpath libs.kotlin.serialization.plugin
|
||||
classpath libs.kotlin.binaryCompatibilityValidator
|
||||
classpath libs.unmock.plugin
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ apply from: "${project.projectDir}/../div-tests.gradle"
|
||||
apply from: "${project.projectDir}/../publish-android.gradle"
|
||||
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
def expressionApiDir = "${project.projectDir}/../../../expression-api/"
|
||||
def crossplatformProjectDir = "${project.projectDir}/../../../test_data/"
|
||||
@@ -67,9 +66,6 @@ dependencies {
|
||||
exclude group: "androidx.fragment", module: "fragment"
|
||||
}
|
||||
|
||||
implementation libs.androidx.datastore
|
||||
implementation libs.kotlin.serialization.json
|
||||
|
||||
testImplementation project(path: ':expression-test-common')
|
||||
|
||||
testImplementation libs.androidx.test.coreKtx
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.yandex.div.internal.storage
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.charset.Charset
|
||||
|
||||
internal interface DataReader<T> {
|
||||
fun read(input: InputStream): T
|
||||
}
|
||||
|
||||
internal interface DataWriter<T> {
|
||||
fun write(value: T, output: OutputStream)
|
||||
}
|
||||
|
||||
internal interface DataEditor<T> : DataReader<T>, DataWriter<T>
|
||||
|
||||
internal abstract class TextDataEditor<T>(
|
||||
private val charset: Charset = Charsets.UTF_8
|
||||
) : DataEditor<T> {
|
||||
|
||||
override fun read(input: InputStream): T {
|
||||
val fileData = input.reader(charset).use { it.readText() }
|
||||
return read(fileData)
|
||||
}
|
||||
|
||||
abstract fun read(input: String): T
|
||||
|
||||
override fun write(value: T, output: OutputStream) {
|
||||
val fileData = write(value)
|
||||
output.writer(charset).use { it.write(fileData) }
|
||||
}
|
||||
|
||||
abstract fun write(value: T): String
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.yandex.div.internal.storage
|
||||
|
||||
internal sealed interface DataState<T> {
|
||||
|
||||
object Initial : DataState<Any>
|
||||
|
||||
class WithData<T>(val value: T) : DataState<T>
|
||||
|
||||
class WithException<T>(val exception: Throwable) : DataState<T>
|
||||
|
||||
object Finalized : DataState<Any>
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.yandex.div.internal.storage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
internal class DataStorage<T> private constructor(
|
||||
context: Context,
|
||||
fileName: String,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val editor: DataEditor<T>,
|
||||
) {
|
||||
|
||||
private val coroutineContext = coroutineScope.coroutineContext
|
||||
private val dataFile = File(context.applicationContext.filesDir, fileName)
|
||||
private val mutex = Mutex()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private val dataStateFlow = MutableStateFlow<DataState<T>>(
|
||||
value = DataState.Initial as DataState<T>
|
||||
)
|
||||
|
||||
val data: StateFlow<T?> = dataStateFlow.onStart { restoreState() }
|
||||
.map { state -> evaluateState(state) }
|
||||
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
init {
|
||||
dataStateFlow.onSubscription { }
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
dataStateFlow.value = DataState.Finalized as DataState<T>
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateState(state: DataState<T>): T? {
|
||||
return when (state) {
|
||||
is DataState.Initial -> null
|
||||
is DataState.WithData<T> -> state.value
|
||||
is DataState.WithException<T> -> throw state.exception
|
||||
is DataState.Finalized -> error("Cannot read from closed storage")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun restoreState() {
|
||||
return withContext(coroutineContext) {
|
||||
mutex.withLock {
|
||||
if (dataStateFlow.value is DataState.Initial) {
|
||||
try {
|
||||
val value = editor.read(dataFile.inputStream())
|
||||
dataStateFlow.value = DataState.WithData(value)
|
||||
} catch (e: Exception) {
|
||||
dataStateFlow.value = DataState.WithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(value: T) {
|
||||
withContext(coroutineContext) {
|
||||
mutex.withLock {
|
||||
try {
|
||||
if (!dataFile.exists()) {
|
||||
dataFile.parentFile?.mkdirs()
|
||||
dataFile.createNewFile()
|
||||
}
|
||||
|
||||
val outputStream = dataFile.outputStream()
|
||||
outputStream.use { output ->
|
||||
editor.write(value, output)
|
||||
}
|
||||
|
||||
dataStateFlow.value = DataState.WithData(value)
|
||||
} catch (e: Exception) {
|
||||
dataStateFlow.value = DataState.WithException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear(): Boolean {
|
||||
return withContext(coroutineContext) {
|
||||
mutex.withLock {
|
||||
try {
|
||||
val result = dataFile.delete()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
dataStateFlow.value = DataState.Initial as DataState<T>
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
dataStateFlow.value = DataState.WithException(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun <T> create(
|
||||
context: Context,
|
||||
fileName: String,
|
||||
editor: DataEditor<T>
|
||||
): DataStorage<T> {
|
||||
return DataStorage(
|
||||
context = context,
|
||||
fileName = fileName,
|
||||
coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||
editor = editor
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@VisibleForTesting
|
||||
internal fun <T> create(
|
||||
context: Context,
|
||||
fileName: String,
|
||||
editor: DataEditor<T>,
|
||||
coroutineScope: CoroutineScope
|
||||
): DataStorage<T> {
|
||||
return DataStorage(
|
||||
context = context,
|
||||
fileName = fileName,
|
||||
coroutineScope = coroutineScope,
|
||||
editor = editor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-4
@@ -1,15 +1,11 @@
|
||||
package com.yandex.div.internal.viewpool
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PreCreationModel(
|
||||
val capacity: Int,
|
||||
val min: Int = 0,
|
||||
val max: Int = Int.MAX_VALUE
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ViewPreCreationProfile(
|
||||
val id: String? = null,
|
||||
val text: PreCreationModel = PreCreationModel(20),
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.yandex.div.internal.viewpool
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
internal object PreCreationModelParser {
|
||||
|
||||
private const val KEY_CAPACITY = "capacity"
|
||||
private const val KEY_MIN = "min"
|
||||
private const val KEY_MAX = "max"
|
||||
|
||||
fun serialize(model: PreCreationModel): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put(KEY_CAPACITY, model.capacity)
|
||||
put(KEY_MIN, model.min)
|
||||
put(KEY_MAX, model.max)
|
||||
}
|
||||
}
|
||||
|
||||
fun deserialize(json: JSONObject): PreCreationModel {
|
||||
return PreCreationModel(
|
||||
capacity = json.getInt(KEY_CAPACITY),
|
||||
min = json.getInt(KEY_MIN),
|
||||
max = json.getInt(KEY_MAX),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal object ViewPreCreationProfileParser {
|
||||
|
||||
private const val KEY_ID = "id"
|
||||
private const val KEY_TEXT = "text"
|
||||
private const val KEY_IMAGE = "image"
|
||||
private const val KEY_GIF_IMAGE = "gifImage"
|
||||
private const val KEY_OVERLAP_CONTAINER = "overlapContainer"
|
||||
private const val KEY_LINEAR_CONTAINER = "linearContainer"
|
||||
private const val KEY_WRAP_CONTAINER = "wrapContainer"
|
||||
private const val KEY_GRID = "grid"
|
||||
private const val KEY_GALLERY = "gallery"
|
||||
private const val KEY_PAGER = "pager"
|
||||
private const val KEY_TAB = "tab"
|
||||
private const val KEY_STATE = "state"
|
||||
private const val KEY_CUSTOM = "custom"
|
||||
private const val KEY_INDICATOR = "indicator"
|
||||
private const val KEY_SLIDER = "slider"
|
||||
private const val KEY_INPUT = "input"
|
||||
private const val KEY_SELECT = "select"
|
||||
private const val KEY_VIDEO = "video"
|
||||
private const val KEY_SWITCH = "switch"
|
||||
|
||||
fun serialize(profile: ViewPreCreationProfile): JSONObject {
|
||||
return JSONObject().apply {
|
||||
putOpt(KEY_ID, profile.id)
|
||||
put(KEY_TEXT, PreCreationModelParser.serialize(profile.text))
|
||||
put(KEY_IMAGE, PreCreationModelParser.serialize(profile.image))
|
||||
put(KEY_GIF_IMAGE, PreCreationModelParser.serialize(profile.gifImage))
|
||||
put(KEY_OVERLAP_CONTAINER, PreCreationModelParser.serialize(profile.overlapContainer))
|
||||
put(KEY_LINEAR_CONTAINER, PreCreationModelParser.serialize(profile.linearContainer))
|
||||
put(KEY_WRAP_CONTAINER, PreCreationModelParser.serialize(profile.wrapContainer))
|
||||
put(KEY_GRID, PreCreationModelParser.serialize(profile.grid))
|
||||
put(KEY_GALLERY, PreCreationModelParser.serialize(profile.gallery))
|
||||
put(KEY_PAGER, PreCreationModelParser.serialize(profile.pager))
|
||||
put(KEY_TAB, PreCreationModelParser.serialize(profile.tab))
|
||||
put(KEY_STATE, PreCreationModelParser.serialize(profile.state))
|
||||
put(KEY_CUSTOM, PreCreationModelParser.serialize(profile.custom))
|
||||
put(KEY_INDICATOR, PreCreationModelParser.serialize(profile.indicator))
|
||||
put(KEY_SLIDER, PreCreationModelParser.serialize(profile.slider))
|
||||
put(KEY_INPUT, PreCreationModelParser.serialize(profile.input))
|
||||
put(KEY_SELECT, PreCreationModelParser.serialize(profile.select))
|
||||
put(KEY_VIDEO, PreCreationModelParser.serialize(profile.video))
|
||||
put(KEY_SWITCH, PreCreationModelParser.serialize(profile.switch))
|
||||
}
|
||||
}
|
||||
|
||||
fun deserialize(json: JSONObject): ViewPreCreationProfile {
|
||||
return ViewPreCreationProfile(
|
||||
id = json.optString(KEY_ID),
|
||||
text = PreCreationModelParser.deserialize(json.getJSONObject(KEY_TEXT)),
|
||||
image = PreCreationModelParser.deserialize(json.getJSONObject(KEY_IMAGE)),
|
||||
gifImage = PreCreationModelParser.deserialize(json.getJSONObject(KEY_GIF_IMAGE)),
|
||||
overlapContainer = PreCreationModelParser.deserialize(json.getJSONObject(KEY_OVERLAP_CONTAINER)),
|
||||
linearContainer = PreCreationModelParser.deserialize(json.getJSONObject(KEY_LINEAR_CONTAINER)),
|
||||
wrapContainer = PreCreationModelParser.deserialize(json.getJSONObject(KEY_WRAP_CONTAINER)),
|
||||
grid = PreCreationModelParser.deserialize(json.getJSONObject(KEY_GRID)),
|
||||
gallery = PreCreationModelParser.deserialize(json.getJSONObject(KEY_GALLERY)),
|
||||
pager = PreCreationModelParser.deserialize(json.getJSONObject(KEY_PAGER)),
|
||||
tab = PreCreationModelParser.deserialize(json.getJSONObject(KEY_TAB)),
|
||||
state = PreCreationModelParser.deserialize(json.getJSONObject(KEY_STATE)),
|
||||
custom = PreCreationModelParser.deserialize(json.getJSONObject(KEY_CUSTOM)),
|
||||
indicator = PreCreationModelParser.deserialize(json.getJSONObject(KEY_INDICATOR)),
|
||||
slider = PreCreationModelParser.deserialize(json.getJSONObject(KEY_SLIDER)),
|
||||
input = PreCreationModelParser.deserialize(json.getJSONObject(KEY_INPUT)),
|
||||
select = PreCreationModelParser.deserialize(json.getJSONObject(KEY_SELECT)),
|
||||
video = PreCreationModelParser.deserialize(json.getJSONObject(KEY_VIDEO)),
|
||||
switch = PreCreationModelParser.deserialize(json.getJSONObject(KEY_SWITCH)),
|
||||
)
|
||||
}
|
||||
}
|
||||
-2
@@ -3,7 +3,6 @@ package com.yandex.div.internal.viewpool.optimization
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.collection.ArrayMap
|
||||
import com.yandex.div.core.view2.DivViewCreator
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed class PerformanceDependentSession {
|
||||
sealed class ViewObtainmentStatistics {
|
||||
@@ -47,7 +46,6 @@ sealed class PerformanceDependentSession {
|
||||
}
|
||||
|
||||
class Detailed : PerformanceDependentSession() {
|
||||
@Serializable
|
||||
data class ViewObtainment(
|
||||
val obtainmentTime: Long,
|
||||
val obtainmentDuration: Long,
|
||||
|
||||
+20
-30
@@ -1,24 +1,18 @@
|
||||
package com.yandex.div.internal.viewpool.optimization
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.yandex.div.core.annotations.Mockable
|
||||
import com.yandex.div.core.dagger.DivScope
|
||||
import com.yandex.div.core.dagger.Names.APP_CONTEXT
|
||||
import com.yandex.div.internal.KLog
|
||||
import com.yandex.div.internal.storage.DataStorage
|
||||
import com.yandex.div.internal.storage.TextDataEditor
|
||||
import com.yandex.div.internal.viewpool.ViewPreCreationProfile
|
||||
import com.yandex.div.internal.viewpool.ViewPreCreationProfileParser
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import kotlinx.serialization.json.encodeToStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import org.json.JSONObject
|
||||
import java.util.WeakHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
@@ -26,35 +20,30 @@ import javax.inject.Named
|
||||
@DivScope
|
||||
@Mockable
|
||||
class ViewPreCreationProfileRepository @Inject constructor(
|
||||
@Named(APP_CONTEXT) private val context: Context,
|
||||
@param:Named(APP_CONTEXT) private val context: Context,
|
||||
private val defaultProfile: ViewPreCreationProfile
|
||||
) {
|
||||
|
||||
suspend fun get(id: String): ViewPreCreationProfile = withContext(Dispatchers.IO) {
|
||||
runCatching { context.getStoreForId(id).data.first() }
|
||||
runCatching { context.getStorageForId(context, id).data.first() }
|
||||
.onFailure { KLog.e(TAG, it) }
|
||||
.getOrNull() ?: defaultProfile.copy(id = id)
|
||||
}
|
||||
|
||||
suspend fun save(profile: ViewPreCreationProfile): Boolean = withContext(Dispatchers.IO) {
|
||||
runCatching { context.getStoreForId(profile.id!!).updateData { profile } }
|
||||
runCatching { context.getStorageForId(context, profile.id!!).update(profile) }
|
||||
.onFailure { KLog.e(TAG, it) }
|
||||
.isSuccess
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private object ViewPreCreationProfileSerializer : Serializer<ViewPreCreationProfile?> {
|
||||
private val json = Json { encodeDefaults = false }
|
||||
private object ViewPreCreationProfileEditor : TextDataEditor<ViewPreCreationProfile>() {
|
||||
|
||||
override val defaultValue: ViewPreCreationProfile? = null
|
||||
override fun read(input: String): ViewPreCreationProfile {
|
||||
return ViewPreCreationProfileParser.deserialize(JSONObject(input))
|
||||
}
|
||||
|
||||
override suspend fun readFrom(input: InputStream) = runCatching {
|
||||
json.decodeFromStream<ViewPreCreationProfile?>(input)
|
||||
}.onFailure { KLog.e(TAG, it) }.getOrNull()
|
||||
|
||||
override suspend fun writeTo(t: ViewPreCreationProfile?, output: OutputStream) {
|
||||
runCatching {
|
||||
json.encodeToStream(t, output)
|
||||
}.onFailure { KLog.e(TAG, it) }
|
||||
override fun write(value: ViewPreCreationProfile): String {
|
||||
return ViewPreCreationProfileParser.serialize(value).toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +51,14 @@ class ViewPreCreationProfileRepository @Inject constructor(
|
||||
const val TAG = "OptimizedViewPreCreationProfileRepository"
|
||||
const val STORE_PATH = "divkit_optimized_viewpool_profile_%s.json"
|
||||
|
||||
val stores = WeakHashMap<String, DataStore<ViewPreCreationProfile?>>()
|
||||
val stores = WeakHashMap<String, DataStorage<ViewPreCreationProfile>>()
|
||||
|
||||
fun Context.getStoreForId(id: String): DataStore<ViewPreCreationProfile?> =
|
||||
fun Context.getStorageForId(context: Context, id: String): DataStorage<ViewPreCreationProfile> =
|
||||
stores.getOrPut(id) {
|
||||
DataStoreFactory.create(
|
||||
serializer = ViewPreCreationProfileSerializer,
|
||||
produceFile = { File(filesDir, STORE_PATH.format(id)) }
|
||||
DataStorage.create(
|
||||
context = context,
|
||||
fileName = STORE_PATH,
|
||||
editor = ViewPreCreationProfileEditor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.yandex.div.internal.storage
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.io.File
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DataStorageTest {
|
||||
|
||||
private val context = ApplicationProvider.getApplicationContext<Application>()
|
||||
private val editor = mock<DataEditor<String>> {
|
||||
on { write(any(), any()) } doAnswer { Unit }
|
||||
}
|
||||
private val testFile = File(context.filesDir, "test_file")
|
||||
|
||||
@Test
|
||||
fun `data should be null initially`() = runTest {
|
||||
val storage = createDataStorage(editor)
|
||||
collectFlow(storage.data)
|
||||
|
||||
assertNull(storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update should write data to file and update state`() = runTest {
|
||||
val storage = createDataStorage(editor)
|
||||
val testData = "test data"
|
||||
|
||||
storage.update(testData)
|
||||
collectFlow(storage.data)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify(editor).write(eq(testData), any())
|
||||
assertEquals(testData, storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear should delete file and reset state`() = runTest {
|
||||
val storage = createDataStorage(editor)
|
||||
val testData = "test data"
|
||||
|
||||
storage.update(testData)
|
||||
collectFlow(storage.data)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(testData, storage.data.value)
|
||||
|
||||
val result = storage.clear()
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(result)
|
||||
assertNull(storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data should be read from file on initialization`() = runTest {
|
||||
testFile.parentFile?.mkdirs()
|
||||
testFile.createNewFile()
|
||||
|
||||
val testData = "test data"
|
||||
whenever(editor.read(any())).thenReturn(testData)
|
||||
val storage = createDataStorage(editor)
|
||||
|
||||
collectFlow(storage.data)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(testData, storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `data should be null if file reading fails`() = runTest {
|
||||
whenever(editor.read(any())).thenThrow(RuntimeException("Read error"))
|
||||
val storage = createDataStorage(editor)
|
||||
|
||||
collectFlow(storage.data)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertNull(storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update should handle exceptions`() = runTest {
|
||||
val testData = "test data"
|
||||
whenever(editor.write(any(), any())).thenThrow(RuntimeException("Write error"))
|
||||
val storage = createDataStorage(editor)
|
||||
|
||||
storage.update(testData)
|
||||
collectFlow(storage.data)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Data should remain null due to exception
|
||||
assertNull(storage.data.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear should handle exceptions`() = runTest {
|
||||
// Make file undeletable by making it a non-empty directory
|
||||
testFile.mkdirs()
|
||||
File(testFile, "sub_file").createNewFile()
|
||||
val storage = createDataStorage(editor)
|
||||
|
||||
val result = storage.clear()
|
||||
advanceUntilIdle()
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
private fun <T> TestScope.createDataStorage(editor: DataEditor<T>): DataStorage<T> {
|
||||
val coroutineDispatcher = StandardTestDispatcher(testScheduler)
|
||||
val coroutineScope = TestScope(coroutineDispatcher)
|
||||
return DataStorage.create(context, testFile.name, editor, coroutineScope)
|
||||
}
|
||||
|
||||
private fun <T> TestScope.collectFlow(flow: Flow<T>): Job {
|
||||
return backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||
flow.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ kotlin = "2.2.10"
|
||||
kotlin-binaryCompatibilityValidator = "0.18.1"
|
||||
kotlin-coroutines = "1.10.2"
|
||||
kotlin-ksp = "2.3.4"
|
||||
kotlin-serialization = "1.9.0"
|
||||
kotlinpoet = "2.2.0"
|
||||
lottie = "6.1.0"
|
||||
markwon = "4.6.2"
|
||||
@@ -46,7 +45,6 @@ androidx-collection = "1.5.0"
|
||||
androidx-compose-bom = "2024.04.00"
|
||||
androidx-constraint = "2.2.1"
|
||||
androidx-core = "1.16.0"
|
||||
androidx-datastore = "1.1.7"
|
||||
androidx-espresso = "3.7.0"
|
||||
androidx-fragment = "1.8.9"
|
||||
androidx-lifecycle = "2.9.4"
|
||||
@@ -80,7 +78,6 @@ androidx-collection = { module = "androidx.collection:collection", version.ref =
|
||||
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraint" }
|
||||
androidx-core = { module = "androidx.core:core", version.ref = "androidx-core" }
|
||||
androidx-coreKtx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
|
||||
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||
androidx-material = { module = "com.google.android.material:material", version.ref = "androidx-material" }
|
||||
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
||||
@@ -159,9 +156,6 @@ kotlin-corountines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
|
||||
kotlin-corountines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
|
||||
kotlin-corountines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
|
||||
|
||||
kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" }
|
||||
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
|
||||
|
||||
kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" }
|
||||
|
||||
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
|
||||
|
||||
Reference in New Issue
Block a user