Índice de contenido
- ¿Qué es un paquete Flutter?
- Caso de Estudio: Un Paquete de Integración con Inteligencia Artificial
- Gestión de Dependencias: Repositorios Privados y Overrides
- El uso de dependency_overrides en Desarrollo Local
- Privacidad y Exposición de Elementos
- ¿Cómo sabe Dart que ese archivo es el que "manda"?
- Usar el paquete en tu app
A medida que una aplicación de Flutter crece, surge la necesidad de modularizar el código y reutilizar funcionalidades en múltiples proyectos. El ecosistema de Flutter ofrece diferentes mecanismos para empaquetar código. Al iniciar la creación de un nuevo proyecto en entornos como Visual Studio Code mediante el comando Flutter: New Project, la interfaz despliega varias opciones que conviene diferenciar:
- Flutter Application / Empty Application: Es la opción estándar para construir una aplicación final. La versión Empty genera un lienzo en blanco sin el código del contador predeterminado.
- Flutter Module: Diseñado para la integración de Flutter en aplicaciones nativas existentes (Android o iOS). Permite añadir pantallas o componentes de Flutter sobre una arquitectura nativa raíz sin alterar su código base.
- Flutter Plugin: Estructura que permite crear una librería especializada, pero con la particularidad de requerir e incluir código nativo (Kotlin/Java en Android o Swift/Objective-C en iOS) para comunicarse con el hardware o APIs del sistema operativo.
- Flutter Package: Es la opción adecuada para construir librerías escritas exclusivamente en Dart. Permite encapsular lógica de negocio, utilidades o componentes visuales (widgets) personalizados para compartirlos fácilmente entre múltiples aplicaciones de Flutter.

