Renderizar contenido HTML en Flutter a widgets
Índice de contenido
Frecuentemente, es necesario mostrar contenido HTML dentro de una aplicación Flutter, puedes usar un WebView en Flutter o el esquema que te presento en esta publicación. Un buen ejemplo de esta necesidad te lo puedo dar yo mismo: he estado creando una aplicación en Flutter para este blog (que también incluye la app de la Academia).
Volviendo al blog, el contenido es inherentemente HTML. Esto que estás leyendo ahora mismo es contenido HTML generado por un plugin de JavaScript de tipo WYSIWYG (What You See Is What You Get). Dado que este contenido se consume desde la app en Flutter mediante una REST API, necesito encontrar una forma fiable de presentar este string de HTML, ya sea utilizando un webview o alguna herramienta similar.
En esta entrada veremos un plugin que permite mostrar contenido HTML en Flutter mediante un plugin al no existir una solución que forme parte del core de Flutter.
Si estás construyendo una aplicación en Flutter y necesitas renderizar contenido HTML, puedes emplear el paquete flutter_html. Este paquete te permite mostrar contenido HTML dentro de tus widgets de Flutter. A continuación, te explico cómo hacerlo:
Instalación:
Agrega
flutter_htmla las dependencias de tu archivopubspec.yamlejecutando el siguiente comando:flutter pub add flutter_htmlLuego, obtén las dependencias con:
flutter pub getUso:
Importa el paquete en tu archivo de código:
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/style.dart'; // Para usar estilos CSSUtiliza el widget
Htmlpara renderizar el contenido HTML:Dart
Html( data: /* Tu fuente HTML */, style: { /* Estilos CSS (no son CSS reales) */ 'h1': Style(color: Colors.red), 'p': Style(color: Colors.black87, fontSize: FontSize.medium), 'ul': Style(margin: EdgeInsets.symmetric(vertical: 20)), }, )En el ejemplo anterior, puedes proporcionar tu propio HTML en lugar de
/* Tu fuente HTML */. Los estilos se aplican utilizando un mapa donde las claves son los nombres de las etiquetas HTML y los valores son objetosStyle.Ejemplo Completo:
Aquí tienes un ejemplo completo de cómo usar
flutter_html:import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/style.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Mi App con HTML', home: HomePage(), ); } } class HomePage extends StatelessWidget { final _htmlContent = """ <div> <h1>Este es un título</h1> <p>Este es un <strong>párrafo</strong>.</p> <p>Me gustan los <i>perros</i></p> <ul> <li>Elemento de lista 1</li> <li>Elemento de lista 2</li> <li>Elemento de lista 3</li> </ul> <img src='https://www.kindacode.com/wp-content/uploads/2020/11/my-dog.jpg' /> </div> """; const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Mi App con HTML')), body: SafeArea( child: SingleChildScrollView( child: Html( data: _htmlContent, style: { 'h1': Style(color: Colors.red), 'p': Style(color: Colors.black87, fontSize: FontSize.medium), 'ul': Style(margin: EdgeInsets.symmetric(vertical: 20)), }, ), ), ), ); } }
En este ejemplo, se muestra el contenido HTML con estilos personalizados, es muy interesante la parte que permite personalizar el CSS del HTML renderizado, con lo cual, podemos mostrar un estilo personalizado por nosotros y no el estilo por defecto en HTML.
HTML a Widgets
Quiero mostrarte una posible variante, o mejor dicho, una opción más simple si deseas representar un contenido HTML y hago énfasis en un contenido HTML, en lugar de emplear los WebViews en Flutter con todos los problemas que me trajeron (te dejo el video aquí en la siguiente tarjeta, en caso de que también quieras verlo).
Encontré una forma mucho más sencilla y directa que te va a funcionar en cualquier tipo de aplicación en Flutter que desees implementar. Me refiero a que funcionará si la quieres lanzar para iOS, si la quieres lanzar para Android, si la quieres ejecutar en Linux, en web... tú me entiendes, en cualquiera de las plataformas que permite Flutter.
Traduce el HTML a Widgets
Traducir tus contenidos HTML a widgets nativos, ya con eso pude como quien dice resolver el problema que el problemón que tenía antes por supuesto voy haciendo aquí una aclaratoria en caso de que todavía no se entienda muy bien:
import 'package:html/parser.dart' as htmlParser;
import 'package:html/dom.dart' as html_dom;
***
List<Widget> showSectionBookByHTML(
String html,
String tokenUser,
int bookSectionId,
Function noteAdd,
BuildContext context, [
Map<String, BookSectionNoteModel> bookSectionNotesModel = const {},
double sizeText = 1.0,
]) {
List<Widget> widgets = [];
List<html_dom.Element> elementsHtml = parseHTMLContent(html);
for (var element in elementsHtml) {
print(element.localName);
switch (element.localName) {
case 'h1':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: Text(
element.text,
style: TextStyle(
fontSize: 45 * sizeText,
fontFamily: 'IBMPlexMono',
),
),
),
);
break;
case 'h2':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: SelectableText(
element.text,
style: TextStyle(
fontSize: 35 * sizeText,
fontFamily: 'IBMPlexMono',
),
),
),
);
break;
case 'h3':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: SelectableText(
element.text,
style: TextStyle(
fontSize: 30 * sizeText,
fontFamily: 'IBMPlexMono',
),
),
),
);
break;
case 'h4':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: SelectableText(
element.text,
style: TextStyle(fontSize: 25 * sizeText),
),
),
);
break;
case 'h5':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: SelectableText(
element.text,
style: TextStyle(fontSize: 20 * sizeText),
),
),
);
break;
case 'h6':
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: SelectableText(
element.text,
style: TextStyle(fontSize: 18 * sizeText),
),
),
);
break;
case 'ul':
if (element.children.isNotEmpty) {
element.children.forEach((e) {
widgets.add(
_hightlightText(
"⚬ ${e.text}",
bookSectionNotesModel,
element.attributes['id'] ?? '',
"",
16 * sizeText,
tokenUser,
bookSectionId,
noteAdd,
context,
),
);
});
widgets.add(SizedBox(height: 15));
}
break;
case 'p':
// img
if (element.children.isNotEmpty &&
element.children[0].localName == 'img') {
String? src = element.children[0].attributes['src'];
if (src != null) {
widgets.add(
Padding(
padding: const EdgeInsets.only(top: 15, bottom: 15),
child: Image.network(baseUrlAcademy + src),
),
);
}
}
//*** p
// child
var idChild2 = '';
if (element.children.isNotEmpty) {
// posiblemente un span dentro del p
idChild2 = element.children[0].attributes['id'] ?? '';
}
widgets.add(
Padding(
padding: const EdgeInsets.only(bottom: 15),
child: _hightlightText(
element.text,
bookSectionNotesModel,
element.attributes['id'] ?? '',
idChild2,
16 * sizeText,
tokenUser,
bookSectionId,
noteAdd,
context,
),
),
);
break;
case 'pre':
widgets.add(
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: CodeView(code: element.text),
),
);
break;
}
}
return widgets;
}
List<html_dom.Element> _parseHTMLContent(String html) {
var document = html_parser.parse(html);
var body = document.body!.children;
} Como puedes ver, recibes el contenido HTML y lo conviertes en bases a las etiquetas que tenga el contenido al widget que más se asemeje. De esta forma, al emplear widgets nativos, puedes mostrar esta página en cualquier plataforma soportada por Flutter.
Ventajas
Las ventajas que nos trae este esquema con respecto al visor no solamente el soporte a cualquier plataforma, si no, que ahora puedes hacer el contenido interactivo, agregando opciones como en mi caso es poder darle formato al código, selección de texto y en resumen, cualquier funcionalidad que permita Flutter sobre los widgets, operaciones que no puedes implementar de manera nativa en un visor.
Siguiente paso, retomemos las pasarelas de pago, y conoce como puedes implementar una pasarela de pagos en la Google Play con Flutter para vender servicios en esta tienda.
Acepto recibir anuncios de interes sobre este Blog.
Veremos como podemos mostrar contenido HTML en Flutter y como crear mecanismo sobre como puedes mostrar contenido HTML en TODOS las plataformas soportadas por Flutter.