Gestión de Archivos y Solicitudes de Red en Flutter con Dart

Operaciones con Archivos

La biblioteca IO de Dart incluye clases para la lectura y escritura de archivos, formando parte estándar del lenguaje. Esto permite que tanto scripts bajo el Dart VM como aplicaciones Flutter utilicen la misma interfaz para manipular archivos.

Directorios de la Aplicación

Las rutas de almacenamiento difieren entre Android e iOS. El complemetno PathProvider ofrece una forma transparente de acceder a ubicaciones comunes en el sistema de archivos del dispositivo, soportando dos posiciones principales:

  • Directorio temporal: Se obtiene con getTemporaryDirectory(). Representa una caché que el sistema puede borrar en cualquier momento. En iOS equivale a NSTemporaryDirectory(), y en Android a getCacheDir().
  • Directorio de documentos: Se accede mediante getApplicationDocumentsDirectory(). Aquí se almacenan archivos privados de la app, eliminados solo si se desinstala la aplicación. En iOS corresponde a NSDocumentDirectory, y en Android al directorio AppData.

Para integrar el complemento, se agrega la dependencia en pubspec.yaml:

dependencies:
  path_provider: ^2.0.0

Posteriormente, se ejecuta flutter pub get para instalarlo.

Ejemplo de uso para persistir un contador:

import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

class PersistenciaDatos extends StatefulWidget {
  PersistenciaDatos({Key? key}) : super(key: key);

  @override
  _PersistenciaDatosState createState() => _PersistenciaDatosState();
}

class _PersistenciaDatosState extends State<PersistenciaDatos> {
  int _visitCount = 0;

  @override
  void initState() {
    super.initState();
    _loadCount().then((valor) {
      setState(() {
        _visitCount = valor;
      });
    });
  }

  Future<File> _obtenerArchivoLocal() async {
    final rutaDirectorio = (await getApplicationDocumentsDirectory()).path;
    return File('$rutaDirectorio/registro_visitas.txt');
  }

  Future<int> _loadCount() async {
    try {
      final archivo = await _obtenerArchivoLocal();
      final contenido = await archivo.readAsString();
      return int.parse(contenido);
    } catch (e) {
      return 0;
    }
  }

