Trabalhando com valores monetários em Apps Flutter

Enrique Marques Junior - Oct 8 - - Dev Community

Trabalhar com valores monetários em aplicativos é uma parte crítica do desenvolvimento, especialmente em soluções de e-commerce, fintechs e apps que lidam com finanças. É necessário cuidado adicional ao trabalhar com operações de moeda para evitar problemas como arredondamento incorreto, perda de precisão ou conversões inadequadas.

Evitando o Uso de double para Valores Monetários

No Dart, o tipo double é frequentemente utilizado para representar números float. Porem, este não é o tipo adequado para valores monetários, pois números em ponto flutuante introduzem imprecisões. Por exemplo:

void main() {
  final double value = 0.1 + 0.2;
  print(value); // Output: 0.30000000000000004
}
Enter fullscreen mode Exit fullscreen mode

Essa imprecisão pode parecer insignificante em alguns casos, mas pode ter relevancia quando se trata de valores financeiros.

Usar Inteiros para Representar Centavos

Uma prática é representar valores monetários em centavos utilizando int, evitando o uso de decimais e a imprecisão associada a double. Por exemplo, R$ 12,34 seria representado como 1234.

void main() {
  final int value = 1234;
  final double decimal = value / 100;
  print(decimal); // Output: 12.34
}
Enter fullscreen mode Exit fullscreen mode

Usando a Biblioteca intl para Formatação Monetária

A biblioteca intl é uma ferramenta boa para lidar com internacionalização e formatação de números, inclusive valores monetários. Ela permite formatar números com o símbolo de moeda correto e de acordo com as convenções locais.

Exemplo de Formatação de Valores Monetários

import 'package:intl/intl.dart';

void main() {
  final int value = 1234;
  final format = NumberFormat.simpleCurrency(locale: 'pt_BR').format;
  final double decimal = value / 100;

  print(format(decimal)); // Output: R$12,34
}
Enter fullscreen mode Exit fullscreen mode

Aqui, usamos NumberFormat.simpleCurrency com a localidade brasileira (pt_BR) para formatar o valor corretamente em reais.

3. Operações Seguras com Moeda

Operações financeiras como adição, subtração, multiplicação e divisão precisam ser feitas com cautela para evitar erros de precisão. A melhor prática é realizar todas as operações em centavos e, somente para exibição, converter o valor para o formato apropriado.

Exemplo de Adição de Valores Monetários

void main() {
  final int value = 1234; // R$12,34
  final int value2 = 5678; // R$56,78
  final int sum = value + value2;
  print('Soma em centavos: $sum'); // Output: 6912
  final double decimal = sum / 100;
  print('Soma em reais: $decimal'); // Output: 69.12
}
Enter fullscreen mode Exit fullscreen mode

Exemplo: Aplicando Desconto

void main() {
  final int original = 10000; // R$100,00
  final double discount = 0.15; // 15%

  final int total = (original * (1 - discount)).round();
  print('Valor com desconto: ${total / 100}'); // Output: 85.00
}
Enter fullscreen mode Exit fullscreen mode

Aqui usamos a função round() para garantir que o valor seja arredondado corretamente após a aplicação do desconto, evitando problemas de precisão.

Exemplo de Arredondamento

import 'package:intl/intl.dart';

void main() {
  final double value = 1234.56789;
  final formatter = NumberFormat("#,##0.00", "pt_BR");

  print(formatter.format(value)); // Output: 1.234,57
}
Enter fullscreen mode Exit fullscreen mode

Mascara de Valores

No Flutter, podemos utilizar o pacote extended_masked_text para aplicar uma máscara ao valor digitado, permitindo que ele seja formatado automaticamente enquanto o usuário insere a quantia.

  • Exemplo de TextField com máscara e validação:
import 'package:flutter/material.dart';
import 'package:extended_masked_text/extended_masked_text.dart';
import 'package:intl/intl.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Validação de Valores Monetários'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: MonetaryTextField(),
        ),
      ),
    );
  }
}

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

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

class _MonetaryTextFieldState extends State<MonetaryTextField> {
  final _controller = MoneyMaskedTextController(leftSymbol: 'R\$ ');

