This is the 19th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021

preface

DataStore is a component of Android’s official Jetpack library, a simple data storage solution that replaces SharedPreferences and supports Koltin coroutines and flows, enabling applications to store and use data asynchronously.

The Preferences DataStore and Proto DataStore are officially recommended. The Preferences DataStore is simple to use and does not require pre-definition, but does not support type safety. Proto DataStore is complex to use and requires protocol buffers to define data beforehand, but is type-safe. So is it possible to be type safe without having to implement the definition? Kotlinx. serialization provides us with a solution.

The cache

If a page needs to be displayed faster, we need to cache the data for that page. The previous solution was to cache the interface response data, and then reparse the cache data to display the page. If performance issues are not discussed, there are two obvious problems in this way. One is that more boilerplate code is required, but the interface data is often not complete data. We often need to obtain the data displayed in the end from the existing data in multiple interfaces or the combination of App. This part of state data cannot be obtained by using this caching scheme. Therefore, can we directly cache the data needed for UI display?

usekotlinx.serializationSerialized data

When developing with Kotlin, we often encapsulate the state in data classes. The state drives the display of the View, and the View can be directly displayed when it gets to the data class.

sealed class PageState {

    object Loading : PageState()

    data class Error(val cause: Throwable) : PageState()

    data class RenderData(

        val modules: List<ModuleModel>

    ) : PageState()

}
Copy the code

Serialization kotlinx.serialization is the official serialization library for Kotlin. The main advantage is that based on the @serializable annotation, the compiler generates serialization code at compile time to avoid reflection overhead at run time.

Kotlinx. serialization makes it easy to serialize and deserialize data classes, so consider using this technique to simplify the caching process.

Kotlinx. serialization is also easy to use. The simplest way to use kotlinx.serialization is to add it to the target data class

@serializable ‘ ‘can be annotated.

@Serializable

 data class RenderData(

    val modules: List<ModuleModel>

) : PageState()
Copy the code

Serialization is simple to use

val bytes = Json.encodeToString(renderData).encodeToByteArray()
Copy the code

Deserialization also has very little code to use

val renderData = Json.decodeFromString<PageState.RenderData>(inputStream.readBytes().decodeToString())
Copy the code

Using kotlinx.serialization requires the introduction of dependencies.

  • The buidld. Gradle of Project introduces the related plug-in configuration
buildscript {

 dependencies {

   classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" 

}
Copy the code
  • Build. gradle in Module introduces plug-ins and dependencies
plugins { id 'kotlinx-serialization' } dependencies { implementation "Org. Jetbrains. Kotlinx: kotlinx serialization - json: 1.1.0"}Copy the code

Use DataStore to cache data

However, serialization and deserialization alone are not enough. Storing and reading samples is also a lot of code, and DataStore can help simplify this process. We just need to define a Serializer for DataStore to tell it how to serialize and deserialize.

private object RenderDataSerializer :

        androidx.datastore.core.Serializer<PageState.RenderData> {

        override val defaultValue: PageState.RenderData

            get() = RENDER_DATA_NULL



        override suspend fun readFrom(input: InputStream): PageState.RenderData {

            return try {

                Json.decodeFromString(input.readBytes().decodeToString())

            } catch (serialization: SerializationException) {

                logger.all( "readFrom: err: ${serialization.message}")

                defaultValue

            }

        }



        override suspend fun writeTo(t: PageState.RenderData, output: OutputStream) {

            try {

                withContext(Dispatchers.IO) {

 output.write(Json.encodeToString(t).encodeToByteArray())

                }

 } catch (ex: Exception) {

                logger.all( "writeTo: ${ex.message}")

            }

        }

    }
Copy the code

A simple framework is as follows

object PageModuleStore{ private val RENDER_DATA_NULL = PageState.RenderData(emptyList()) private val Context.dataStore by dataStore( fileName = "page_module.json", serializer = RenderDataSerializer ) suspend fun getPageCache(): PageState.RenderData? { return try { val data = MyApp.getAppContext().dataStore.data.first() return if (data == RENDER_DATA_NULL) { null } else { data } } catch (ex: Exception) { null } } suspend fun savePageCache(data: PageState.RenderData): Boolean { return try { MyApp.getAppContext().dataStore.updateData { data } logger.all( "savePageCache success: $data") true } catch (e: Exception) { logger.all( "savePageCache failed: ${e.message}") false } } suspend fun clearPageCache() { logger.all( "clearPageCache.") savePageCache(RENDER_DATA_NULL) }  private object RenderDataSerializer : androidx.datastore.core.Serializer<PageState.RenderData> { override val defaultValue: PageState.RenderData get() = RENDER_DATA_NULL override suspend fun readFrom(input: InputStream): PageState.RenderData { return try { Json.decodeFromString(input.readBytes().decodeToString()) } catch (serialization: SerializationException) { logger.all( "readFrom: err: ${serialization.message}") defaultValue } } override suspend fun writeTo(t: PageState.RenderData, output: OutputStream) { try { withContext(Dispatchers.IO) { output.write(Json.encodeToString(t).encodeToByteArray()) } } catch (ex: Exception) { logger.all( "writeTo: ${ex.message}") } } } }Copy the code

Kotlinx. serialization lets us ignore serialization and deserialization details, and DataStore helps us store and use the cache. At the same time, we cache the final data, which is stateless. In this way, the data can be used, which also speeds up the display of the interface.

Happy ending.