Construindo um App Financeiro com Flutter 3.41: PDF, Supabase, Notificações e Deploy (Final)

Neste encerramento, daremos inteligência ativa ao app (Notificações de Vencimento), capacidade de prestação de contas (Relatórios em PDF), segurança contra perda de dados (Backup em Nuvem com Supabase) e, por fim, a preparação completa para as lojas de aplicativos.

🌱 1. Sistema de Inicialização (Database Seeding)

Quando o usuário abre o aplicativo pela primeira vez, ele não deve encontrar uma tela vazia sem opções. Precisamos pré-cadastrar as Categorias essenciais (Alimentação, Transporte) e uma Conta Padrão (Carteira) no Isar.

Atualize a inicialização no isar_service.dart:

Future<Isar> _initDB() async {
  final dir = await getApplicationDocumentsDirectory();
  final isar = await Isar.open([TransactionSchema, CategorySchema, BankAccountSchema, /* outros */], directory: dir.path);

  // Seeding: Verifica se o banco está vazio
  if (await isar.categorys.count() == 0) {
    await isar.writeTxn(() async {
      // Categorias Padrão
      await isar.categorys.putAll([
        Category()..name = 'Alimentação'..iconData = '58738'..colorHex = 0xFFF44336..isIncome = false,
        Category()..name = 'Salário'..iconData = '57895'..colorHex = 0xFF4CAF50..isIncome = true,
      ]);
      // Conta Padrão
      await isar.bankAccounts.put(BankAccount()..name = 'Carteira Física'..initialBalance = 0.0..themeColor = 0xFF9E9E9E..logoPath = '');
    });
  }
  return isar;
}

🔔 2. Notificações Locais (Vencimentos do Dia)

O aplicativo deve avisar o usuário proativamente se há uma conta a pagar hoje. Usaremos o pacote flutter_local_notifications combinado com uma checagem em background.

Configuração Básica do Serviço (notification_service.dart):

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class NotificationService {
  static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();

  static Future<void> init() async {
    const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const DarwinInitializationSettings iosSettings = DarwinInitializationSettings();
    const InitializationSettings settings = InitializationSettings(android: androidSettings, iOS: iosSettings);
    
    await _notificationsPlugin.initialize(settings);
  }

  static Future<void> showDueBillNotification(String title, double amount) async {
    const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'bills_channel', 'Lembretes de Vencimento',
      importance: Importance.max,
      priority: Priority.high,
      color: Color(0xFFE53935),
    );
    const NotificationDetails platformDetails = NotificationDetails(android: androidDetails);
    
    await _notificationsPlugin.show(
      0, 'Vencimento Hoje! 🚨', '$title no valor de R\$ ${amount.toStringAsFixed(2)} vence hoje.', platformDetails,
    );
  }
}

(Na tela Home, você pode consultar o Riverpod para faturas vencendo DateTime.now() com isPaid == false e disparar esta função).

📄 3. Exportação de Relatórios em PDF

Para prestação de contas ou controle no computador, a exportação em PDF é um recurso premium. Utilizaremos os pacotes pdf e printing.

Arquivo (pdf_export_service.dart):

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';

class PdfExportService {
  static Future<void> generateMonthlyReport(List<Transaction> transactions, String month) async {
    final pdf = pw.Document();

    pdf.addPage(
      pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Column(
            crossAxisAlignment: pw.CrossAxisAlignment.start,
            children: [
              pw.Text('Relatório Financeiro - $month', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),
              pw.Divider(),
              pw.SizedBox(height: 16),
              pw.TableHelper.fromTextArray(
                headers: ['Data', 'Descrição', 'Valor (R$)', 'Tipo'],
                data: transactions.map((t) => [
                  '${t.dueDate.day}/${t.dueDate.month}',
                  t.title,
                  t.amount.toStringAsFixed(2),
                  t.isIncome ? 'Entrada' : 'Saída'
                ]).toList(),
              ),
            ],
          );
        },
      ),
    );

    // Abre a interface nativa de compartilhamento/impressão do celular
    await Printing.layoutPdf(onLayout: (PdfPageFormat format) async => pdf.save());
  }
}

☁️ 4. Backend em Nuvem: Script SQL do Supabase

O Isar é local. Se o usuário perder o celular, perde os dados. Vamos usar o Supabase (alternativa Open-Source ao Firebase baseada em PostgreSQL) para fazer o backup.

Primeiro, você deve acessar o painel do Supabase e rodar este Script SQL para espelhar nosso modelo:

-- Habilita a extensão UUID (caso não esteja ativa)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Tabela de Categorias
CREATE TABLE categories (
  id SERIAL PRIMARY KEY,
  local_isar_id BIGINT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  color_hex INTEGER,
  is_income BOOLEAN,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE
);