  String? _errorText;

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      keyboardType: TextInputType.number,
      decoration: InputDecoration(
        labelText: 'Valor',
        border: const OutlineInputBorder(),
        errorText: _errorText,
      ),
      inputFormatters: [
        FilteringTextInputFormatter.digitsOnly,
      ],
      onChanged: (value) {
        setState(() {
          _errorText = _validateInput(value);
        });
      },
    );
  }

  String? _validateInput(String value) {
    if (value.isEmpty) {
      return 'Por favor, insira um valor';
    }

    final numericValue = _controller.numberValue;
    if (numericValue <= 0) {
      return 'O valor deve ser maior que zero';
    }

    return null;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
Enter fullscreen mode Exit fullscreen mode

Conversão de Valores para Centavos

final int value = (_controller.numberValue * 100).round()
Enter fullscreen mode Exit fullscreen mode

Exemplo: se o usuário inserir 1,234.56, o valor será convertido para 123456 centavos, garantindo que o dado seja armazenado corretamente.

Armazenando a Moeda

Além de salvar o valor em centavos, é essencial armazenar a moeda associada a esse valor. Aplicativos frequentemente lidam com múltiplas moedas e associar cada valor à sua moeda permite o tratamento correto nas operações e na exibição.

Por exemplo, para um produto com preço em dólares:

  • Valor em centavos (int): 4567 (representando $45.67)
  • Moeda (string): USD

Isso garante que, ao exibir o valor ao usuário, o aplicativo possa formatá-lo corretamente como $45.67, preservando a moeda adequada.

Extra: packages

Aqui estão alguns pacotes que ajudam a lidar com valores monetários em Dart, fornecendo soluções mais robustas para cálculos financeiros, formatação e manipulação de moedas:

1. money2

O pacote money2 oferece uma API completa para lidar com valores monetários em qualquer moeda. Ele ajuda a resolver problemas comuns de precisão e formatação ao trabalhar com valores financeiros.

Recursos principais:

  • Representação de valores em diferentes moedas.
  • Manipulação segura de operações financeiras.
  • Arredondamento configurável e suporte para múltiplas casas decimais.
  • Integração com intl para formatação.

Exemplo:

import 'package:money2/money2.dart';

void main() {
  final currency = Currency.create('BRL', 2, symbol: 'R\$');
  final value = Money.fromInt(12345, currency); // R$123,45
  print(value.format('S0.00')); // Output: R$123.45
}
Enter fullscreen mode Exit fullscreen mode

2. decimal

O pacote decimal é ideal para quem precisa de precisão exata em operações numéricas, evitando as limitações do tipo double. É muito útil em cálculos financeiros onde a precisão é crítica.

Recursos principais:

  • Suporta números com precisão arbitrária.
  • Ideal para cálculos que envolvem valores monetários grandes ou complexos.

Exemplo:

import 'package:decimal/decimal.dart';

void main() {
  final value1 = Decimal.parse('10.25');
  final value2 = Decimal.parse('20.35');
  final result = value1 + value2;

  print(result); // Output: 30.60
}
Enter fullscreen mode Exit fullscreen mode

3. money_formatter

O money_formatter é um pacote simples e eficiente para formatação de valores monetários, fornecendo diferentes opções de exibição e manipulação. Ele é útil se você precisa apenas de formatação, sem operações matemáticas complexas.

Recursos principais:

  • Formatação flexível e suporte para múltiplas moedas.
  • Suporte para arredondamento e exibição de valores em diferentes formatos.

Exemplo:

import 'package:money_formatter/money_formatter.dart';

void main() {
  final fmf = MoneyFormatter(amount: 1234567.89);
  print(fmf.output.symbolOnLeft); // Output: $1,234,567.89
}
Enter fullscreen mode Exit fullscreen mode

4. currency_formatter

O currency_formatter é outra alternativa para lidar com a formatação de valores monetários, mas ele também inclui opções de conversão entre moedas, o que pode ser útil se o seu aplicativo precisa exibir múltiplas moedas.

Recursos principais:

  • Conversão de moedas usando taxas de câmbio.
  • Formatação de moedas com suporte para diferentes locais.

Exemplo:

import 'package:currency_formatter/currency_formatter.dart';

void main() {
  final formatter = CurrencyFormatter();
  final formattedValue = formatter.format(123456.78, CurrencyFormatterSettings(symbol: 'R\$', symbolSide: SymbolSide.left));

  print(formattedValue); // Output: R$123,456.78
}
Enter fullscreen mode Exit fullscreen mode

É isso! :)


Foto de Eric Prouzet na Unsplash

. . . . . . .
Terabox Video Player