This repository has been archived by the owner on Jan 5, 2023. It is now read-only.
/
FirebaseUserDataSyncHelper.java
162 lines (142 loc) · 6.61 KB
/
FirebaseUserDataSyncHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.samples.apps.iosched.sync.userdata.firebase;
import android.content.Context;
import android.text.TextUtils;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper;
import com.google.samples.apps.iosched.sync.userdata.UserAction;
import com.google.samples.apps.iosched.util.FirebaseUtils;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
import static com.google.samples.apps.iosched.util.LogUtils.LOGI;
import static com.google.samples.apps.iosched.util.LogUtils.LOGW;
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
/**
* Performs Firebase authentication if necessary and syncs local data with remote data in Firebase.
* This class queries live Firebase data to perform a sync, explicitly not relying on the {@link
* Firebase#keepSynced(boolean)} functionality in order to have more control over conflict
* resolution for different types of data being stored.
*/
public class FirebaseUserDataSyncHelper extends AbstractUserDataSyncHelper
implements FirebaseAuthCallbacks {
private static final String TAG = makeLogTag(FirebaseUserDataSyncHelper.class);
/**
* Wait time for the Firebase sync to complete before we exit from {@code syncImpl()}.
*/
public static final int AWAIT_TIMEOUT_IN_MILLISECONDS = 10000; // 10 seconds.
/**
* Tracks if the sync process changed local data.
*/
private boolean mDataChanged = false;
/**
* Lock used to prevent {@code syncImpl()} from exiting before we're done syncing with
* Firebase.
*/
private CountDownLatch mCountDownLatch;
/**
* A list of {@link UserAction}s that are involved in the data sync.
*/
private List<UserAction> mActions;
/**
* Constructor.
*
* @param context The {@link Context}.
* @param accountName The name associated with the currently chosen account.
*/
public FirebaseUserDataSyncHelper(Context context, String accountName) {
super(context, accountName);
}
@Override
protected boolean syncImpl(final List<UserAction> actions, final boolean hasPendingLocalData) {
mActions = actions;
mCountDownLatch = new CountDownLatch(1);
// The Firebase shard where data is synced.
String firebaseUrl = FirebaseUtils.getFirebaseUrl(mContext, mAccountName);
if (TextUtils.isEmpty(firebaseUrl)) {
LOGW(TAG, "Cannot proceed with user data sync: Firebase url is not known.");
return mDataChanged;
}
// The Firebase reference used to sync data.
final Firebase firebaseRef = new Firebase(firebaseUrl);
boolean authenticated = !TextUtils.isEmpty(FirebaseUtils.getFirebaseUid(mContext));
if (authenticated) {
LOGW(TAG, "Already authenticated with Firebase.");
performSync(actions);
} else {
// Authenticate and wait for onAuthSucceeded() to fire before performing sync.
new FirebaseAuthHelper(mContext, firebaseRef, this).authenticate();
}
try {
// Make the current thread wait until we've heard back from Firebase.
LOGW(TAG, "Waiting until the latch has counted down to zero");
mCountDownLatch.await(AWAIT_TIMEOUT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
} catch (InterruptedException exception) {
LOGW(TAG, "Waiting thread awakened prematurely", exception);
}
LOGD(TAG, "local data changed after sync = " + mDataChanged);
return mDataChanged;
}
/**
* Syncs local data with remote data in Firebase. Assumes Firebase authentication has
* successfully completed. See {@link FirebaseDataReconciler} for details on how remote and
* local data is merged.
*
* @param actions The user actions that triggered the sync.
*/
private void performSync(final List<UserAction> actions) {
// Do a one-time read of Firebase data (equivalent to an asynchronous query call) located
// at /<data_path>/<uid>/.
FirebaseUtils.getDataUIDRef(mContext, mAccountName).addListenerForSingleValueEvent(
new ValueEventListener() {
@Override
public void onDataChange(final DataSnapshot dataSnapshot) {
FirebaseDataReconciler firebaseDataReconciler =
new FirebaseDataReconciler(mContext, mAccountName, actions,
dataSnapshot);
firebaseDataReconciler.buildRemoteDataObject()
.buildLocalDataObject()
.merge()
.updateRemote()
.updateLocal();
FirebaseUserDataSyncHelper.this.mDataChanged =
firebaseDataReconciler.localDataChanged();
LOGW(TAG, "Done syncing with Firebase. Decrementing latch count.");
mCountDownLatch.countDown();
}
@Override
public void onCancelled(final FirebaseError firebaseError) {
LOGW(TAG, "firebaseError = " + firebaseError);
mCountDownLatch.countDown();
}
});
}
@Override
public void onAuthSucceeded(final String uid) {
FirebaseUtils.setFirebaseUid(mContext, mAccountName, uid);
performSync(mActions);
}
@Override
public void onAuthFailed() {
// Clear out the <uid> previously obtained from Firebase.
FirebaseUtils.setFirebaseUid(mContext, mAccountName, "");
mCountDownLatch.countDown();
incrementIoExceptions();
}
}