Guía de adaptación de la aplicación Compass de Flutter en OpenHarmony

Descripción del complemento

La aplicación compass_app es un proyecto de ejemplo en Flutter para planificación de viajes, diseñada para mostrar la construcción de aplicaciones multiplataforma con estructura modular. Incluye múltiples pantallas, gestión de datos local y remota, y estilos personalizables.

Características principales

  • Exploración de destinos por continente con información detallada
  • Sistema de reserva de actividades integrado
  • Autenticación de usuarios y gestión de perfiles
  • Creación y compartición de itinerarios de viaje
  • Soporte para entornos de desarrollo (datos locales JSON) y staging (servidor HTTP)
  • Diseño responsivo que se adapta a diferentes tamaños de pantalla y modos de tema

Arquitectura técnica

La aplicación sigue un patrón de arquitectura en capas:

  1. Capa de datos: Maneja la persistencia local y la comunicación con APIs remotas.
  2. Capa de dominio: Contiene la lógica de negocio y casos de uso esenciales.
  3. Capa de presentación: Gestiona la interfaz de usuario y las interacciones.
  4. Capa de navegación: Controla el enrutamiento entrre pantallas.
  5. Capa de configuración: Administra la inyección de dependencias y ajustes de entorno.

Tecnologías utilizadas

  • Flutter: Framework para interfaces de usuario multiplataforma.
  • Provider: Biblioteca para gestión de estado.
  • Freezed: Generador de modelos de datos inmutables.
  • json_serializable: Serialización y deserialización de JSON.
  • HTTP: Cliente para comunicaciones de red.
  • SharedPreferences: Almacenamiento local de datos.
  • Share Plus: Funcionalidad para compartir contenido.

Instalación y uso

Para integrar compass_app en un proyecto de OpenHarmony, se requiere añadir dependencias mediante Git:

Paso 1: Añadir dependencias

En el archivo pubspec.yaml, incluir:

dependencies:
  flutter:
    sdk: flutter
  provider:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/provider/provider"
  http:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/http/http"
  shared_preferences:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/shared_preferences/shared_preferences"
  share_plus:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/share_plus/share_plus"
  freezed_annotation:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/freezed/freezed_annotation"
  json_annotation:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/json_annotation/json_annotation"

dev_dependencies:
  freezed:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/freezed/freezed"
  json_serializable:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/json_serializable/json_serializable"
  build_runner:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/build_runner/build_runner"

Paso 2: Obtener dependencias

flutter pub get

Paso 3: Configuración de OpenHarmony

Asegurar la configuración correcta del motor de Flutter y dependencias. Para aplicaciones con comunicación de red, añadir permisos en ohos/entry/src/main/module.json5:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.READ_USER_STORAGE"
      },
      {
        "name": "ohos.permission.WRITE_USER_STORAGE"
      }
    ]
  }
}

Paso 4: Ejecutar la aplicación

Entorno de desarrollo (datos locales)
cd app
flutter run --target lib/main_development.dart
Entorno staging (datos remotos)

Iniciar el servidor:

cd server
dart run
# => Servidor escuchando en puerto 8080

Luego ejecutar la aplicación:

cd app
flutter run --target lib/main_staging.dart

Ejemplos de uso de API

A continuación, ejemplos de código que ilustran el funcionamiento básico de la aplicación:

1. Configuración de inyección de dependencias

// Configuración para datos remotos
List<SingleChildWidget> obtenerProveedoresRemotos {
  return [
    Provider(create: (_) => ServicioApiAutenticacion()),
    Provider(create: (_) => ClienteApi()),
    Provider(create: (_) => ServicioAlmacenamientoLocal()),
    ChangeNotifierProvider(
      create: (contexto) => RepositorioAutRemoto(
        apiAuth: contexto.read(),
        apiCliente: contexto.read(),
        almLocal: contexto.read(),
      ) as RepositorioAutenticacion,
    ),
    Provider(
      create: (contexto) => RepositorioDestinosRemoto(
        apiCliente: contexto.read(),
      ) as RepositorioDestinos,
    ),
    // Otros repositorios...
  ];
}

2. Implementación de caso de uso

// Caso de uso para crear reservas
class CasoUsoCrearReserva {
  final RepositorioDestinos _repoDestinos;
  final RepositorioActividades _repoActividades;
  final RepositorioReservas _repoReservas;

  CasoUsoCrearReserva({
    required RepositorioDestinos repoDestinos,
    required RepositorioActividades repoActividades,
    required RepositorioReservas repoReservas,
  }) : _repoDestinos = repoDestinos,
       _repoActividades = repoActividades,
       _repoReservas = repoReservas;

