Reputation: 1794
In my Firebase Android Application, Every time a user likes a Post, I need to create a link between this user and the post post and increment the "number of likes" of this Post (this number can be > 10000 or more).
According to the doc. Here: https://firebase.google.com/docs/database/android/read-and-write#save_data_as_transactions
I can use a transaction to increment this counter.
But I want to enable the Offline Capacity.
Problem: in the Firebase documentation it's written that "Transactions are not persisted across app restarts".
So how can I manage this use case: "a User likes 20 posts while offline and then stop the application"?
Upvotes: 2
Views: 1116
Reputation: 4040
An easy way to do this is by running the transaction using WorkManager:
The WorkManager API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
So your transaction is guaranteed to run!
Sample code:
public class TransactionWorker extends Worker {
private static final String TAG = "TransactionWorker";
private boolean success = false;
private boolean started = false;
public TransactionWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
//database path
String path = getInputData().getString("path");
//increment amount
long increment = getInputData().getLong("increment",0);
DatabaseReference ref;
try{
//if database reference does not exist yet
// (e.g. app not closed or database not started yet)
ref = FirebaseDatabase.getInstance().getReference(path);
}catch (NullPointerException error){
error.printStackTrace();
return Result.retry();
}
//wait while the transaction did not succeed yet
while(!success){
//only sleep if the transaction already started
if(started){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
//start transaction only if not started yet
ref.runTransaction(new Transaction.Handler() {
@NonNull
@Override
public Transaction.Result doTransaction(@NonNull MutableData mutableData) {
long value = 0;
try{
value = mutableData.getValue(Long.class);
}catch (Exception e){
e.printStackTrace();
}
//increment value
mutableData.setValue(value + increment);
return Transaction.success(mutableData);
}
@Override
public void onComplete(@Nullable DatabaseError databaseError, boolean b, @Nullable DataSnapshot dataSnapshot) {
Log.d(TAG, "onComplete: " + b);
success = b;
}
});
started = true;
}
//work succeeded
return Result.success();
}}
From your Activity:
public static void doIncrementTransaction(String path,final long increment){
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build();
Data transactData = new Data.Builder().putString("path",path)
.putLong("increment",increment).build();
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.
Builder(TransactionWorker.class).setInputData(transactData)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.LINEAR,3000, TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);
}
That's it.
Hope it helps!
Upvotes: 1
Reputation: 599766
Since a transaction requires access to the current value of a field to determine the new value of a field, there is no way to run transactions while you're not connected to the database server.
The Firebase clients don't persist transactions across app restarts for the same reasons: the concept of transactions doesn't work well when a user is not connected.
If you want to record user actions while they are not connected, you should literally store that in your database: the user actions.
So instead of trying to increase the likeCount
with a transaction, you could keep a list of likedPosts
for the user:
likedPosts
uidOfTooFoo
post1: true
post3: true
uidOfTooPuf
post2: true
post3: true
With this structure you don't need a transaction to increase a counter, because each user is essentially isolated from everyone else.
Alternative, you could keep a queue of like actions:
likesQueue
-K234782387432
uid: "uidOfPoofoo"
post: post1
-K234782387433
uid: "uidOfPuf"
post: post2
-K234782387434
uid: "uidOfPuf"
post: post3
-K234782387434
uid: "uidOfPoofoo"
post: post3
With this last structure, you'd then spin up a small back-end service that consumes this queue (by listening for child_added
events or preferably by using firebase-queue) and then increases the shared counter.
Upvotes: 3