Utilizando o Google Firestore em aplicações Android com Architecture Components

Trilha Android

Msc. Paulo César Siécola

Quem sou eu

  • Mestre em Ciência da Computação pelo IME da USP

Agenda

  • O que é Google Cloud Firestore
  • O que é Android Architecture Components
  • Como criar aplicações Android unindo as essas duas tecnologias
  • Demonstração

Google Cloud Firestore

  • Banco de dados NoSQL escalável

Fonte: cloud.google.com/firestore

Google Cloud Firestore

  • Aplicações Android, iOS, Web e backend

Fonte: cloud.google.com/firestore

Google Cloud Firestore

  • Coleções de documentos estruturados

Fonte: console.firebase.google.com

Google Cloud Firestore

  • Sincronismo em tempo real entre todos os clientes

Fonte: cloud.google.com/firestore

Android Architecture Componentes

  • Componentes que facilitam o gerenciamento do ciclo de vida de fragments e activities

Repository

ViewModel

LiveData<Product>

LiveData<State>

View

Data operation

Action

Observes

Android Architecture Componentes

  • Criar objetos que notificam views quando os dados são modificados
  • Armazenar dados e estados que sobrevivem ao ciclo de vida de uma view
  • Separar gerenciamento da view de seus dados

O que pode ser feito com eles?

Criando o projeto Android

implementation "android.arch.lifecycle:extensions:1.1.1"


implementation 'com.android.support:recyclerview-v7:28.0.0'


implementation 'com.google.firebase:firebase-core:16.0.8'
implementation 'com.google.firebase:firebase-auth:16.2.1'
implementation 'com.google.firebase:firebase-firestore:18.2.0'

implementation 'com.google.android.gms:play-services-auth:16.0.1'


apply plugin: 'com.google.gms.google-services'
  • Arquivo build.gradle

Estrutura do projeto

  • Classe modelo
  • Repositório de dados
  • ViewModel
  • Fragmento de listagem
  • Fragmento de criação/edição
  • Adapter para RecyclerView 

Estrutura do projeto

Product

Model

Repository

Product

ViewModel

LiveData<Product>

LiveData<List<Product>>

Products

ListFragment

Data operation

Action

Observes

Product

Fragment

Product

Adapter

Action

Observes

Cloud

Firestore

Classe modelo

@IgnoreExtraProperties
public class Product implements Serializable {
    public static final String COLLECTION = "products";
    public static final String FIELD_userId = "userId";
    public static final String FIELD_name = "name";
    public static final String FIELD_description = "description";
    public static final String FIELD_code = "code";
    public static final String FIELD_price = "price";

    private String id;
    private String userId;
    private String name;
    private String description;
    private String code;
    private double price;

    @Exclude
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    //getters and setters
}

Documento no Firestore

  • Os atributos são mapeados como campos do documento no Firestore

Repositório de dados

public String saveProduct(Product product)  {
    DocumentReference document;

    if (product.getId() != null) {
        document = mFirestore.collection(Product.COLLECTION).document(product.getId());
    } else {
        product.setUserId(mFirebaseAuth.getUid());
        document = mFirestore.collection(Product.COLLECTION).document();
    }

    document.set(product);

    return document.getId();
}

Repositório de dados

public MutableLiveData<List<Product>> getProducts() {
    MutableLiveData<List<Product>> liveProducts = new MutableLiveData<List<Product>>();

    mFirestore.collection(Product.COLLECTION)
            .whereEqualTo(Product.FIELD_userId, mFirebaseAuth.getUid())
            .orderBy(Product.FIELD_name, Query.Direction.ASCENDING)
            .addSnapshotListener((snapshot, e) -> {
                if (e != null) {
                    Log.w(TAG, "Listen failed.", e);
                    return;
                }

                List<Product> products = new ArrayList<>();
                if (snapshot != null && !snapshot.isEmpty()) {
                    for (DocumentSnapshot documentSnapshot : snapshot.getDocuments()) {
                        Product product = documentSnapshot.toObject(Product.class);
                        product.setId(documentSnapshot.getId());
                        products.add(product);
                    }
                }
                liveProducts.postValue(products);
            });

    return liveProducts;
}

Repositório de dados

public MutableLiveData<Product> getProductById(String productId) {
    MutableLiveData<Product> liveProject = new MutableLiveData<>();

    final DocumentReference docRef = mFirestore.collection(Product.COLLECTION).document(productId);
    docRef.addSnapshotListener((snapshot, e) -> {
        if (e != null) {
            Log.w(TAG, "Listen failed.", e);
            return;
        }

        if (snapshot != null && snapshot.exists()) {
            Product product = snapshot.toObject(Product.class);
            product.setId(snapshot.getId());
            liveProject.postValue(product);
        } else {
            Log.d(TAG, "Current data: null");
        }
    });

    return liveProject;
}

ViewModel

public class ProductViewModel extends ViewModel {
    private MutableLiveData<Product> product;
    private MutableLiveData<List<Product>> products;

    public MutableLiveData<List<Product>> getAllProducts() {
        if (products == null) {
            products = ProductRepository.getInstance().getProducts();
        }
        return products;
    }

    public MutableLiveData<Product> getProductById(String productId) {
        if (product == null) {
            product = ProductRepository.getInstance().getProductById(productId);
        }
        return product;
    }
}

Listando os produtos

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_products_list, container, false);

    productViewModel = ViewModelProviders.of(this).get(ProductViewModel.class);

    RecyclerView rcvProducts = rootView.findViewById(R.id.rcvProducts);
    productAdapter = new ProductAdapter(this);
    rcvProducts.setLayoutManager(new LinearLayoutManager(getActivity()));
    rcvProducts.setAdapter(productAdapter);

    //...
    return rootView;
}

@Override
public void onResume() {
    super.onResume();

    productViewModel.getAllProducts().observe(this, products -> {
        productAdapter.setProducts(products);
    });
}

Adapter para RecyclerView

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ViewHolder> {

    private List<Product> products;

   //...

    public void setProducts(List<Product> products) {
        this.products = products;
        notifyDataSetChanged();
    }

    //...
}

Criando e editando produtos

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    //...

    productViewModel = ViewModelProviders.of(this).get(ProductViewModel.class);
}

@Override
public void onResume() {
    super.onResume();
    if (productId != null) {
        productViewModel.getProductById(productId).observe(this, product -> {
            this.product = product;
            if (product != null) {
                edtName.setText (product.getName());
                edtCode.setText (product.getCode());
                edtDescription.setText (product.getDescription());
                edtPrice.setText (String.valueOf(product.getPrice()));
            }
        });
    }
}

Atualizações em tempo real

Product

Model

Repository

Product

ViewModel

LiveData<Product>

LiveData<List<Product>>

Products

ListFragment

Data operation

Action

Observes

Product

Fragment

Product

Adapter

Action

Observes

Cloud

Firestore

1

2

3

5

6

7

4

Demonstração

Referências

Onde me encontrar