Firestore offline persistence error when using workmanager - android

I'm using a WorkManger to retrieve information periodically from my Firestore database when the app is in the background and foreground. This information is used to update the UI based on a status (so different statuses adds or removes different parts of the UI). On first run this works well, however, once the app is in the background and the WorkManager tries to run, it crashes with this error:
Caused by: java.lang.RuntimeException: Failed to gain exclusive lock to the Firestore client's offline persistence. This generally means you are using Firestore from multiple processes in your app. Keep in mind that multi-process Android apps execute the code in your Application class in all processes, so you may need to avoid initializing Firestore in your Application class. If you are intentionally using Firestore from multiple processes, you can only enable offline persistence (i.e. call setPersistenceEnabled(true)) in one of them.
Originally I did: FirebaseFirestore firestore = FirebaseFirestore.getInstance(); in all activities and fragments because the error stated it was a bad idea to initialise inside the application class. However, I still received the error and I even tried creating just a single instance of the initialisation inside the application class like so:
public FirebaseFirestore getFirestore(){
if(firestore == null){
firestore = FirebaseFirestore.getInstance();
}
return firestore;
}
But I'm still seeing this error whenever the WorkManager runs. I've tried the solution from this question since it's a bit similar but it didn't resolve the error. I also saw somewhere that firestore can't be called on a separate thread, or I could be mistaken.
I'm fairly certain it has to do with how I initialize and access the firestore instance but I'm not sure how to fix it. I tried using separate initializations in each activity, fragment and service where it's being used, with no luck.
EDIT
Worker Class:
public class PopulateThemeCache extends Worker {
#Inject
FirebaseMethods mFirebaseMethods;
private ThemeDao themeDao;
private String themeKey;
public PopulateThemeCache() {
FireApp.getApp().getApplicationComponent().inject(this);
themeDao = PictematicOfflineCache.getDatabase(getApplicationContext()).themeDao();
}
#Override
#NonNull
public Result doWork() {
try{
Query query = FireApp.getApp().getFirestore().collection("themes").orderBy("start_time");
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
//executed code here
}
});
Overall overall = new Overall();
query = FireApp.getApp().getFirestore().collection("profile_posts");
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
//executed code
}
});
return Result.SUCCESS;
}catch (Throwable throwable){
Log.e("PopulateThemeCache", "Error caching", throwable);
return Result.FAILURE;
}
}}
MainActivity onCreate:
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(PopulateThemeCache.class, 2, TimeUnit.HOURS)
.addTag("Theme_Update")
.build();
WorkManager.getInstance().enqueue(request);
I integrated firebase using the Android Studio Assistant tool. The above worker class queries a theme node and checks for the currently available theme and updates a room database.

Related

Issue while parsing a list of objects retrieved from Firebase Firestore collection

I am working on an android project and I've been trying to use Firebase Firestore collections. Everything seems fine up until the second activity retrieving the intent with the parsed list of watches.
public class MainActivity extends AppCompatActivity {
private FirebaseFirestore db = FirebaseFirestore.getInstance();
private CollectionReference watchesRef = db.collection("watches");
private List<Watch> watches = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
watchesRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Watch watch = document.toObject(Watch.class);
watches.add(watch);
}
Intent intent = new Intent(MainActivity.this, WatchesActivity.class);
intent.putParcelableArrayListExtra("watches", (ArrayList<Watch>) watches);
startActivity(intent);
} else {
Log.d("MainActivity", "Error getting documents: ", task.getException());
}
}
});
Say I wanted to print out each watch upon retrieval from firstore, it works just fine. When debugging, I can see the list getting filled with the watches. Debugging goes through to the next activity:
Intent intent = getIntent();
if (intent.hasExtra("watches")) {
watches = intent.getParcelableArrayListExtra("watches");
}
Right up until watches = intent.getParcelableArrayListExtra("watches"); I can see the data just fine, then I get this error java.lang.RuntimeException: Parcel android.os.Parcel#f983085: Unmarshalling unknown type code 7536745 at offset 316
KEEP IN MIND: When I clear the list then add static data, it works just fine.
Overall your approach to Android app architecture is suffering here, fetching a potentially large list of data and then attempting to pass it via an Intent to another Activity is an architecture that won't scale well.
It may fail when the parcel data size exceeds 1MB.
It may fail because Watch doesn't use only primitives or hasn't implemented itself well as Parcelable.
It may fail because when the user opens that Activity, the Watch list is so old and out-dated that your updated app crashes.
A better way to architect the app is to have the Activity that you navigate to fetch the data it needs itself.
If/when you use Intents, keep the payloads minimal. Try to pass just a single id with which the recipient can retrieve from local database or fetch from the Internet with.

Changes are not observable by viewmodel

I have created an app which is relied on my local server which fetch profile image and information about user..Code works fine without any problem but when I change my data in the local server (for example profile picture )the updated profile is not reflecting in the application until activity is restarted but this should not be happened because live data should reflect the change immediately as soon as changes occurred in the database.
below is the code of live data class
private MutableLiveData<Profile> profileMutableLiveData;
public void init(String token){
if (profileMutableLiveData!=null){
return;
}
repository=Repository.getInstance();
profileMutableLiveData=repository.getProfile(token);
}
public LiveData<Profile> getProfile(){
return profileMutableLiveData;
}
here is my Repository code
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if (instance==null){
instance=new Repository();
}
return instance;
}
public MutableLiveData<Profile> getProfile(String token){
MutableLiveData<Profile> data=new MutableLiveData<>();
RetrofitApi retrofitApi=RetrofitInstance.getInstance();
Call<Profile> call=retrofitApi.getProfile(token);
call.enqueue(new Callback<Profile>() {
#Override
public void onResponse(Call<Profile> call, Response<Profile> response) {
Profile profile=response.body();
if (response.isSuccessful()){
data.setValue(profile);
}
}
#Override
public void onFailure(Call<Profile> call, Throwable t) {
}
});
return data;
}
}
Code in main activity to observe changes....
actually I am showing profile image in navigation drawer ... like telegram app
viewModelClass = new ViewModelProvider(this).get(ViewModelClass.class);
viewModelClass.init(token);
viewModelClass.getProfile().observe(this, new Observer<Profile>() {
#Override
public void onChanged(Profile profile) {
Picasso.get().load("http://192.168.43.216:8000" + profile.getProfile_photo()).into(profileImage);
fName = profile.getFirst_name();
lName = profile.getLast_name();
image = profile.getProfile_photo();
nameView.setText("Hello " + profile.getFirst_name());
}
});
}
The code is working fine but I want the data must be updated as soon as changes made in my server...
but data is updated when I restart the activity or opening app again after closing the activity...
May be the problem - is that you begin to observe in your activity one instance of MutableLiveData, and then you replace it with another one.
In your ViewModel:
profileMutableLiveData=repository.getProfile(token);
you override it instead of setting new value with "postValue"
In your Repository:
MutableLiveData<Profile> data=new MutableLiveData<>();
you make another instance of LiveData
You can try to change your return value from a Repository to a "Profile" and set it as a new value of MutableLiveData in your ViewModel with "postValue"
UPDATED
I've read your question more carefully. I think my answer above wouldn't give you what you expect (in case you expect Retrofit should update LiveData instantly like ROOM does)
So my thoughts:
You expect too much using LiveData+Retrofit. Just using them doesn't mean you'll get on-line updates of your data on your server. To achieve that you have to change mechanism of your interaction with your server, not just fix few lines in code you've shown.
There is mechanism LiveData+ROOM that works with local DB (Sqlite) in a way, that you expect from LiveData+Retrofit. But there is no magic there. Room is using mechanic, that built-in in Sqlite for notifying (triggering) when there are some changes in DB tables occur. But Retrofit doesn't implement similar mechanism with Rest Api and actually it's not its responsibility.
To achieve what you want you can look at several possibilities:
To use some Cloud Service API, that contains that built-in mechanism for notifying your device when data changes (Firebase, for example)
To implement some kind of periodic synchronisation of your app data with server. After this synchronisation you'll have all data on device and depending on where you put your data you could observe changes with LiveData+Room or FileObserver.
To simplify your case and refresh your data from the server at activity explicitly after click on Button "Refresh" on your activity. In that case you can implement steps that I wrote at first version of my answer.

Previous task(s) waiting on Firebase Realtime Database need to complete first before new one starts

