Chat com Flutter e Firebase | Parte 2 — Conectando nossa aplicação ao Firebase
Nesse artigo, iremos conectar o app ao Firebase (acesse aqui a Parte 1). A idéia principal é não manter nossas credenciais no repositório. Para isso usaremos o pacote envied (documentação oficial) e variáveis de ambiente para manter as informações sensíveis seguras. Vamos lá!
Primeiro, é necessário que você crie seu projeto no Firebase e baixe os arquivos de configuração para cada Sistema Operacional. No nosso caso, estamos trabalhando apenas iOS (GoogleService-Info.plist) e Android (google-services.json). Não iremos adicionar esses arquivos no projeto e também não iremos usar a CLI do firebase para conectar o projeto e gerar arquivos de configuração (como demonstrado na documentação oficial).
Basicamente a gente precisa das seguintes informações:
- API_KEY: Chave da nossa aplicação (essa informação muda para cada S.O.);
- APP_ID: Identificador único de cada projeto (essa informação muda para cada S.O.);
- SENDER_ID: Identificador do mensageiro que está conectando ao Firebase (informação idêntica para cada S.O.);
- PROJECT_ID: Identificador do projeto Firebase (informação idêntica para cada S.O.).
Como as 2 primeiras propriedades mudam para cada S.O., teremos uma chave para cada sistema operacional em nosso arquivo de variáveis de ambiente. Vamos criá-lo agora na raiz do projeto. Ele deve ficar conforme a imagem a seguir:
firebaseiOSApiKey=<API_KEY>
firebaseAndroidApiKey=client.api_key.current_key
firebaseAndroidAppId=client.client_info.mobilesdk_app_id
firebaseiOSAppId=<GOOGLE_APP_ID>
firebaseMessagingSenderId=<GCM_SENDER_ID> or project_info.project_number
firebaseProjectId=<PROJECT_ID> or project_info.project_info
Será necessário que cada propriedade seja preenchida conforme cada valor encontrado nos arquivos baixados anteriormente. Após preencher os valores, vamos criar a classe que representará os valores e será utilizada pela aplicação.
Crie o arquivo env.dart. Eu irei criá-lo em lib/core/utils/env.dart. Conforme a documentação oficial do pacote envied, iremos gerar uma propriedade estática para cada uma das informações que criamos em nosso arquivo de variaáveis de ambiente. Além dessas propriedades, iremos gerar 2 getters para buscar as propriedades de cada sistema operacional de forma dinâmica. Nossa classe terá a seguinte estrutura:
import 'dart:io';
import 'package:envied/envied.dart';
part 'env.g.dart';
@Envied()
abstract class Env {
/// On iOS this is represented as API_KEY within the GoogleService-Info.plist file
@EnviedField(varName: 'firebaseiOSApiKey')
static const String firebaseiOSApiKey = _Env.firebaseiOSApiKey;
/// On Android this is represened as client.client_info.api_key.current_key
/// within the google-services.json file
@EnviedField(varName: 'firebaseAndroidApiKey')
static const String firebaseAndroidApiKey = _Env.firebaseAndroidApiKey;
/// On iOS this is represented as API_KEY within the GoogleService-Info.plist file
@EnviedField(varName: 'firebaseiOSAppId')
static const String firebaseiOSAppId = _Env.firebaseiOSAppId;
/// On Android this is represened as client.client_info.mobilesdk_app_id
/// within the google-services.json file
@EnviedField(varName: 'firebaseAndroidAppId')
static const String firebaseAndroidAppId = _Env.firebaseAndroidAppId;
/// On iOS this is represented as GCM_SENDER_ID within the GoogleService-Info.plist file
/// On Android this is represened as project_info.project_number
/// within the google-services.json file
@EnviedField(varName: 'firebaseMessagingSenderId')
static const String firebaseMessagingSenderId =
_Env.firebaseMessagingSenderId;
/// On iOS this is represented as PROJECT_ID within the GoogleService-Info.plist file
/// On Android this is represened as project_info.project_info
/// within the google-services.json file
@EnviedField(varName: 'firebaseProjectId')
static const String firebaseProjectId = _Env.firebaseProjectId;
/// This information is different boetween OS
/// On iOS this is represented as API_KEY within the GoogleService-Info.plist file
/// On Android this is represened as client.client_info.mobilesdk_app_id
/// within the google-services.json file
static String get firebaseAppId =>
Platform.isIOS ? _Env.firebaseiOSAppId : _Env.firebaseAndroidAppId;
/// This information is different boetween OS
/// On iOS this is represented as API_KEY within the GoogleService-Info.plist file
/// On Android this is represened as client.client_info.api_key.current_key
/// within the google-services.json file
static String get firebaseApiKey =>
Platform.isIOS ? _Env.firebaseiOSApiKey : _Env.firebaseAndroidApiKey;
}
Para cada propriedade, deixei um comentário no código explicando onde conseguir cada valor (que deve ser preenchido no arquivo .env). A classe estará apresentando erros, mas não se preocupe, iremos resolver a seguir.
Como nossa classe criada eos valores preenchidos no arquivo de variáveis de ambiente, iremos rodar o build_runner para gerar o arquivo parcial env.g.dart ao qual nossa classe precisa para obter cada valor corretamente. Abra o terminal e navegue até a pasta do projeto e rode o comando a seguir:
flutter packages pub run build_runner build --delete-conflicting-outputs
O arquivo gerado terá a mesma estrutura a seguir, porém com cada propriedade devidamente preenchida.
Agora podemos conectar a aplicação com o Firebase. Para isso vamos alterar o arquivo main.dart. É necessário nesse caso:
- Tornar o método main assíncrono (através da assinatura async);
- Garantir que nosso aplicativo só rode após nossas dependências terem sido inicializadas (através do comando WidgetsFlutterBinding.ensureInitialized());
- Chamar o método de inicialização do Firebase passando as opções esperadas. Usaremos nossa class _ENV criada anteriormente.
Nosso método mais deve ficar da seguinte forma:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: FirebaseOptions(
apiKey: Env.firebaseApiKey,
appId: Env.firebaseAppId,
messagingSenderId: Env.firebaseMessagingSenderId,
projectId: Env.firebaseProjectId,
),
);
runApp(const MainApp());
}
Agora precisamos de uma classe que será responsável por escrever algo no Firestore (banco de dados) e que a gente possa validar o funcionamento destas configurações. Vamos criar uma classe de serviço chamada FirebaseService. Eu criei o arquivo no caminho lib/services/firebase_service.dart.
Ela terá apenas um método onde escreveremos uma entrada de testes. Então ela ficará assim:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirebaseService {
final FirebaseFirestore firestore;
FirebaseService({required this.firestore});
void writeTestMessage() {
firestore
.collection('test_collection')
.add({'message': 'test successfully'});
}
}
Para testarmos se ocorreu tudo conforme o esperado, iremos alterar a classe MainApp transformando ela em um Stateful e chamando nossa classe de serviço no initState para que ela consiga escrever em nosso banco de dados. A classe MainApp deve ficar assim:
class _MainAppState extends State<MainApp> {
final _firebaseService =
FirebaseService(firestore: FirebaseFirestore.instance);
@override
void initState() {
_firebaseService.writeTestMessage();
super.initState();
}
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
}
Acesse o Firebase Firestore e verifique se existe a collection 'test_collection' e se ela possui a entrada esperada.
Antes de enviar suas alterações para o repositório, garanta que os arquivos gerados via build_runner e também o arquivo de variáveis de ambiente não sejam enviados. Para isso, adicione-os ao .gitignore.
Dessa forma garantimos que nossas credenciais não sejam expostas.
Nesse artigo a gente viu:
- Como usar o pacote envied;
- Criar o arquivo de variáveis de ambiente necessário para conectar ao Firebase (iOS e Android);
- Manter nossas credenciais protegidas.
No próximo artigo iremos ciar a UI simples do Chat e começar a brincadeira =)
Me segue aí, compartilha e me ajude com sugestões de melhorias! Até a próxima!