  Future<void> _incrementarContador() async {
    setState(() {
      _visitCount++;
    });
    final archivo = await _obtenerArchivoLocal();
    await archivo.writeAsString('$_visitCount');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Persistencia Local')),
      body: Center(child: Text('Total de visitas: $_visitCount')),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementarContador,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Solicitudes HTTP con HttpClient

La biblioteca IO de Dart provee clases para realizar peticiones HTTP. El uso de HttpClient sigue un proceso de cinco pasos:

  1. Instanciar el cliente: HttpClient cliente = HttpClient();
  2. Abrir la conexión y configurar la petición. Se puede usar cualquier método HTTP: ``` var solicitud = await cliente.getUrl(Uri.parse('https://ejemplo.com'));
    
     Parámetros de consulta pueden incluirse al construir el URI. Los encabezados se añaden con `solicitud.headers.add()`, y el cuerpo se envía con `solicitud.add()` codificado en UTF-8.
    
  3. Esperar la respuesta del servidor: ``` HttpClientResponse respuesta = await solicitud.close();
    
     El objeto `respuesta` contiene encabezados y un flujo de datos (stream).
    
  4. Leer el contenido de la respuesta: ``` String cuerpo = await respuesta.transform(utf8.decoder).join();
  5. Cerrar el cliente: ``` cliente.close();
    
    

Biblioteca Dio para HTTP

Dio es una librería robusta para peticiones HTTP en Dart, con soporte para API REST, FormData, interceptores, cancelación de peticiones, gestión de cookies, subida/descarga de archivos y timeouts.

Se recomienda utilizar un singleton para instancias de Dio en aplicaciones con una sola fuente de datos.

Agregar en pubspec.yaml:

dependencies:
  dio: ^5.0.0

Ejemplos de uso:

import 'package:dio/dio.dart';

final clienteDio = Dio();

// Solicitud GET
final respuestaGet = await clienteDio.get('/recurso', queryParameters: {'param': 'valor'});

// Solicitud POST con datos
final respuestaPost = await clienteDio.post('/endpoint', data: {'clave': 'valor'});

// Peticiones concurrentes
final respuestas = await Future.wait([
  clienteDio.get('/info'),
  clienteDio.post('/token'),
]);

// Descarga de archivo
await clienteDio.download('https://ejemplo.com/archivo.zip', '/ruta/local/archivo.zip');

// Envío de FormData
final formData = FormData.fromMap({
  'campo': 'texto',
  'archivo': await MultipartFile.fromFile('./documento.txt'),
});
final respuestaForm = await clienteDio.post('/subir', data: formData);

Comunicación en Tiempo Real con WebSockets

El protocolo WebSocket permite comunicación bidireccional entre cliente y servidor, ideal para actualizaciones en tiempo real. En Flutter, se utiliza el paquete web_socket_channel.

Pasos para implementar WebSockets:

  1. Establecer conexión: ``` import 'package:web_socket_channel/io.dart'; final canal = IOWebSocketChannel.connect('wss://servidor-websocket.com');
  2. Escuchar mensajes del servidor usando StreamBuilder: ``` StreamBuilder( stream: canal.stream, builder: (contexto, instantanea) { if (instantanea.hasData) { return Text('Mensaje: ${instantanea.data}'); } return const Text('Esperando datos...'); }, )
  3. Enviar datos al servidor a través del sink: ``` canal.sink.add('Mensaje desde el cliente');
  4. Cerrar la conexión cuando ya no se necesite: ``` canal.sink.close();
    
    

Ejemplo completo de una interfaz para chat eco:

import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';

class ChatWebSocket extends StatefulWidget {
  @override
  _ChatWebSocketState createState() => _ChatWebSocketState();
}

class _ChatWebSocketState extends State<ChatWebSocket> {
  final _controladorTexto = TextEditingController();
  late IOWebSocketChannel _canal;
  String _ultimoMensaje = 'Conectando...';

  @override
  void initState() {
    super.initState();
    _canal = IOWebSocketChannel.connect('wss://echo.websocket.org');
    _canal.stream.listen((dato) {
      setState(() {
        _ultimoMensaje = 'Eco: $dato';
      });
    }, onError: (error) {
      setState(() {
        _ultimoMensaje = 'Error de conexión';
      });
    });
  }

  void _enviarMensaje() {
    if (_controladorTexto.text.isNotEmpty) {
      _canal.sink.add(_controladorTexto.text);
      _controladorTexto.clear();
    }
  }

  @override
  void dispose() {
    _canal.sink.close();
    _controladorTexto.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('WebSocket Interactivo')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _controladorTexto,
              decoration: const InputDecoration(labelText: 'Escribe un mensaje'),
            ),
            const SizedBox(height: 20),
            Text(_ultimoMensaje),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _enviarMensaje,
        child: const Icon(Icons.send),
      ),
    );
  }
}

Serialización de Modelos JSON

Debido a la desactivación de reflexión en Flutter, la conversión dinámica de JSON a objetos no es directa. Se deben definir clases modelo con métodos de serialización explícitos.

Por ejemplo, una clase Usuario manual:

class Usuario {
  final String nombre;
  final String correo;

  Usuario(this.nombre, this.correo);

  Usuario.fromJson(Map<String, dynamic> mapa)
      : nombre = mapa['nombre'],
        correo = mapa['correo'];

  Map<String, dynamic> toJson() => {
        'nombre': nombre,
        'correo': correo,
      };
}

Generación Automática con json_serializable

La biblioteac json_serializable automatiza la generación de código de serialización, reduciendo errores. Configuración en pubspec.yaml:

dependencies:
  json_annotation: ^4.8.0

dev_dependencies:
  build_runner: ^2.4.0
  json_serializable: ^6.7.0

Clase modelo con anotaciones:

import 'package:json_annotation/json_annotation.dart';
part 'usuario.g.dart';

@JsonSerializable()
class Usuario {
  String nombre;
  String email;

  Usuario(this.nombre, this.email);

  factory Usuario.fromJson(Map<String, dynamic> json) => _$UsuarioFromJson(json);
  Map<String, dynamic> toJson() => _$UsuarioToJson(this);
}

Para generar el código de serialización:

  • Generación única: dart run build_runner build
  • Generación continua (watcher): dart run build_runner watch

Los archivos .g.dart se crean automáticamente y deben incluirse en el control de versiones.

Etiquetas: Dart Flutter path_provider HttpClient Dio

Publicado el 7-18 22:46