Uff, esto de los colores hexadecimales en Flutter me costó un poco al principio, lo admito. Recuerdo que hace unos años, en un proyecto donde estábamos portando una app legacy a Flutter, el diseñador nos pasó una paleta de colores enorme, todos en formato hex como #b74093. Mi primera reacción fue pensar: “Ya, esto es un string, lo parseo y listo”. ¡Error garrafal!
Me puse a escribir una función, después una clase HexColor que hacía un int.parse(hexColor.replaceAll('#', ''), radix: 16) y lo metía en el constructor de Color. Funcionaba, sí, pero cada vez que el widget se reconstruía, esa función se ejecutaba, parseando la misma cadena una y otra vez. Se veía lento y la verdad, no me convencía para nada la elegancia de la solución. Sentía que estaba forzando algo simple a ser complejo, pura sobreingeniería. De hecho, me generaba un poco de frustración ver tanto código para algo que debería ser directo.
El Problema Base: Flutter no Entiende Hex Strings Directamente
La clase Color de Flutter, en su constructor principal, espera un entero de 32 bits que represente el valor ARGB (Alpha, Red, Green, Blue). No le puedes pasar un String como "#b74093" y esperar que lo entienda. Necesitas convertir ese string hexadecimal en un valor entero.
Además, y esto es clave, el valor de la opacidad (Alpha) siempre tiene que estar presente. Si tu diseñador te da un hex de seis dígitos (RGB), asumes opacidad total. Esa opacidad total en hexadecimal es FF, que en decimal es 255. Entonces, un color como #b74093 se convierte en 0xFFB74093.
El prefijo 0x indica que es un número hexadecimal. El FF que le sigue es el valor de la opacidad. Y luego, B74093 es el código de color RGB que tenías. Así de simple. El resultado es un entero que Flutter entiende perfectamente.
// Opacidad 100% (FF) para el color #b74093
const miColorPrimario = Color(0xFFB74093);
// Si quisieras 50% de opacidad (80 en hex)
const miColorTransparente = Color(0x80B74093);
Ojo que la segunda const en la asignación es opcional en Dart moderno. Basta con la primera.
Mi Solución Favorita: Constantes y Código Limpio
Para la mayoría de los casos, sobre todo cuando los colores son parte de tu tema y son estáticos, esta es la solución que yo uso. Es directa, no hay parseo en tiempo de ejecución y lo más importante: puedes declararlos como const. Esto es un win-win en Flutter, porque los widgets que usan estas constantes se pueden reconstruir de forma más eficiente. No me digas que no te gusta la idea de que tu app sea más rápida sin hacer malabares.
En mi experiencia, la mayor parte del tiempo los colores son fijos. Los defines en tu archivo colors.dart o theme_data.dart y los usas por toda la aplicación. Hacerlo con const Color(0xFF...) es lo más eficiente y claro.
¿Cuándo tiene sentido usar una extensión o una función de parseo?
Ahora, ¿qué pasa si los colores vienen de una API? O de una base de datos? Ahí sí que no puedes usar const, porque el valor no es conocido en tiempo de compilación. En esos escenarios, una función que parsee el string sí tiene sentido. Para esto, Dart 2.6+ introdujo las extensiones, que son bastante útiles para añadir funcionalidad a tipos existentes.
Puedes crear una extensión para Color, o incluso una clase utilidad que tenga el método de parseo. Yo prefiero la extensión porque se siente más integrada. Aquí un ejemplo adaptado de lo que me funcionó bien en su momento, pero ya pulido con las extensiones de Dart:
extension HexColor on Color {
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
String toHex({
bool leadingHashSign = true,
bool includeHash = false, // Ignored if leadingHashSign is true
}) =>
'${leadingHashSign ? '#' : ''}'
'${alpha.toRadixString(16).padLeft(2, '0')}'
'${red.toRadixString(16).padLeft(2, '0')}'
'${green.toRadixString(16).padLeft(2, '0')}'
'${blue.toRadixString(16).padLeft(2, '0')}';
}
Con esta extensión, podrías hacer:
// Si 'miColorString' viene de una API o es dinámico
Color colorDinamico = HexColor.fromHex("#b74093");
Fíjate que en este caso, colorDinamico no puede ser const. Esa es la principal desventaja respecto a la primera opción. Cada vez que llamas a HexColor.fromHex(), se ejecuta la lógica de parseo.
Un Detalle sobre Propiedades Deprecadas
Me gustaría añadir algo que vi en una de las respuestas de Stack Overflow y que me parece importante. A partir de Flutter 3.27, propiedades como alpha, red, green y blue de la clase Color están deprecadas. Ahora devuelven valores double en lugar de int, lo que requiere un poco más de manejo si quieres obtener los componentes individuales. Esto refuerza aún más la idea de que la forma preferida de manejar colores es a través del valor entero ARGB directo.
¿Cuál usar entonces, Jorge?
Mi recomendación es clara y directa, como me gusta a mí. Si tus colores son fijos y conocidos en tiempo de compilación, usa siempre la notación const Color(0xFF...). Es la forma más eficiente, limpia y Flutter-friendly de manejar los colores estáticos de tu app. Tendrás un rendimiento óptimo y un código fácil de leer.
Si, y solo si, tus colores vienen de una fuente externa y son dinámicos (por ejemplo, desde una API o un tema configurable por el usuario que se carga en runtime), entonces sí, considera una extensión como la que te mostré. Pero piensa bien si realmente la necesitas, porque muchas veces uno empieza con algo dinámico y termina siendo estático, generando complejidad innecesaria. No te compliques la vida cuando no es necesario.
Para mí, el 90% de las veces la solución const Color(0xFF...) es la indicada y la que yo implemento.