Flutter

Construindo o App APSREDE com Flutter, Provider e IA – Parte 1

No universo do desenvolvimento mobile, unir tecnologias modernas com metodologias assistidas por Inteligência Artificial tem se mostrado uma maneira poderosa de acelerar entregas, organizar o código e promover boas práticas. Neste artigo, vou compartilhar o início do desenvolvimento do aplicativo APSREDE, um projeto que utiliza Flutter, consumo de API WordPress e gerenciamento de estado com Provider — tudo isso com o apoio da IA para gerar, revisar e refinar o código ao longo do processo.

🎯 Objetivo do Projeto

O objetivo foi desenvolver, do zero, um aplicativo Flutter que:

  • Consome os posts do site apsredes.org via API REST do WordPress;
  • Lista os últimos posts publicados na tela inicial;
  • Exibe o conteúdo de cada post com imagem, título, resumo e botão “Leia Mais”;
  • Inclui um efeito de carregamento nas imagens;
  • Navega entre telas com simplicidade e fluidez;
  • Utiliza cores institucionais:
    • Primária: #005BAF
    • Secundária: #FF783F

🛠️ Etapas Iniciais

1. Criação do Projeto

flutter create apsrede
cd apsrede

2. Adição das Dependências

No pubspec.yaml, foram incluídas:

dependencies:
flutter:
sdk: flutter
http: ^0.13.6
provider: ^6.1.1
cached_network_image: ^3.3.1
html: ^0.15.0

Essas bibliotecas foram sugeridas pela IA para otimizar a construção e estilização dos widgets, tratamento de HTML e consumo da API REST.

📁 Estrutura de Pastas

plaintextCopiarEditarlib/
├── main.dart
├── core/
│   └── theme.dart
├── models/
│   └── post.dart
├── services/
│   └── wordpress_service.dart
├── providers/
│   └── post_provider.dart
├── screens/
│   ├── home_screen.dart
│   ├── category_screen.dart
│   └── post_detail_screen.dart
└── widgets/
    └── post_card.dart

📦 Modelagem de Dados

lib/models/post.dart

class Post {
final int id;
final String title;
final String excerpt;
final String content;
final String imageUrl;

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

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

🎨 Tema Customizado

lib/core/theme.dart

import 'package:flutter/material.dart';

final ThemeData appTheme = ThemeData(
primaryColor: const Color(0xFF005BAF),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF005BAF),
secondary: const Color(0xFFFF783F),
),
useMaterial3: true,
);

🧠 Provider e Consumo da API

📡 WordPressService

lib/services/wordpress_service.dart

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

class WordPressService {
static const String _baseUrl = 'https://apsredes.org/wp-json/wp/v2';

Future<List<Post>> fetchLatestPosts() async {
final response = await http.get(Uri.parse('$_baseUrl/posts?_embed'));

if (response.statusCode == 200) {
List data = json.decode(response.body);
return data.map((json) => Post.fromJson(json)).toList();
} else {
throw Exception('Erro ao carregar posts');
}
}
}

🧩 PostProvider

lib/providers/post_provider.dart

import 'package:flutter/material.dart';
import '../models/post.dart';
import '../services/wordpress_service.dart';

class PostProvider extends ChangeNotifier {
final WordPressService _service = WordPressService();
List<Post> _posts = [];
bool _isLoading = false;

List<Post> get posts => _posts;
bool get isLoading => _isLoading;

Future<void> loadPosts() async {
_isLoading = true;
notifyListeners();

_posts = await _service.fetchLatestPosts();

_isLoading = false;
notifyListeners();
}
}

🧱 Construção da Interface

🏠 Tela Inicial

lib/screens/home_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/post_provider.dart';
import '../widgets/post_card.dart';

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
Provider.of<PostProvider>(context, listen: false).loadPosts();
}

@override
Widget build(BuildContext context) {
final postProvider = Provider.of<PostProvider>(context);

return Scaffold(
appBar: AppBar(title: const Text('Últimos Posts')),
body: postProvider.isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: postProvider.posts.length,
itemBuilder: (context, index) {
return PostCard(post: postProvider.posts[index]);
},
),
);
}
}

🖼️ PostCard Widge

lib/widgets/post_card.dart

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_html/flutter_html.dart';
import '../models/post.dart';
import '../screens/post_detail_screen.dart';

class PostCard extends StatelessWidget {
final Post post;

const PostCard({super.key, required this.post});

@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(12),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => PostDetailScreen(post: post)),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(post.title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
CachedNetworkImage(
imageUrl: post.imageUrl,
placeholder: (context, url) => const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => const Icon(Icons.broken_image),
),
const SizedBox(height: 8),
Html(data: post.excerpt),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => PostDetailScreen(post: post)),
),
child: const Text('Leia Mais'),
),
),
],
),
),
),
);
}
}

📖 Tela de Detalhe do Post

lib/screens/post_detail_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import '../models/post.dart';

class PostDetailScreen extends StatelessWidget {
final Post post;

const PostDetailScreen({super.key, required this.post});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Voltar')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Html(data: post.content),
),
);
}
}

🤖 Como a IA Otimizou o Processo

Utilizar a IA neste processo trouxe os seguintes benefícios:

  • Geração automatizada de estrutura de pastas e arquivos iniciais
  • Sugestão de boas práticas de navegação, organização de código e nomenclatura
  • Criação de código reutilizável, limpo e modularizado
  • Ajuste dinâmico de temas e UI com base em especificações fornecidas
  • Eliminação de erros comuns e testes conceituais prévios à execução

🔜 Continuação

Na Parte 2 deste artigo, exploraremos a implementação do MenuApp, a exibição das categorias vindas da API, e o carregamento dos posts por categoria, tudo com a fluidez que o Flutter proporciona.