Consumir API REST de WordPress en Flutter

Introducción

En este artículo aprenderás a consumir la API REST de WordPress desde Flutter, una solución ideal para quienes ya tienen un blog o sitio web en WordPress y quieren mostrar su contenido en una app móvil. Vamos a usar una arquitectura moderna basada en Riverpod y GoRouter, con soporte para paginación, vista de detalles y organización modular.

Requisitos previos

  • Tener un sitio en WordPress con la API REST habilitada (por defecto viene activa).
  • Conocimientos básicos de Flutter y Riverpod.
  • Tener configurado un proyecto Flutter funcional.

Paso 1: Crear la estructura base del proyecto

Organizaremos el proyecto en capas. Dentro del directorio principal de tu app Flutter, crea las siguientes carpetas:

lib/
├── core/
├── models/
├── services/
├── features/
│   └── posts/
├── providers/
├── routing/
└── main.dart

Cada carpeta tendrá una función específica:

  • core/: constantes, configuración global, helpers.
  • models/: definición de modelos como Post.
  • services/: lógica de conexión a la API REST.
  • features/posts/: UI y lógica específica de los posts.
  • providers/: control de estado con Riverpod.
  • routing/: configuración de rutas con GoRouter.

Paso 2: Crear el modelo Post

Este modelo representa la estructura de un artículo del blog. Extraemos datos como el título, el contenido, la imagen destacada y la fecha desde el JSON que devuelve WordPress. Usamos Post.fromJson() para transformar los datos en una instancia del modelo.

Archivo: lib/models/post.dart

class Post {
  final int id;
  final String title;
  final String excerpt;
  final String content;
  final String featuredImage;
  final DateTime date;

  Post({
    required this.id,
    required this.title,
    required this.excerpt,
    required this.content,
    required this.featuredImage,
    required this.date,
  });

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title']['rendered'],
      excerpt: json['excerpt']['rendered'],
      content: json['content']['rendered'],
      featuredImage: json['_embedded']?['wp:featuredmedia']?[0]?['source_url'] ?? '',
      date: DateTime.parse(json['date']),
    );
  }
}

✅ Asegúrate de incluir ?_embed en las solicitudes para acceder a la imagen destacada.

Paso 3: Crear el servicio para consumir la API

Aquí encapsulamos la lógica para conectarnos con la API REST de WordPress. Usamos la función fetchPosts() para obtener los artículos en formato JSON y los convertimos en una lista de objetos Post. También manejamos errores si la respuesta no es exitosa.

Archivo: lib/services/api_service.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/post.dart';

class ApiService {
  static const String baseUrl = 'https://jaracoder.com/wp-json/wp/v2';

  Future<List<Post>> fetchPosts({int page = 1}) async {
    final response = await http.get(Uri.parse('$baseUrl/posts?_embed&page=$page'));

    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Post.fromJson(json)).toList();
    } else {
      throw Exception('Error al cargar los posts');
    }
  }
}

Paso 4: Crear el provider con Riverpod

Este provider es responsable de cargar los artículos desde la API y exponerlos a la UI de forma reactiva. Usamos FutureProvider.family para permitir pasar la página como parámetro (útil para la paginación).

Archivo: lib/providers/posts_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/post.dart';
import '../services/api_service.dart';

final postsProvider = FutureProvider.family<List<Post>, int>((ref, page) async {
  final api = ApiService();
  return api.fetchPosts(page: page);
});

Paso 5: Crear una pantalla para mostrar los artículos

En esta pantalla mostramos los artículos usando un ListView. Riverpod se encarga de escuchar el provider y renderizar automáticamente el contenido cuando esté disponible. También mostramos indicadores de carga y errores si ocurren.

Archivo: lib/features/posts/posts_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/posts_provider.dart';

class PostsScreen extends ConsumerWidget {
  const PostsScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final postsAsync = ref.watch(postsProvider(1));

    return Scaffold(
      appBar: AppBar(title: const Text('Últimos artículos')),
      body: postsAsync.when(
        data: (posts) => ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) => ListTile(
            title: Text(posts[index].title),
            subtitle: Text(posts[index].excerpt),
          ),
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (e, _) => Center(child: Text('Error: $e')),
      ),
    );
  }
}

Paso 6: Agregar la ruta con GoRouter

Usamos GoRouter para definir una ruta principal que cargue la pantalla de posts. Esto nos permite navegar de forma declarativa y organizada. Más adelante podremos añadir más rutas para categorías, favoritos o detalles de cada artículo.

Archivo: lib/routing/app_router.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../features/posts/posts_screen.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const PostsScreen(),
    ),
  ],
);

Archivo: lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'routing/app_router.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: router,
      title: 'Jaracoder Blog',
      theme: ThemeData.light(),
    );
  }
}

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio