Skip to content

Firebase-Repository

Firebase Repository

For every operation with firebase we first need to create a repository interface. All interface methods names are convention based. For example following is a sample interface for workingwith transactions stored in Firebase.

    [FirebasePath("transactions")]
    public interface IFirebaseTrasactionRepository
    {
        // Will add to `/users/{userId}/transactions/{transactionId}`
        // if transactionId is null then path will be: `/users/{userId}/transactions/`
        Task AddTransactionByIdAsync(string userId, string transactionId, Transaction transaction);

        // Will add to `/transactions/{transactionId}`
        // if transactionId is null then path will be: `/transactions/`
        Task AddTransactionByIdAsync(string transactionId, Transaction transaction);

        // Will add to `/users/{userId}/transactions`
        Task<string> AddTransactionAsync(string userId, Transaction transaction);

        // Will add to `/transactions`
        Task<string> AddTransactionAsync(Transaction transaction);

        Task DeleteTransactionAsync(string userId, string transactionId);
        Task DeleteTransactionAsync(string transactionId);

        string GetToken(string userId);

        // Will Read from `/users/{userId}/transactions/{transactionId}`
        // if transactionId is null then path will be: `/users/{userId}/transactions/`
        Task<Transaction> GetTransactionByIdAsync(string userId, string transactionId);

        // Will Read from `/transactions/{transactionId}`
        // if transactionId is null then path will be: `/transactions/`
        Task<Transaction> GetTransactionByIdAsync(string transactionId);

        Task SendPushNotificationAsync(FirebasePushMessage message);

        // Override Node Path
        // Will update path `/users/{userId}/balance/{balanceRecordId}`
        // if balanceRecordId is null then path will be: `/users/{userId}/balance/`
        [FirebasePath("balance")]
        Task UpdateUserBalance(string userId, string balanceRecordId, AvailableBalanceModel balance);

        // Just update/add properties directly under user/{userid} node
        [FirebasePath(null)]
        Task UpdateUserBalanceProperty(string userId, AvailableBalanceModel balance);
        [FirebasePath(null)]
        Task UpdateUserBalanceProperty(AvailableBalanceModel balance);

        // Just update/add properties directly under user/{userid}/balance node
        [FirebasePath("balance")]
        Task AddUserBalanceProperty(string userId, AvailableBalanceModel balance);
        [FirebasePath("balance")]
        Task AddUserBalanceProperty(AvailableBalanceModel balance);

        Task UpdateTransactionStatusAsync(string userId, string transactionId, TransactionStatus status);
        Task UpdateTransactionStatusAsync(string transactionId, TransactionStatus status);
    }

Firebase Path Attribute

Every record will have its own path under the firebase data tree. So we will need to specify a path name for each node type as an attribute on top of repository class. In this case since we are working with transactions, hence we will be be storing records at either of the following paths

  • /transactions/{transaction-id}
  • /users/{user-id}/transactions/{transaction-id}

All records stored under /users node are user specific and only associated user will be able to read/write those those records. No other user can see those records. On the other hand all records which are not under /users node will be treated as global records and any authenticated user will be able to work with those records. Note that we still need to first configure security rules in firebase console. A sample rules configuration is as follows:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    },
    "global": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}

Note that attribute can also be placed on a method declaration in repository interface. This will override attribute value from interface declaration.

Repository Method Naming Conventions

  • All methods starting with Add and ending with ById or ByIdAsync will be treated as an eqvivalent as a call to ReplaceRecordAsync() in IFirebaseService. This method must contain 3 or 2 parameters for user-specific or global records respectively. E.g.
    AddTransactionByIdAsync(string userId, string transactionId, Transaction transaction) will store a transaction at /users/{userId}/transactions/{transactionId}. If a data already exists at this location it will be overwritten. It's other counterpart AddTransactionByIdAsync(string transactionId, Transaction transaction) will create a global record at /transactions/{transactionId}
  • All other methods starting with Add and NOT ending with ById or ByIdAsync will be treated as an eqvivalent as a call to AddRecordAsync() in IFirebaseService. Note that this method does not expect a user-scpefied id for new record, instead it will return firebase generated key. This method must contain 2 or 1 parameters for user-specific or global records respectively. E.g.
    AddTransactionAsync(string userId, Transaction transaction) will store a transaction at /users/{userId}/transactions/{firebase-key}.
  • Same logic applies for Delete, Get*ById, Get*ByIdAsync and Update methods. For Update method user does not need to provide an object of Transaction class, it can be any class type as shown in above interface. e.g. TransactionStatus in above case will only update status property of Transaction record.
    public class TransactionStatus
    {
        public string Status { get; set; }
    }
    
  • GetToken() and SendPushNotificationAsync() methods will be matched to eqvivalent methods in IFirebaseService.

Registering Repository

We also need to register our newly created repository in AutoFac. For this we will register FirebaseModule and our Repository as follows:

builder.RegisterModule<FirebaseModule>();

builder.RegisterFirebaseRepository<IFirebaseTrasactionRepository>();