Skip to content

Instantly share code, notes, and snippets.

@NiPfi
Created April 18, 2018 07:36
Show Gist options
  • Save NiPfi/f00dc0f8178ef907202ed1046ced2746 to your computer and use it in GitHub Desktop.
Save NiPfi/f00dc0f8178ef907202ed1046ced2746 to your computer and use it in GitHub Desktop.
Basic implementation of a repository pattern for Firebase Firestore
package ch.jojoni.jamplan.model.repository;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import ch.jojoni.jamplan.model.Identifiable;
/**
* Manages data access for Firebase
*/
public class FirestoreRepository<TEntity extends Identifiable<String>> implements Repository<TEntity, String> {
private static final String TAG = "FirestoreRepository";
private final Class<TEntity> entityClass;
private final CollectionReference collectionReference;
private final String collectionName;
/**
* Initializes the repository storing the data in the given collection. Should be from {@link FirestoreCollections}.
*/
public FirestoreRepository(Class<TEntity> entityClass, String collectionName) {
this.collectionName = collectionName;
this.entityClass = entityClass;
FirebaseFirestore db = FirebaseFirestore.getInstance();
this.collectionReference = db.collection(this.collectionName);
}
@Override
public Task<Boolean> exists(final String documentName) {
DocumentReference documentReference = collectionReference.document(documentName);
Log.i(TAG, "Checking existence of '" + documentName + "' in '" + collectionName + "'.");
return documentReference.get().continueWith(new Continuation<DocumentSnapshot, Boolean>() {
@Override
public Boolean then(@NonNull Task<DocumentSnapshot> task) {
Log.d(TAG,"Checking if '" + documentName + "' exists in '" + collectionName +"'.");
return task.getResult().exists();
}
});
}
@Override
public Task<TEntity> get(String id) {
final String documentName = id;
DocumentReference documentReference = collectionReference.document(documentName);
Log.i(TAG, "Getting '" + documentName + "' in '" + collectionName + "'.");
return documentReference.get().continueWith(new Continuation<DocumentSnapshot, TEntity>() {
@Override
public TEntity then(@NonNull Task<DocumentSnapshot> task) throws Exception {
DocumentSnapshot documentSnapshot = task.getResult();
if (documentSnapshot.exists()) {
return documentSnapshot.toObject(entityClass);
} else {
Log.d(TAG, "Document '" + documentName + "' does not exist in '" + collectionName + "'.");
return entityClass.newInstance();
}
}
});
}
@Override
public Task<Void> create(TEntity entity) {
final String documentName = entity.getEntityKey();
DocumentReference documentReference = collectionReference.document(documentName);
Log.i(TAG, "Creating '" + documentName + "' in '" + collectionName + "'.");
return documentReference.set(entity).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d(TAG, "There was an error creating '" + documentName + "' in '" + collectionName + "'!", e);
}
});
}
@Override
public Task<Void> update(TEntity entity) {
final String documentName = entity.getEntityKey();
DocumentReference documentReference = collectionReference.document(documentName);
Log.i(TAG, "Updating '" + documentName + "' in '" + collectionName + "'.");
return documentReference.set(entity).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d(TAG, "There was an error updating '" + documentName + "' in '" + collectionName + "'.", e);
}
});
}
@Override
public Task<Void> delete(final String documentName) {
DocumentReference documentReference = collectionReference.document(documentName);
Log.i(TAG, "Deleting '" + documentName + "' in '" + collectionName + "'.");
return documentReference.delete().addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d(TAG, "There was an error deleting '" + documentName + "' in '" + collectionName + "'.", e);
}
});
}
}
package ch.jojoni.jamplan.model;
import com.google.firebase.firestore.Exclude;
/**
* Represents an object that can be uniquely identified among other objects of the same type
* by using an UID.
*
* @param <TKey> type of the unique key (UID) this object is uniquely identified by. The type needs
* a correct implementation of its equals() method or the behaviour of code using this
* interface will be undefined.
*/
public interface Identifiable<TKey> {
@Exclude
TKey getEntityKey();
}
package ch.jojoni.jamplan.model.repository;
import com.google.android.gms.tasks.Task;
import ch.jojoni.jamplan.model.Identifiable;
/**
* Manages data access for POJOs that are uniquely identifiable by a key, such as POJOs implementing {@link Identifiable}.
*/
public interface Repository<TEntity extends Identifiable<TKey>, TKey> {
/**
* Checks the repository for a given id and returns a boolean representing its existence.
* @param id the unique id of an entity.
* @return A {@link Task} for a boolean which is 'true' if the entity for the given id exists, 'false' otherwise.
*/
Task<Boolean> exists(TKey id);
/**
* Queries the repository for an uniquely identified entity and returns it. If the entity does
* not exist in the repository, a new instance is returned.
* @param id the unique id of an entity.
* @return A {@link Task} for an entity implementing {@link Identifiable}.
*/
Task<TEntity> get(TKey id);
/**
* Stores an entity in the repository so it is accessible via its unique id.
* @param entity the entity implementing {@link Identifiable} to be stored.
* @return An {@link Task} to be notified of failures.
*/
Task<Void> create(TEntity entity);
/**
* Updates an entity in the repository
* @param entity the new entity to be stored.
* @return A {@link Task} to be notified of failures.
*/
Task<Void> update(TEntity entity);
/**
* Deletes an entity from the repository.
* @param id uniquely identifying the entity.
* @return A {@link Task} to be notified of failures.
*/
Task<Void> delete(TKey id);
}
@gastsail
Copy link

gastsail commented Apr 7, 2019

This is excellent !! Thanks :)

@ankurg22
Copy link

ankurg22 commented Dec 9, 2019

@NiPfi This is great!! One question though, how can I query by adding whereEqualTo and orderBy on get method?

@wonsuc
Copy link

wonsuc commented Jun 30, 2020

Is there a reason these codes are written in Java instead of Kotlin?

@NiPfi
Copy link
Author

NiPfi commented Jun 30, 2020

@wonsuc There's a few but it basically boils down to this code being created for a school project during one semester and the school primarily teaching programming using Java. Also, when I wrote this code 2 years ago, Google was still in the process of making Kotlin the primary language for Android and since I don't program for Android professionally now, it didn't make sense to learn Kotlin for the purpose of a single semester.

@wonsuc
Copy link

wonsuc commented Jul 1, 2020

@NiPfi Thanks for the answer, I just wondered if I can't use Repository pattern with Kotlin for Firestore.

@NiPfi
Copy link
Author

NiPfi commented Jul 1, 2020 via email

@Aleksandr-Nebobzod
Copy link

Aleksandr-Nebobzod commented Apr 26, 2024

Found conflicting getters for name "getEntityKey" on class...
...
I had the same issue. Found out why. When you implement the identifiable interface, you need to also write the @exclude above @OverRide.
-- @NiPfi Thank you! This @Exclude firestore interface quite useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment