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
Addand ending withByIdorByIdAsyncwill be treated as an eqvivalent as a call toReplaceRecordAsync()inIFirebaseService. 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 counterpartAddTransactionByIdAsync(string transactionId, Transaction transaction)will create a global record at/transactions/{transactionId}
- All other methods starting with
Addand NOT ending withByIdorByIdAsyncwill be treated as an eqvivalent as a call toAddRecordAsync()inIFirebaseService. 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*ByIdAsyncandUpdatemethods. ForUpdatemethod user does not need to provide an object ofTransactionclass, it can be any class type as shown in above interface. e.g.TransactionStatusin above case will only update status property of Transaction record.
public class TransactionStatus { public string Status { get; set; } }
GetToken()andSendPushNotificationAsync()methods will be matched to eqvivalent methods inIFirebaseService.
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>();