  Future<ResumenReserva> ejecutar({
    required String idDestino,
    required List<String> idsActividades,
    required DateTime fechaInicio,
    required DateTime fechaFin,
    required int numViajeros,
  }) async {
    final destino = await _repoDestinos.obtenerPorId(idDestino);
    final actividades = await Future.wait(
      idsActividades.map((id) => _repoActividades.obtenerPorId(id)),
    );

    final reserva = Reserva(
      id: GeneradorUUID().nuevoUUID(),
      destinoId: idDestino,
      destino: destino,
      actividadesIds: idsActividades,
      actividades: actividades,
      inicio: fechaInicio,
      fin: fechaFin,
      viajeros: numViajeros,
      creacion: DateTime.now(),
    );

    final reservaGuardada = await _repoReservas.guardar(reserva);
    return ResumenReserva.desdeReserva(reservaGuardada);
  }
}

3. Cliente API genérico

// Cliente base para peticiones HTTP
class ClienteApi {
  final String urlBase;
  final Duration tiempoEspera;

  ClienteApi({
    this.urlBase = 'http://localhost:8080',
    this.tiempoEspera = const Duration(seconds: 30),
  });

  Future<Response> obtener(String ruta, {Map<String, String>? encabezados}) async {
    final uri = Uri.parse('$urlBase$ruta');
    return await http.get(
      uri,
      headers: encabezados,
    ).timeout(tiempoEspera);
  }

  Future<Response> enviar(
    String ruta,
    dynamic datos,
    {Map<String, String>? encabezados},
  ) async {
    final uri = Uri.parse('$urlBase$ruta');
    final cuerpoJson = jsonEncode(datos);
    return await http.post(
      uri,
      headers: {
        'Content-Type': 'application/json',
        ...?encabezados,
      },
      body: cuerpoJson,
    ).timeout(tiempoEspera);
  }

  // Otros métodos HTTP...
}

4. Repositorio de destinos remoto

// Repositorio para acceder a destinos vía API
class RepositorioDestinosRemoto implements RepositorioDestinos {
  final ClienteApi _clienteApi;

  RepositorioDestinosRemoto({required ClienteApi clienteApi}) : _clienteApi = clienteApi;

  @override
  Future<List<Destino>> obtenerTodos() async {
    final respuesta = await _clienteApi.obtener('/destinos');
    if (respuesta.statusCode == 200) {
      final List<dynamic> datos = jsonDecode(respuesta.body);
      return datos.map((json) => Destino.fromJson(json)).toList();
    } else {
      throw Exception('Error al cargar destinos');
    }
  }

  @override
  Future<Destino> obtenerPorId(String id) async {
    final respuesta = await _clienteApi.obtener('/destinos/$id');
    if (respuesta.statusCode == 200) {
      return Destino.fromJson(jsonDecode(respuesta.body));
    } else {
      throw Exception('Error al cargar destino');
    }
  }

  @override
  Future<List<Destino>> obtenerPorContinenteId(String continenteId) async {
    final respuesta = await _clienteApi.obtener('/destinos?continenteId=$continenteId');
    if (respuesta.statusCode == 200) {
      final List<dynamic> datos = jsonDecode(respuesta.body);
      return datos.map((json) => Destino.fromJson(json)).toList();
    } else {
      throw Exception('Error al cargar destinos por continente');
    }
  }
}

5. Pantalla de ejemplo

// Pantalla que muestra listado de destinos
class PantallaDestinos extends StatelessWidget {
  const PantallaDestinos({super.key});

  @override
  Widget build(BuildContext contexto) {
    return Scaffold(
      appBar: AppBar(title: const Text('Destinos')),
      body: FutureBuilder<List<Destino>>(
        future: contexto.read<RepositorioDestinos>().obtenerTodos(),
        builder: (contexto, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return const Center(child: Text('No se encontraron destinos'));
          } else {
            final destinos = snapshot.data!;
            return GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 16.0,
                mainAxisSpacing: 16.0,
              ),
              padding: const EdgeInsets.all(16.0),
              itemCount: destinos.length,
              itemBuilder: (contexto, indice) {
                final destino = destinos[indice];
                return TarjetaDestino(destino: destino);
              },
            );
          }
        },
      ),
    );
  }
}

Etiquetas: Flutter OpenHarmony Provider Freezed json_serializable

Publicado el 5-29 23:16