Preservación del estado y almacenamiento persistente
El contenido y las muestras de código que aparecen en esta página están sujetas a las licencias que se describen en la Licencia de Contenido. Java y OpenJDK son marcas registradas de Oracle o sus afiliados.
Última actualización: 2025-07-27 (UTC)
[null,null,["Última actualización: 2025-07-27 (UTC)"],[],[],null,["# State preservation and persistent storage\n\nJetpack Compose\n---------------\n\nIn Jetpack Compose, UI state is typically managed using\n[`remember`](/reference/kotlin/androidx/compose/runtime/package-summary#remember(kotlin.Function0))\nand\n[`rememberSaveable`](/reference/kotlin/androidx/compose/runtime/saveable/package-summary#rememberSaveable(kotlin.Array,androidx.compose.runtime.saveable.Saver,kotlin.String,kotlin.Function0)).\nWhile\n[`rememberSaveable`](/reference/kotlin/androidx/compose/runtime/saveable/package-summary#rememberSaveable(kotlin.Array,androidx.compose.runtime.saveable.Saver,kotlin.String,kotlin.Function0))\noffers automatic state preservation across configuration changes, its built-in\ncapabilities are limited to primitive data types and objects that implement\n[`Parcelable`](/reference/kotlin/android/os/Parcelable) or\n[`Serializable`](/reference/java/io/Serializable).\n\nFor custom objects such as\n[`Brush`](/reference/kotlin/androidx/ink/brush/Brush), which may encompass\nintricate nested structures and properties, explicit serialization and\ndeserialization mechanisms are necessary. This is where a custom state saver\nbecomes useful. By defining a custom\n[`Saver`](/reference/kotlin/androidx/compose/runtime/saveable/Saver) for\nthe `Brush` object, as\ndemonstrated in the provided example with `brushStateSaver`using the example\n`Converters`class, you can guarantee the\npreservation of the brush's essential attributes even when configuration changes\noccur. \n\n fun brushStateSaver(converters: Converters): Saver\u003cMutableState\u003cBrush\u003e, String\u003e = Saver(\n save = { state -\u003e\n converters.brushToString(state.value)\n },\n restore = { jsonString -\u003e\n val brush = converters.stringToBrush(jsonString)\n mutableStateOf(brush)\n }\n )\n\nYou can then use the custom\n[`Saver`](/reference/kotlin/androidx/compose/runtime/saveable/Saver) to\npreserve a user's selected brush state like so: \n\n val converters = Converters()\n val currentBrush = rememberSaveable(saver = brushStateSaver(converters)) { mutableStateOf(defaultBrush) }\n\n### Persistent storage\n\nTo enable features such as document saving, loading, and potential real-time\ncollaboration, store strokes and associated data in a serialized format. For the\nInk API, manual serialization and deserialization are necessary.\n\nTo accurately restore a stroke, save its`Brush` and \\[`StrokeInputBatch`\\].\n\n- [**`Brush`**](/reference/kotlin/androidx/ink/brush/Brush): Includes numeric fields (size, epsilon), color, and [`BrushFamily`](/reference/kotlin/androidx/ink/brush/BrushFamily).\n- [**`StrokeInputBatch`**](/reference/kotlin/androidx/ink/strokes/StrokeInputBatch): Essentially a list of input points with numeric fields.\n\n#### Basic serialization\n\nDefine a serialization object structure that mirrors the Ink library objects.\n\nEncode the serialized data using your preferred framework like Gson, Moshi,\nProtobuf, and others, and use compression for optimization. \n\n data class SerializedStroke(\n val inputs: SerializedStrokeInputBatch,\n val brush: SerializedBrush\n )\n\n data class SerializedBrush(\n val size: Float,\n val color: Long,\n val epsilon: Float,\n val stockBrush: SerializedStockBrush\n )\n\n enum class SerializedStockBrush {\n MARKER_V1,\n PRESSURE_PEN_V1,\n HIGHLIGHTER_V1\n }\n\n data class SerializedStrokeInputBatch(\n val toolType: SerializedToolType,\n val strokeUnitLengthCm: Float,\n val inputs: List\u003cSerializedStrokeInput\u003e\n )\n\n data class SerializedStrokeInput(\n val x: Float,\n val y: Float,\n val timeMillis: Float,\n val pressure: Float,\n val tiltRadians: Float,\n val orientationRadians: Float,\n val strokeUnitLengthCm: Float\n )\n\n enum class SerializedToolType {\n STYLUS,\n TOUCH,\n MOUSE,\n UNKNOWN\n }\n\n class Converters {\n\n private val gson: Gson = GsonBuilder().create()\n\n companion object {\n private val stockBrushToEnumValues =\n mapOf(\n StockBrushes.markerV1 to SerializedStockBrush.MARKER_V1,\n StockBrushes.pressurePenV1 to SerializedStockBrush.PRESSURE_PEN_V1,\n StockBrushes.highlighterV1 to SerializedStockBrush.HIGHLIGHTER_V1,\n )\n\n private val enumToStockBrush =\n stockBrushToEnumValues.entries.associate { (key, value) -\u003e value to key }\n }\n\n private fun serializeBrush(brush: Brush): SerializedBrush {\n return SerializedBrush(\n size = brush.size,\n color = brush.colorLong,\n epsilon = brush.epsilon,\n stockBrush = stockBrushToEnumValues[brush.family] ?: SerializedStockBrush.MARKER_V1,\n )\n }\n\n private fun serializeStrokeInputBatch(inputs: StrokeInputBatch): SerializedStrokeInputBatch {\n val serializedInputs = mutableListOf\u003cSerializedStrokeInput\u003e()\n val scratchInput = StrokeInput()\n\n for (i in 0 until inputs.size) {\n inputs.populate(i, scratchInput)\n serializedInputs.add(\n SerializedStrokeInput(\n x = scratchInput.x,\n y = scratchInput.y,\n timeMillis = scratchInput.elapsedTimeMillis.toFloat(),\n pressure = scratchInput.pressure,\n tiltRadians = scratchInput.tiltRadians,\n orientationRadians = scratchInput.orientationRadians,\n strokeUnitLengthCm = scratchInput.strokeUnitLengthCm,\n )\n )\n }\n\n val toolType =\n when (inputs.getToolType()) {\n InputToolType.STYLUS -\u003e SerializedToolType.STYLUS\n InputToolType.TOUCH -\u003e SerializedToolType.TOUCH\n InputToolType.MOUSE -\u003e SerializedToolType.MOUSE\n else -\u003e SerializedToolType.UNKNOWN\n }\n\n return SerializedStrokeInputBatch(\n toolType = toolType,\n strokeUnitLengthCm = inputs.getStrokeUnitLengthCm(),\n inputs = serializedInputs,\n )\n }\n\n private fun deserializeStroke(serializedStroke: SerializedStroke): Stroke? {\n val inputs = deserializeStrokeInputBatch(serializedStroke.inputs) ?: return null\n val brush = deserializeBrush(serializedStroke.brush) ?: return null\n return Stroke(brush = brush, inputs = inputs)\n }\n\n private fun deserializeBrush(serializedBrush: SerializedBrush): Brush {\n val stockBrushFamily = enumToStockBrush[serializedBrush.stockBrush] ?: StockBrushes.markerV1\n\n return Brush.createWithColorLong(\n family = stockBrushFamily,\n colorLong = serializedBrush.color,\n size = serializedBrush.size,\n epsilon = serializedBrush.epsilon,\n )\n }\n\n private fun deserializeStrokeInputBatch(\n serializedBatch: SerializedStrokeInputBatch\n ): StrokeInputBatch {\n val toolType =\n when (serializedBatch.toolType) {\n SerializedToolType.STYLUS -\u003e InputToolType.STYLUS\n SerializedToolType.TOUCH -\u003e InputToolType.TOUCH\n SerializedToolType.MOUSE -\u003e InputToolType.MOUSE\n else -\u003e InputToolType.UNKNOWN\n }\n\n val batch = MutableStrokeInputBatch()\n\n serializedBatch.inputs.forEach { input -\u003e\n batch.addOrThrow(\n type = toolType,\n x = input.x,\n y = input.y,\n elapsedTimeMillis = input.timeMillis.toLong(),\n pressure = input.pressure,\n tiltRadians = input.tiltRadians,\n orientationRadians = input.orientationRadians,\n )\n }\n\n return batch\n }\n\n fun serializeStrokeToEntity(stroke: Stroke): StrokeEntity {\n val serializedBrush = serializeBrush(stroke.brush)\n val serializedInputs = serializeStrokeInputBatch(stroke.inputs)\n return StrokeEntity(\n brushSize = serializedBrush.size,\n brushColor = serializedBrush.color,\n brushEpsilon = serializedBrush.epsilon,\n stockBrush = serializedBrush.stockBrush,\n strokeInputs = gson.toJson(serializedInputs),\n )\n }\n\n fun deserializeEntityToStroke(entity: StrokeEntity): Stroke {\n val serializedBrush =\n SerializedBrush(\n size = entity.brushSize,\n color = entity.brushColor,\n epsilon = entity.brushEpsilon,\n stockBrush = entity.stockBrush,\n )\n\n val serializedInputs =\n gson.fromJson(entity.strokeInputs, SerializedStrokeInputBatch::class.java)\n\n val brush = deserializeBrush(serializedBrush)\n val inputs = deserializeStrokeInputBatch(serializedInputs)\n\n return Stroke(brush = brush, inputs = inputs)\n }\n\n fun brushToString(brush: Brush): String {\n val serializedBrush = serializeBrush(brush)\n return gson.toJson(serializedBrush)\n }\n\n fun stringToBrush(jsonString: String): Brush {\n val serializedBrush = gson.fromJson(jsonString, SerializedBrush::class.java)\n return deserializeBrush(serializedBrush)\n }\n\n }"]]