I'm using the Task API in my app to retrieve data from Firebase Database, which is usually from different nodes. I have a helper class for Firebase Database like so:
public class FirebaseDbHelper {
public Task<DataSnapshot> getData() {
TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference(FIRST_NODE).child(SUB_NODE);
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
source.setResult(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
source.setException(databaseError.toException());
}
});
return source.getTask();
}
}
As you can see, getData() returns a Task object, which I use on my interactor class (I'm using the MVP architecture for my app) like so:
public class TestDbInteractor {
private FirebaseDbHelper mDbHelper;
private Listener mListener;
public TestDbInteractor(#NonNull Listener listener) {
mDbHelper = new FirebaseDbHelper();
mListener = listener;
}
void getData() {
mDbHelper.getData().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
public interface Listener {
void onGetDataSuccess(MyObject object);
void onGetDataFailed(Exception exception);
}
}
This works as expected. However, we noticed a behavior that when retrieving a lot of data, even if the activity that started the task is already finish()ed, the task still proceeds and attempts to complete. This I believe, is something that could be considered as a memory leak, since a process is still going even though it's supposed to be stopped/destroyed already.
What's worse is that when I try to get a different data (using a different Task in a different activity to a different node in Firebase), we noticed that it waits for the previous task to complete first before proceeding with this new one.
To give more context, we're developing a chat app similar to Telegram, where users could have multiple rooms and the behavior we saw is happening when a user enters a room. This is the flow:
User enters room, I request data for the room details.
Upon getting the room details, I display it, then request for the messages. I only retrieve the most recent 10. During this time, I just show a progress bar on the activity.
In order for the message details to be complete, I get data from different nodes on Firebase, this is where I use Tasks mainly.
After getting the messages, I pass it on to the View, to display the messages, then I attach a listener for new messages. Everything works as expected.
The behavior I mentioned at the beginning is noticeable when the user does something like this:
User enters a room with messages, room details are retrieved instantly, messages are still loading.
User leaves the room (presses the back button), this gets the user back to the room list, and enters a different one.
At this point, the retrieval of the room details takes such a long time - which we thought was odd, since the data isn't really that big to begin with.
After a few more testing, we concluded that the long retrieval time was caused by the current task (get room details) is still waiting for the previous task (get messages) started in a different activity, to finish first before starting.
I attempted to implement my answer here, trying to use a CancellableTask, but I am at a loss on how to use it with my current implementation, where I use a TaskCompletionSource, where you could only set a result or an exception.
I was thinking this could work if I move the task completion source to the interactor class level instead of the helper -- I haven't tried it yet. I think it's possible, but would take a lot of time to refactor the classes I already have.
So I figure why not try Doug's answer, using activity-scoped listeners. So I tested it like below.
In my activity, I added a getActivity() method, which can be called in the presenter:
public class TestPresenter
implements TestDbInteractor.Listener {
private View mView;
private TestDbInteractor mDbInteractor;
#Override
void bindView(View view) {
mView = view;
mDbInteractor = new TestDbInteractor(this);
}
#Override
void requestMessages() {
mDbInteractor.getData(mView.getActivity());
}
// Listener stuff below
}
and updated my getData() like so:
void getData(#NonNull Activity activity) {
mDbHelper.getData().addOnCompleteListener(activity, task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
Unfortunately, this doesn't seem to work though, exiting the activity still waits for the tasks to complete, before the new task initiated in a different activity starts.
If you kick off a query to Realtime Database, it will always run to completion, whether or not there are any listeners attached to the Task that was returned. There is no way to cancel that work, neither by removing the last listener manually, nor by using activity-scoped listeners that are removed automatically. Queries in motion stay in motion. Also, all traffic to and from RTDB is pipelined over a single socket, which implies that the results of subsequent queries after one that's incomplete will have to wait for the everything ahead of it in the queue to complete first. This is likely the root cause for your observation - you have an incomplete query that other queries are waiting on, regardless of your use of the Task API.
Fortunately, if you have persistence enabled, the second query should be served by the cache of the first query, and not require another round trip to the server.
If you need to make sure that you retain the results of the first query across configuration changes that destroy the activity, then you should use something like LiveData from the Android architecture components to manage this, so that you can pick up the query where it left off after a configuration change. If you do this, don't use activity-scoped listeners.
I've written a three-part blog post about using architecture components with Firebase, which may also be of interest.
Hey You can use childEventListener. use dataSnapshot.getChildrenCount().
dbFriend=FirebaseDatabase.getInstance().getReference("Friend");
dbFriend=dbFriend.child(mPreferences.getString("username","")).child("already");
dbFriend.addChildEventListener(new ChildEventListener() {
int already=0;
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Username u=dataSnapshot.getValue(Username.class);
already=alread+1;
if(already >= dataSnapshot.getChildrenCount()){
//get to know when data fetching got completed
}
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});

Realm instance has already been closed - with RxJava2

I used Realm in conjunction with RxJava it this way:
public Flowable<List<EventEntity>> getAll() {
try (final Realm realm = Realm.getInstance(mRealmConfiguration)) {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
Flowable<RealmResults<RealmEvent>> result;
if (realm.isAutoRefresh()) {
result = query
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded);
} else {
result = Flowable.just(query.findAll());
}
return result
.unsubscribeOn(AndroidSchedulers.mainThread());
}
}
I use this chain on multiple places in app. For example:
return Observable.merge(
mEventRepository.getAll()
.toObservable(),
subjectNotificationChange
.flatMapMaybe(notification ->
mEventRepository.getAll()
.firstElement()
)
)
Problem is that I obtain exception: java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable.
I looked at implementation method from of RealmObservableFactory and each call of subscribe method should create new instance of Realm. Entire situation looks as problem with references counting.
Do you know where is problem?
Java's try-with-resource closes the resource as soon as you leave the code block, but RxJava being lazy and all, only begins working when you actually subscribe, which happens after your code exits the getAll() function.
Edit: since you build a special Realm instance each time, passing configuration to it, the instance is not shared and therefore definitively closed each time.
Instead, initialize your Realm earlier using Realm.setDefaultConfiguration(config). Then, use Realm.getDefaultInstance() in your function so you access the default shared instance instead of creating a new one each time.
Edit2: the easiest solution is to keep a reference to the Realm instance:
class MyRepository {
private final Realm realm;
public MyRepository(Realm realm) {
this.realm = realm;
}
public Flowable<List<EventEntity>> getAll() {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
// ...
}
}
Realm realm = Realm.getDefaultInstance();
MyRepository repository = MyRepository(realm);
repository.getAll()
// ...
I find solution. It is bug in official example. When you call mentioned chain than must exist other open Realm instance for same thread. In other cases RealmResult is invalidated. Can be used solution mentioned by ESala.

Managing Android ORMLite Concurrency and Overwriting

I'm trying to solve a (hypothetical) concurrency problem in my Android app that uses ORMLite for the database management.
In particular, I have a ContentService class that manages the database, here's some code (simplified to understand the problem):
public ContentManagerImpl(Context context) {
mContext = context;
configure();
// Once configured, get the DatabaseHelper
DatabaseManager.configure(context);
}
private void configure() {
// If needed, unzip a folder with an sqlite file
// representing the database and saves it on device
}
#Override
public void checkForUpdate() {
// Checks if new database version is available
new CheckForUpdateTask(CheckForUpdateTask.CheckForUpdateCallback() {
#Override
public void onCheckForUpdateFinished(boolean updateNeeded) {
if (updateNeeded) {
update();
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
#Override
public void update() {
// Downloads the new database zip, unzip the folder and sava an sqlite file
// representing the database
new UpdateTask(mContext, mMetaData, new UpdateTask.UpdateCallback() {
#Override
public void onUpdateFinished() {
DatabaseManager.refresh();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
This class is created in the onCreate method of my Android Application class.
Since in the app I have only one Activity, the checkForUpdate method is called every time the onResume of that activity is called.
The problem is, sometimes when there's an update needed and the app is resumed (or even when first launched) I get an exception on a database query performed inside the DatabaseManager.configure() and DatabaseManager.refresh() methods.
That's because I try to get the first element of a single-row table but the query returns an empty list. It seems that the database is not ready, or that someone is still writing on it. I've also tried some lock mechanism but without luck.
So, since is the main (UI) thread that overwrites the database file, my questions are:
Can it be a problem to access the database from multiple threads/tasks?
Can I perform database queries on a separated thread if I write the db file on the UI thread?
Thank you all for your support.

Categories

Resources