Como abstrair sua aplicação flutter para não depender de packages
Tags: flutter, braziliandevs, dart, tutorial
Published: 2024-05-17 15:52:45

Flutter, o framework de UI do Google para criar aplicativos multiplataforma, é conhecido por sua extensa coleção de pacotes que simplificam o desenvolvimento. No entanto, confiar demais em pacotes pode tornar seu projeto vulnerável a problemas como descontinuação de pacotes, falta de manutenção ou incompatibilidades futuras. Abstrair sua aplicação para minimizar a dependência de pacotes pode aumentar a longevidade e a estabilidade do seu projeto. Neste artigo, vamos explorar estratégias para alcançar isso.

## Tópicos
- [Entenda suas Necessidades](#entenda-suas-necessidades)
- [Use Interfaces para Abstração](#use-interfaces-para-abstração)
- [Implementação Própria de Funcionalidades Simples](#implementação-própria-de-funcionalidades-simples)
- [Módulos de Serviço](#módulos-de-serviço)
- [Conclusão](#conclusão)

## Entenda Suas Necessidades
Antes de decidir abstrair seu aplicativo, é crucial entender as necessidades específicas do seu projeto. Nem todos os pacotes são iguais e alguns fornecem funcionalidades complexas que podem ser difíceis de replicar. Avalie se a funcionalidade fornecida pelo pacote é algo que você pode ou deve implementar por conta própria. 
Pergunte-se:

- Este pacote é crucial para a funcionalidade principal do meu aplicativo?
- A funcionalidade é complexa ou pode ser implementada com uma solução personalizada?
- O pacote tem uma alta probabilidade de ser descontinuado?

Com essas informações em mãos, podemos elaborar algumas estratégias para o seu desacoplamento:

## Use Interfaces para Abstração
Uma das melhores práticas para reduzir a dependência de pacotes é usar interfaces para definir a funcionalidade necessária. Em vez de depender diretamente de uma implementação de um pacote, você pode definir uma interface que descreve o comportamento necessário e, em seguida, fornecer uma implementação concreta que utiliza o pacote.
### Exemplo

```dart
// Definindo a interface
abstract class LocalStorage {
  Future<void> saveData(String key, String value);
  Future<String?> getData(String key);
}

// Implementação usando um pacote (por exemplo, SharedPreferences)
class SharedPreferencesStorage implements LocalStorage {
  final SharedPreferences _prefs;

  SharedPreferencesStorage(this._prefs);

  @override
  Future<void> saveData(String key, String value) async {
    await _prefs.setString(key, value);
  }

  @override
  Future<String?> getData(String key) async {
    return _prefs.getString(key);
  }
}
```
Dessa forma, você pode facilmente substituir `SharedPreferencesStorage` por outra implementação sem alterar o restante do código que depende de `LocalStorage`.

## Implementação Própria de Funcionalidades Simples
Para funcionalidades simples, considere implementar você mesmo em vez de usar um pacote. Por exemplo, se você precisa apenas de uma solução simples de cache ou armazenamento local, pode ser mais fácil escrever sua própria implementação do que depender de um pacote.

### Exemplo de Cache Simples

```dart
// Define a classe SimpleCache
class SimpleCache {
// Cria um mapa privado para armazenar pares de chave-valor  
  final Map<String, String> _cache = {};
// Método para adicionar dados ao cache
  void saveData(String key, String value) {
    _cache[key] = value;
  }
// Método para recuperar dados do cache
  String? getData(String key) {
    return _cache[key];
  }
}
```

## Módulos de Serviço
Separar a lógica de negócios em módulos de serviço pode ajudar a reduzir a dependência de pacotes. Esses módulos podem fornecer abstração sobre qualquer funcionalidade de terceiros, permitindo que você mude facilmente a implementação sem impactar outras partes do código.

### Exemplo
```dart
// Define a classe AuthService
class AuthService {
  // Cria uma instância privada de FirebaseAuth
  final FirebaseAuth _firebaseAuth;

  // Construtor que aceita uma instância de FirebaseAuth
  AuthService(this._firebaseAuth);

  // Método para autenticar um usuário com email e senha
  Future<User?> signIn(String email, String password) async {
    try {
      // Tenta autenticar o usuário com email e senha
      UserCredential userCredential = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password);
      // Se a autenticação for bem-sucedida, retorna o User
      return userCredential.user;
    } catch (e) {
      // Se ocorrer um erro, imprime o erro e retorna null
      print("Error: $e");
      return null;
    }
  }

  // Método para deslogar o usuário atual
  Future<void> signOut() async {
    await _firebaseAuth.signOut();
  }
}
```

Aqui, `AuthService` abstrai a implementação do `FirebaseAuth`, permitindo que você substitua facilmente `_firebaseAuth` por outra solução de autenticação no futuro.

## Gerenciamento de Estado Independente de Pacotes
O gerenciamento de estado é uma área onde muitos desenvolvedores dependem de pacotes como Provider, Bloc ou Riverpod. No entanto, é possível gerenciar o estado de forma eficiente sem depender de pacotes externos, utilizando apenas as funcionalidades nativas do Flutter, como `InheritedWidget`, `ChangeNotifier` e `ValueNotifier`.

### Exemplo usando `ChangeNotifier`

```dart
// Define a classe Counter que notifica os ouvintes quando o valor do contador muda
class Counter with ChangeNotifier {
  // Inicializa o contador como 0
  int _count = 0;

  // Getter para obter o valor atual do contador
  int get count => _count;

  // Método para incrementar o contador
  void increment() {
    _count++;  // Incrementa o contador
    notifyListeners();  // Notifica os ouvintes sobre a mudança
  }
}

// Define a classe CounterProvider que fornece uma instância de Counter para seus descendentes
class CounterProvider extends InheritedWidget {
  // Mantém uma referência para a instância de Counter
  final Counter counter;

  // Construtor que aceita um filho e uma instância de Counter
  CounterProvider({Key? key, required Widget child, required this.counter}) : super(key: key, child: child);

  // Método para obter a instância mais próxima de CounterProvider na árvore de widgets
  static CounterProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterProvider>();
  }

  // Método para determinar se este widget deve notificar seus descendentes quando é reconstruído
  @override
  bool updateShouldNotify(CounterProvider oldWidget) {
    // Retorna true se a instância de Counter mudou
    return counter != oldWidget.counter;
  }
}
```

## Conclusão

Abstrair sua aplicação Flutter para não depender de pacotes envolve um equilíbrio entre reutilização de código e a criação de uma base sólida e independente. Usando interfaces, implementando funcionalidades simples por conta própria e separando a lógica em módulos de serviço, você pode reduzir a dependência de pacotes externos e aumentar a flexibilidade e a longevidade do seu projeto. Adotar essas práticas pode exigir um esforço adicional no início, mas os benefícios a longo prazo de um código mais limpo e sustentável valem a pena.