Nos quedamos en que conocemos Cómo actualizar un proyecto o aplicación existente en Flutter
¿Qué es un paquete Flutter?
Un paquete Flutter es una colección de código prediseñada que agrega funcionalidades y características a su aplicación Flutter. Actúan como bloques de construcción, ahorrándole tiempo y esfuerzo al permitirle reutilizar el código existente en lugar de escribirlo todo desde cero. Estos paquetes pueden incluir widgets, bibliotecas, utilidades, activos y más, lo que permite a los desarrolladores integrarlos fácilmente en sus proyectos de Flutter.
También hay una distinción en Flutter entre "paquetes" y "complementos". Los paquetes generalmente se refieren a una colección de código Dart puro, mientras que los complementos se refieren a paquetes con código nativo dentro de ellos. Tenga en cuenta que la palabra "paquete" en esta serie abarca tanto paquetes como complementos, a menos que se indique específicamente lo contrario.
Se puede agregar un paquete a una aplicación a través del archivo pubspec.yaml (que se verá más adelante) a través de la sección de dependencias:
dependencies:
stream_chat_flutter: Caso de Estudio: Un Paquete de Integración con Inteligencia Artificial
Para ilustrar la utilidad de un paquete, analizaremos un caso práctico: un conector genérico para proveedores de Modelos de Lenguaje Grande (LLMs) como Gemini o Perplexity. En una aplicación móvil destinada al aprendizaje de idiomas (como la práctica de inglés), el software debe conectarse a estas APIs mediante prompts específicos y mapear las respuestas en estructuras de datos estrictas.
Dado que la lógica de conexión, el manejo de URLs, la validación de API Keys y la configuración de los parámetros del modelo (como el system prompt) son funcionalidades genéricas, encapsularlas en un Package independiente permite reutilizar este motor de inteligencia artificial en otros proyectos (por ejemplo, una aplicación de notas que requiera resumir texto), sin duplicar el código fuente.
El servicio encapsulado expone métodos abstractos y concretos para interactuar con los endpoints, abstrayendo por completo a la aplicación principal de la complejidad de la petición HTTP o del SDK de la IA.
lib\src\services\ia_api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/ai_provider.dart';
import '../models/ai_request_config.dart';
//region API Constants
const String _geminiApiUrl =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
const String _perplexityApiUrl = "https://api.perplexity.ai/chat/completions";
const String _openAiApiUrl = "https://api.openai.com/v1/chat/completions";
const String _grokApiUrl = "https://api.x.ai/v1/chat/completions";
const String _perplexityModel = "sonar-pro";
const String _openAiModel = "gpt-4o-mini";
const String _grokModel = "grok-3-mini";
Future<List<T>> executeRequest<T>(
AiRequestConfig<T> config,
String apiKey,
) async {
final rawItems = await callAi(
provider: config.provider,
apiKey: apiKey,
userPrompt: config.buildPrompt(),
outputSchema: config.outputSchema,
systemContent: config.systemContent,
);
return rawItems.map(config.parseItem).toList();
}Aunque, puedes también implementar widgets:
lib\src\widgets\api_key_dialog.dart
const ApiKeyDialog({
super.key,
this.apiKey,
required this.onSave,
this.titleAdd = 'Add API Key',
this.titleEdit = 'Edit API Key',
this.labelApiType = 'API Type',
this.labelApiKey = 'API Key',
this.labelCancel = 'Cancel',
this.labelSave = 'Save',
this.labelWait = 'Validating key...',
this.errorEmptyKey = 'Please enter a key',
});
@override
State<ApiKeyDialog> createState() => _ApiKeyDialogState();
}
class _ApiKeyDialogState extends State<ApiKeyDialog> {
final _formKey = GlobalKey<FormState>();
final _keyController = TextEditingController();
AiProvider _selectedProvider = AiProvider.gemini;
bool _isValidating = false;Gestión de Dependencias: Repositorios Privados y Overrides
Un paquete no requiere ser publicado obligatoriamente de forma abierta en el repositorio oficial de pub.dev. Para desarrollos comerciales o privados, es común alojar el código en un repositorio privado de GitHub. Para consumir este paquete en tus aplicaciones, debes declararlo en el archivo pubspec.yaml apuntando directamente a la ruta Git:
# Consumo de un paquete privado desde un repositorio Git
dependencies:
ia_service:
git:
url: git@github.com:tu-usuario/ia_service.git
ref: main
El uso de dependency_overrides en Desarrollo Local
Trabajar directamente con repositorios remotos durante la fase de desarrollo es ineficiente, ya que obligaría a realizar un git push y un flutter pub get por cada modificación introducida en el paquete. Para solucionar esto, Flutter permite reescribir temporalmente la ruta de la dependencia hacia una ubicación local en tu máquina de desarrollo utilizando dependency_overrides:
# Configuración en la aplicación principal durante el desarrollo local
dependencies:
ia_service:
git:
url: git@github.com:tu-usuario/ia_service.git
ref: main
dependency_overrides:
ia_service:
path: ../ia_service
Al situar la carpeta del paquete al mismo nivel que el proyecto de la aplicación (una al lado de la otra), puedes modificar simultáneamente el paquete y la app. Los cambios se reflejarán de inmediato en el entorno local sin alterar el control de versiones remoto.
Privacidad y Exposición de Elementos
Una diferencia clave en la estructura de un paquete frente a una aplicación tradicional es el manejo de la visibilidad de sus clases, funciones y widgets. La regla de oro en Dart determina que el único archivo expuesto públicamente al exterior es aquel que se encuentra en la raíz de la carpeta lib/.
Por convención, el resto de la lógica (servicios concretos, modelos internos, interfaces de diálogo) se almacena dentro de subcarpetas (por ejemplo, lib/src/, lib/models/, etc.). Estas carpetas se consideran estrictamente privadas para la aplicación consumidora.
ai_service/
└── lib/
├── ai_service.dart <-- ¡PÚBLICO! Aquí configuras los exports con código Dart.
└── src/ <-- ¡PRIVADO! Nadie de fuera puede tocar esto directamente.
├── models/
├── services/
└── widgets/Para exponer componentes selectivos hacia el exterior, el archivo raíz de la carpeta lib/ (por ejemplo, lib/ia_service.dart) actúa como un archivo "barril", utilizando la palabra reservada library y directivas export:
// Archivo raíz: lib/ia_service.dart
library ia_service;
// Exponer los elementos públicos para las aplicaciones consumidoras
export 'models/provider_model.dart';
export 'services/ai_connector_base.dart';
export 'widgets/api_key_dialog.dart';
De esta manera, la aplicación que consume el paquete solo necesitará realizar un único import (import 'package:ia_service/ia_service.dart';) para acceder a todas las clases y widgets autorizados, protegiendo los componentes auxiliares o de uso interno del paquete.
¿Cómo sabe Dart que ese archivo es el que "manda"?
Por la declaración de la librería que pusiste en la primera línea de tu archivo:
library ai_service;
Usar el paquete en tu app
Una vez registrado el paquete, para usarlo es tan sencillo como si se tratase de una clase o recurso local al proyecto:
import 'package:ai_service/ai_service.dart';
void _showApiKeyDialog({ApiKey? apiKey}) {
showDialog(
context: context,
builder: (context) => ApiKeyDialog(
apiKey: apiKey,
titleAdd: 'add_api_key'.tr(),
titleEdit: 'edit_api_key'.tr(),