Rx2Firebase: Firebase + RxJava [Android]

Tempo de leitura: 3 minutes

Hoje vou falar sobre Rx2Firebase, um wrapper Rxjava 2.0 na biblioteca Android Firebase do Google.

Todos os dias, muitas pessoas trabalham com Firebase, desenvolvedores de dispositivos móveis e da web … Quase qualquer desenvolvedor que se mantém atualizado com as últimas notícias em suas respectivas comunidades sabe sobre isso e possivelmente já as usou até certo ponto. O uso pode ser limitado apenas ao Crash Reporting ou Analytics, embora, provavelmente, a maioria dos desenvolvedores esteja usando todo o seu potencial com Firebase Storage, Realtime Database, Admob, Test lab.

Cerca de 2 anos atrás, comecei a usar o Firebase Realtime Database como um banco de dados não relacional em um dos meus projetos pessoais de aplicativos móveis. Depois de entender como a API funciona e deixar de lado minhas idéias sobre os bancos de dados relacionais tradicionais, comecei a trabalhar e montei todo o repositório de dados do meu aplicativo. Parte do meu código costumava ser assim:

public void retrieveUsers() {
      firebaseInstance.getReference().child(USER_REF)
         .addListenerForSingleValueEvent(new ValueEventListener() {
            @Override public void onDataChange(DataSnapshot dataSnapshot) {
               if (dataSnapshot.exists()) {
                  PublicUserData userData = dataSnapshot.getValue(PublicUserData.class);
                  updateUserUi(userData);
               } else {
                  noUserFound();
               }
            }

            @Override public void onCancelled(DatabaseError databaseError) {
               manageError(databaseError);
            }
         });
   }
public void retriveRealtimeUsers() {
     firebaseInstance.getReference().child(USER_REF)
        .addChildEventListener(new ChildEventListener() {
        
           @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) {
              manageAddedUser(userData);
           }

           @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) {
              manageChangedUser(userData);
           }

           @Override public void onChildRemoved(DataSnapshot dataSnapshot) {
              manageRemovedUser(userData);
           }

           @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) {
              manageMovedUser(userData);
           }

           @Override public void onCancelled(DatabaseError databaseError) {
              throwable -> manageError(databaseError);
           }
        });
  }

De acordo com esse código, no Firebase, para cada chamada para recuperar dados do servidor, eu precisava implementar uma nova interface e substituir os métodos corretos. Isso gera muito código clichê, que chega ao ponto de ser opressor quando um repositório tem dezenas de métodos.

Por outro lado, as chamadas ao servidor se tornaram mais caóticas nos casos em que decidi não desnormalizar minhas informações, tendo que usar consultas aninhadas para recuperar os dados desejados:

public void retrieveAuthor() {
      DatabaseReference authorQuery = firebaseInstance.getReference().child(USER_REF);
      firebaseInstance.getReference().child(POSTS).child("example")
         .addValueEventListener(new ValueEventListener() {
         
            @Override public void onDataChange(DataSnapshot dataSnapshot) {
               WorkoutPost post = dataSnapshot.getValue(WorkoutPost.class);
               authorQuery.child(post.getAuthor().getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
               
                  @Override public void onDataChange(DataSnapshot dataSnapshot) {
                     Author author = dataSnapshot.getValue(Author.class);
                     showAuthorData(author);
                  }

                  @Override public void onCancelled(DatabaseError databaseError) {
                     manageError(throwable);
                  }
               });
            }

            @Override public void onCancelled(DatabaseError databaseError) {
               manageError(throwable);
            }
         });
   }

Callbacks aninhados de novo e de novo … Acho que nenhum de nós gosta desse tipo de coisa em nosso código, certo? Essa dor foi parcialmente mitigada quando adicionei retrolambda ao meu projeto, mas não resolveu totalmente o problema principal.

Na época, eu estava usando RxJava 2.0 junto com a arquitetura Clean, então pensei em dedicar algum do meu tempo para envolver todas as minhas chamadas de repositório em chamadas reativas. Foi assim que nasceu o Rx2Firebase.

 

Rx2Firebase

Neste ponto deste artigo, presumo que você esteja familiarizado com o RxJava e o padrão do observador. Se não estiver, sugiro que você faça uma pausa e investigue o assunto, pois isso abrirá um novo mundo de possibilidades à sua frente.

Abaixo, mostro os exemplos mencionados acima usando Rx2Firebase em vez da API convencional:

public void retrieveUser(){
     DatabaseReference query = instance.getReference().child(USER_REF);
     RxFirebaseDatabase.observeSingleValueEvent(query, PublicUserData.class)
        .subscribe(userData -> updateUserUi(userData),
           throwable -> manageError(throwable));
  }
public void retriveRealtimeUsers() {
     DatabaseReference query = firebaseInstance.getReference().child(USER_REF);
     Disposable childEventDisposable = RxFirebaseDatabase.observeChildEvent(query, PublicUserData.class)
        .subscribe(userData -> {
              switch (userData.getEventType()) {
                 case ADDED:
                    manageAddedUser(userData);
                 case CHANGED:
                    manageChangedUser(userData);
                 case REMOVED:
                    manageRemovedUser(userData);
                 case MOVED:
                    manageMovedUser(userData);
              }
           },
           throwable -> manageError(throwable));

     //Call dispose will removeEventListener for us
     childEventDisposable.dispose();
  }
public void retrieveAuthor() {
      DatabaseReference postQuery = firebaseInstance.getReference().child(POSTS).child("example");
      DatabaseReference authorQuery = firebaseInstance.getReference().child(USER_REF);
      RxFirebaseDatabase.observeSingleValueEvent(postQuery, WorkoutPost.class)
         .flatMap(post -> RxFirebaseDatabase.observeSingleValueEvent(authorQuery.child(post.getAuthor().getUid()), Author.class))
         .subscribe(author -> showAuthorData(author),
            throwable -> manageError(throwable));
   }

É muito mais simples e fácil de ler, certo? Existem milhares de exemplos como este. Todos os métodos das APIs Storage, FirebaseDatabase, Authentication e FirebaseFirestore (beta) são agrupados com suas respectivas funções reativas na biblioteca.

Se você já trabalhou com o RxJava antes, verá facilmente todas as possibilidades que pode alcançar usando mapas e mapas planos. Criar consultas aninhadas em seus DataSnapshots ou lançá-los em seus respectivos POJOs são apenas alguns exemplos. Da mesma forma, você poderá utilizar zip ou merge para concatear múltiplas chamadas de banco de dados em um único descartável, retornando todas ao mesmo tempo.

Você pode pegar o Rx2Firebase adicionando dependência ao seu arquivo build.gradle:

compile 'com.github.frangsierra:rx2firebase:1.1.3'
//If you are using Firestore in your project
compile 'com.github.frangsierra:rx2firebase:1.4-beta.2'

Estes são apenas alguns exemplos de como usar a biblioteca, sinta-se à vontade para usá-la, abrir questões, criar solicitações de pull e contribuir com o projeto.

Junte-se a nós no GitHub, teste a API e vamos trabalhar juntos para tornar o Firebase ainda mais incrível! Boa codificação!