flutter_quill: Editor WYSIWYG con texto enriquecido en Flutter (Delta y HTML)

- Andrés Cruz - EN In english

Video thumbnail

En este artículo te muestro cómo integrar un editor de tipo WYSIWYG (What You See Is What You Get) en Flutter usando el paquete flutter_quill. A diferencia de los editores web clásicos como CKEditor —que trabajan directamente con HTML—, flutter_quill utiliza un formato interno llamado Delta para representar el contenido enriquecido. Entender esta diferencia es clave para implementarlo correctamente en tu aplicación Flutter.

Anteriormente aprendimos a crear un fondo o background animado en Flutter

Funcionalidades del plugin flutter_quill

El paquete flutter_quill te ofrece un editor de texto enriquecido listo para usar, con una barra de herramientas configurable. Entre las funcionalidades más destacadas puedes encontrar:

  • Tipografía: Puedes cambiar la fuente directamente desde la barra de control del editor.
  • Tamaño de fuente: Se puede ajustar indicando un valor numérico.
  • Bloques de código: Permite insertar fragmentos de código con formato especial y diferenciado visualmente.
  • Negritas, cursivas y subrayado: Opciones de formato de texto básico disponibles en la barra de herramientas.
  • Listas ordenadas y no ordenadas: Para estructurar el contenido de forma clara.
  • Color de texto y fondo: Personalización visual directamente desde el editor.

En resumen, flutter_quill te permite crear contenido enriquecido sobre un campo de texto de manera sencilla y sin necesidad de manipular HTML directamente dentro de Flutter. Lo que ves en el editor es exactamente lo que se va a renderizar en tu aplicación.

Puedes encontrar el paquete y su documentación oficial en pub.dev: https://pub.dev/packages/flutter_quill

Instalación de flutter_quill

La instalación sigue el proceso estándar de cualquier paquete en Flutter. Añade la dependencia en tu archivo pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  flutter_quill: ^10.0.0 # Verifica la versión más reciente en pub.dev

Luego ejecuta flutter pub get para descargar el paquete. Una vez instalado, importa el paquete en el archivo Dart donde vayas a usarlo y crea una instancia del controlador principal:

import 'package:flutter_quill/flutter_quill.dart';

// Instancia del controlador del editor
final QuillController _controllerHtmlEditor = QuillController.basic();

@override
void dispose() {
  // Siempre libera el controlador para evitar fugas de memoria
  _controllerHtmlEditor.dispose();
  super.dispose();
}

Un punto importante: recuerda siempre hacer el dispose() del controlador cuando ya no lo necesites, al igual que haces con animaciones o controladores de texto (TextEditingController). De lo contrario, puedes generar fugas de memoria en tu aplicación.

Inicialización del contenido existente

Si necesitas cargar contenido previo en el editor —por ejemplo, un comentario o un artículo guardado en la base de datos—, debes inicializar el controlador con ese contenido. Dado que el editor trabaja internamente con el formato Delta, si tu contenido está almacenado como HTML deberás convertirlo primero. Para ello, puedes usar el método auxiliar htmlToDelta() que veremos más adelante:

// c es el string HTML obtenido desde la base de datos o la API
_controllerHtmlEditor.document = Document.fromDelta(htmlToDelta(c));

Esta conversión es necesaria únicamente si almacenas tu contenido como HTML. Si en cambio guardas el Delta directamente en tu base de datos (como JSON), puedes cargar el contenido sin conversión usando Document.fromJson().

Configuración y uso del plugin en la UI

La integración en la interfaz de usuario se divide en dos bloques bien diferenciados, lo que al principio puede resultar un poco confuso pero tiene mucho sentido una vez lo entiendes:

  • Barra de herramientas (QuillSimpleToolbar): Es la parte superior del editor. Aquí se configuran los botones disponibles: colores, fondos, listas, bloques de código, etc. Se puede personalizar pasando un objeto QuillSimpleToolbarConfigurations.
  • Área de edición (QuillEditor): Es el campo de texto enriquecido propiamente dicho. Se puede envolver en un Container para controlar su apariencia, altura, bordes e interlineado.

Ambos bloques comparten la misma instancia del QuillController, que actúa como el puente entre la barra de herramientas y el área de contenido. Este es el código básico para montar el editor:

QuillSimpleToolbar(
  controller: _controllerHtmlEditor,
  configurations: const QuillSimpleToolbarConfigurations(),
),
Container(
  padding: const EdgeInsets.all(5),
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(5),
    border: Border.all(color: Theme.of(context).primaryColor),
    color: appTheme.darkTheme ? Colors.black26 : Colors.black12,
  ),
  height: 300,
  child: QuillEditor.basic(
    controller: _controllerHtmlEditor,
    configurations: const QuillEditorConfigurations(),
  ),
),

En versiones más recientes del paquete, como la v11, la configuración de ciertos comportamientos como el manejo de links se hace a través de QuillEditorConfig y el parámetro onLaunchUrl. Si usas links en tu editor, asegúrate de implementar ese callback para que los enlaces funcionen correctamente al tocarlos.

Delta en Flutter: el formato interno de flutter_quill para texto enriquecido

Video thumbnail

Antes de entrar en las conversiones, es importante que entiendas qué es Delta y por qué existe. Delta es el formato nativo con el que flutter_quill —y la librería Quill.js de la que deriva— representa el contenido enriquecido internamente. Se trata de una estructura de datos en formato JSON, compuesta por una lista de operaciones de inserción con atributos opcionales.