-- Tabela de Transações
CREATE TABLE transactions (
  id SERIAL PRIMARY KEY,
  local_isar_id BIGINT UNIQUE NOT NULL,
  title TEXT NOT NULL,
  amount DECIMAL(10, 2) NOT NULL,
  is_income BOOLEAN NOT NULL,
  due_date DATE NOT NULL,
  is_paid BOOLEAN DEFAULT FALSE,
  category_local_id BIGINT REFERENCES categories(local_isar_id),
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
);

-- Segurança de Nível de Linha (RLS) para garantir que cada usuário só veja seus dados
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Usuários acessam apenas suas próprias transações" 
ON transactions FOR ALL USING (auth.uid() = user_id);

🔄 5. Integração e Sincronização no Flutter (Supabase)

Adicione a dependência supabase_flutter: ^2.4.0. Faremos um serviço que lê do Isar e faz Upsert (Atualiza se existir, insere se não existir) no PostgreSQL da nuvem.

Arquivo (sync_service.dart):

import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class SyncService {
  final SupabaseClient _supabase = Supabase.instance.client;

  Future<void> backupTransactionsToCloud(WidgetRef ref) async {
    final isar = await ref.read(isarProvider).db;
    final transactions = await isar.transactions.where().findAll();
    final userId = _supabase.auth.currentUser?.id;

    if (userId == null) return; // Requer usuário logado

    final dataList = transactions.map((t) => {
      'local_isar_id': t.id,
      'title': t.title,
      'amount': t.amount,
      'is_income': t.isIncome,
      'due_date': t.dueDate.toIso8601String(),
      'is_paid': t.isPaid,
      'category_local_id': t.category.value?.id,
      'user_id': userId,
    }).toList();

    // Faz o Upsert em lote no Supabase
    await _supabase.from('transactions').upsert(dataList, onConflict: 'local_isar_id');
  }
}

🛠️ 6. Preparo Profissional do Código

Antes de gerar os binários, certifique-se de que o app está otimizado:

  • Remova todos os print() do código usando pacotes como o logger ou garantindo que só rodem em ambiente de Debug (kDebugMode).

  • Verifique os ícones do aplicativo (flutter_launcher_icons).

  • Configure a tela de Splash Nativa (usando o pacote flutter_native_splash) para evitar a tela branca no milissegundo antes de carregar o motor do Flutter.


🚀 7. Preparo para Publicação: Google Play (AAB)

O Google exige o formato Android App Bundle (.aab) para otimizar o tamanho do download para cada dispositivo.

  1. No arquivo android/app/build.gradle, certifique-se de atualizar o versionCode e o versionName.

  2. Gere as chaves de assinatura (Keystore) seguindo a documentação oficial.

  3. No terminal, execute o comando de build ofuscado:

flutter build appbundle --release --obfuscate --split-debug-info=./debug_info

Isto gerará o arquivo em build/app/outputs/bundle/release/app-release.aab.

🍎 8. Preparo para Publicação: App Store (iOS)

A Apple exige o Xcode e um Mac para a compilação.

  1. Atualize a versão no pubspec.yaml.

  2. Abra a pasta ios/ no Xcode, configure seu Time de Desenvolvimento (Apple Developer Program) e assine o app.

  3. Execute no terminal:

flutter build ipa --release --obfuscate --split-debug-info=./debug_info

📦 9. Gerando um APK Autônomo (Sideloading)

Muitas vezes, você quer distribuir o app diretamente pelo WhatsApp, site próprio ou instalá-lo em dispositivos de teste que não têm a Google Play vinculada. O formato .aab não permite instalação direta. Você precisa do .apk.

Para gerar um APK único (Universal) que roda em qualquer arquitetura Android (ARM, x86), use:

flutter build apk --release

Para gerar APKs divididos por arquitetura (menores e mais rápidos, ideais para repositórios como o GitHub Releases), use:

flutter build apk --split-per-abi --release

Os arquivos estarão em build/app/outputs/flutter-apk/

 

Fim da Temporada!

Parabéns! Você construiu uma arquitetura que rivaliza com soluções de grandes bancos e fintechs:

  1. Fundação Relacional Local: Isar NoSQL com relacionamentos robustos.

  2. Estado Previsível: Riverpod orquestrando UI reativa sem sobrecarga de memória.

  3. Hardware: Câmera para leitura de códigos de barras.

  4. Cloud & BI: Geração de PDFs e sincronização pontual e segura com PostgreSQL via Supabase.

Please follow and like us:
error0
fb-share-icon
Tweet 20
fb-share-icon20