Es similar al HTML en el sentido de que describe texto con estilos aplicados, pero tiene su propia sintaxis y lógica. No son intercambiables de forma directa, aunque existen paquetes de conversión entre ambos formatos.

Conversión entre Delta y HTML en Flutter

En proyectos donde la aplicación web ya existe y almacena contenido como HTML —por ejemplo, en mi caso la plataforma de Academia, donde el editor web es CKEditor—, la app Flutter necesita poder leer ese HTML y mostrarlo en el editor. Para los comentarios de cada clase, por ejemplo, necesito que el usuario pueda escribir con negritas, encabezados y listas, tal como lo haría en la web.

El problema es que flutter_quill no trabaja con HTML nativo, sino con Delta. Por eso, la conversión entre ambos formatos es esencial en este tipo de proyectos multiplataforma.

¿Por qué Delta no es igual al HTML?

La traducción entre Delta y HTML no es perfecta ni bidireccional al 100%. Aquí tienes un ejemplo claro de cómo el mismo contenido se representa en cada formato:

// Formato Delta (JSON)
[
  {"insert": "Title 1"},
  {"insert": "\n", "attributes": {"header": 2}},
  {"insert": "Content", "attributes": {"bold": true}},
  {"insert": "\n"}
]

// Equivalente en HTML
<h2>Title 1</h2>
<p><strong>Content</strong></p>

Como puedes ver, Delta usa objetos insert con attributes para aplicar estilos, mientras que HTML usa etiquetas semánticas como <h2> o <strong>. Hay casos en los que la conversión pierde matices —por ejemplo, ciertos estilos CSS no tienen un equivalente directo en Delta—, pero para la mayoría de casos de uso cotidiano (negritas, encabezados, listas, links) funciona perfectamente.

De Delta a HTML: paquete vsc_quill_delta_to_html

Para convertir un Delta a HTML, utilizo el paquete vsc_quill_delta_to_html. Aquí tienes la función auxiliar que uso en el proyecto:

import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';

/// Convierte una lista de operaciones Delta a un string HTML.
/// [delta] es la lista de mapas que representan las operaciones del documento.
String deltaToHTML(List<Map<String, dynamic>> delta) {
  final converter = QuillDeltaToHtmlConverter(delta);
  return converter.convert();
}

Simplemente pasas el Delta —que es una lista de mapas— al QuillDeltaToHtmlConverter y llamas a convert(). El resultado es un string HTML listo para guardar en la base de datos o para enviar a tu API.

De HTML a Delta: paquete flutter_quill_delta_from_html

Para el caso inverso —convertir HTML a Delta para poder editar contenido existente—, uso el paquete flutter_quill_delta_from_html:

import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';

/// Convierte un string HTML a formato Delta para usarlo en flutter_quill.
/// Si la conversión falla, retorna un documento vacío para evitar errores en el editor.
Delta htmlToDelta(String html) {
  try {
    return HtmlToDelta().convert(html);
  } catch (e) {
    // Fallback seguro: retorna un documento Delta con un párrafo vacío
    return Document.fromJson([
      {'insert': '\n'},
    ]).toDelta();
  }
}

El try/catch es importante: si el HTML viene malformado o con estructuras que el conversor no reconoce, el editor podría lanzar una excepción y romperse. El fallback retorna un documento Delta vacío y válido, lo que garantiza que el editor siempre se muestre correctamente aunque el contenido no se pueda convertir.

El uso en código es directo:

// Conversión de HTML a Delta
final delta = htmlToDelta(htmlString);

// Conversión de Delta a HTML
final html = deltaToHTML(deltaList);

Flujo completo: edición de contenido HTML con flutter_quill

Para que todo quede claro, te dejo el flujo completo de extremo a extremo que uso en el proyecto de Academia:

Al cargar el contenido para editar:

  • Paso 1: Obtener el contenido HTML desde la base de datos (a través de la API).
  • Paso 2: Convertir ese HTML a Delta usando htmlToDelta(htmlString).
  • Paso 3: Asignar el Delta al controlador: _controllerHtmlEditor.document = Document.fromDelta(delta).

Al guardar o enviar el contenido editado:

  • Paso 1: Obtener el Delta actual del editor: _controllerHtmlEditor.document.toDelta().toJson().
  • Paso 2: Convertir el Delta a HTML usando deltaToHTML(deltaList).
  • Paso 3: Enviar el HTML resultante a la API para que lo guarde en la base de datos.

De esta manera, la aplicación Flutter siempre maneja el formato Delta internamente, mientras que la comunicación con el backend se hace en HTML —el formato que el servidor ya conoce y con el que funciona el editor web.

¿Por qué es necesario este flujo de conversión?

Este flujo es necesario porque la plataforma de Academia nació originalmente como una aplicación web, donde el contenido se almacena como HTML. Al extender la plataforma a Flutter, fue necesario establecer este puente de conversión para mantener la compatibilidad con el contenido existente sin tener que migrar toda la base de datos ni cambiar el backend.

Si estás arrancando un proyecto nuevo en Flutter sin necesidad de interoperabilidad con la web, podrías optar por guardar el Delta directamente como JSON y evitar las conversiones por completo. Pero si tu escenario implica compartir contenido entre una app web y una app Flutter, este es el camino.

Aprende ahora a generar un PDF en Flutter

Aprende a usar flutter_quill para crear un editor WYSIWYG en Flutter. Configuración del QuillController, QuillSimpleToolbar, y conversión entre Delta y HTML con los paquetes vsc_quill_delta_to_html y flutter_quill_delta_from_html.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.