Get notified when data changed in database - android

I'd like to know how can I be notified when data change in my database.
Is there a solution to this or not?
The fact is I know how to push and get data in it but I don't know how to be notified when a modification happens

If the responsibility changing the data in your database is your app, you should create a database layer and then create some callback to notify your app your data has an Insert, update or delete operation.
If you use sqlite like local database:
First approach, using the android SDK content provider, the content observer class. Create a content observer that monitors some table in your local sqlite.
http://www.grokkingandroid.com/use-contentobserver-to-listen-to-changes/
If you use a ORM, there are callbacks in the ORM to notify changes. For example GreenDao using AsyncSession:
AsyncSession asyncSession = App.getInstance().daoSession.startAsyncSession();
asyncSession.setListener( new AsyncOperationListener() {
#Override
public void onAsyncOperationCompleted(AsyncOperation operation) {
// do whats needed
}
});
asyncSession.insert(MyObject);
If you use Realm.IO, there is a callback from the realm transaction to notify is an update is done.
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
MyObject myobject = new MyObject();
myobject.setId(UUID.randomUUID().toString());
myobject.setName("My object name test");
realm.copyToRealm(myobject);
}
}, new Realm.Transaction.Callback() {
#Override
public void onSuccess() {
//realm already update
Log.e("lh", "this callback i can use to notify. after save " + realm.allObjects(MyObject.class).size());
realm.close();
}
});
Changues in the backend.
If the changes are in the backend. A possible solution is send a push notification and start to do a background service in the app.
In this URL you have a PHP script to do a push notification to google cloud.
https://gist.github.com/prime31/5675017
You must attach the google cloud push notification receiver in you android app and create a specific service that will do an HTTP request to download the new data if you expose your database with a REST endpoint.

the most simple way is to keep a row in database and update its value when data is changed.

Related

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.

LiveData - Observe objects in list

I have a list ofFunctions that is retrieved from a local SQLite database using Room and I want to observe every function in that list. At the moment I'm doing the following:
public List<MutableLiveData<Function>> getLiveFunctions() {
if (liveFunctions == null) {
liveFunctions = new ArrayList<>();
for (Function function : functions) {
//Livedata with a default value on background thread (postValue)
liveFunctions.add(new DefaultLiveData<>(function, true));
}
}
return liveFunctions;
}
After a local fetch from the database, I can request the status of a given function using an RPC to my server. When I receive a response, I can set the new value for that function and I want my UI to observe the changes in that function.
Just some clarifications:
The difference between LiveData<List<Function>>> and List<LiveData<Function>> is that the first will only observe whether an object was added, updated or removed in the list, correct? It's not that LiveData<List<Function>>> also listens to changes on their items?
I'm using a MediatorLiveData to combine my observers to a "FunctionObserver". Is this the correct approach to handle all my function callbacks?
[code]
MediatorLiveData<LiveData<Function>> mediator = new MediatorLiveData<>();
List<MutableLiveData<Function>> functions = //functions
for (LiveData<Function> function : functions) {
mediator.addSource(function, functionObserver);
}
mediator.observe(MainActivity.this, new Observer<LiveData<Function>>() {
#Override
public void onChanged(#Nullable LiveData<Function> functionLiveData) {
Log.i(TAG, "Live data change: ");
}
});
Can my code logic be improved? I know that I can request a LiveData<List<Function>>> from Room but I'm having trouble with my parent class having a #Relation annotation which needs the type to be a List or Set (and not LiveData)

join firebase model and update live data with combined results

HelloBelow is my database structure
I want to first get results from post object and using "userid" from that I want to get results from Users Object.
I want to update ViewModel with the above results that contain fields that are in both the objects
How to achieve this ?
I have written code to get the result from post object but how to again make call to get user object and update viewmodel and livedata object
private static final DatabaseReference POST_REF =
FirebaseDatabase.getInstance().getReference("/Post");
private final FirebaseQueryLiveData liveData = new
FirebaseQueryLiveData(POST_REF);
#NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
There are different ways to model you database to archive this, It is always good to follow best practices see: https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure
In this case, since you only want the Profile Pic and name I would save it directly into the object:
{
"Posts":{
"post1":
{
"likes":23,
"userId":"id",
"user":
{
"imageUrl":"url",
"name":"Name"
}
}
}
}
Of course, the tradeoff is that the image URL won’t be updated if the user node gets updated (unless you code something to update it in a Cloud Function for example)
On the other hand, you could also perform those two calls to the Firebase Realtime Database (one to get the post, and the other to get the user data):
ValueEventListener postListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Post object and use the values to update the UI
Post post = dataSnapshot.getValue(Post.class);
ValueEventListener userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot userDataSnapshot) {
post.setUser(userDataSnapshot.getValue(User.class);)
}
};
mUserReference.addListenerForSingleValueEvent(userListener);//only fetch data once
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
// ...
}
};
mPostReference.addValueEventListener(postListener);
EDIT:
Having the user object inside the post would work using the architecture components (Just be sure to have the proper class to deserialize), on the other hand, since you are using the FirebaseQueryLiveData https://firebase.googleblog.com/2017/12/using-android-architecture-components.html and may want to avoid writing the user on that post node, I think you can have both ViewModels with different Database references, and once the data is fetched you could just update the post object e.g. post.setUser(user) with the user obtained from the other ViewModel Observer and then update the UI. You could also have a HashMap to keep track of what post needs what user, although this answer looks like a way to go: https://stackoverflow.com/a/46483213/1537389. Hope that helps

Firestore offline persistence error when using workmanager

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.

HowTo: Provide notification to Android app whenever SQLite table updates

I am in process of developing an Android tablet app using sqllite 3.7.4 which would
perform following:
Fetches information from the UI
Performs some logic and store related information to the sqlite database
The stored information has to be send immediately OR at schedule
interval (ex. at 5:00 on xyz date) over the network
Currently, we have developed a dispacher mechanism (thread ), which constantly polls the database for new information inserted in the database. The thread fetches the information and send to the network module.
But, I feel this is not the correct approach as
Polling every time is a overhead. There can be times when there is nothing to execute
It is not real time , because we poll after every 5 seconds
So
Is there a way to send a trigger to my network module as soon as information is updated in database?
Or any better way to achieve this task?
Thanks in advance.
This question is about one year ago, but i think this is a common problem. This is how i handled the Database changes:
In my Adapter ( SQL ADAPTER) i have methods for updating / deleting or inserting data into the Database obviously. Like this method:
public long addProduct(String code, String name ... String gid, String gdate) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_CODE, code);
initialValues.put(KEY_NAME, name);
...
initialValues.put(KEY_CHECK, this.checkfalse);
initialValues.put(KEY_GID, gid);
---------
Intent i = new Intent("data_inserted");
i.putExtra("date", date);
sendBroadcast(i);
---------
return mDb.insert(SQLITE_TABLE, null, initialValues);
}
After the change happened it will send an broadcast intent. To fetch this first register your Broadcast Receiver in your onCreate Method (uploaderClass or whatever). This will look like this:
registerReceiver(Updated, new IntentFilter("data_inserted"));
And this Method to handle the following actions!
private final BroadcastReceiver Updated= new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
doSomething(); // Data was inserted upload it
}
};
EDIT :
To fetch only new items from database, I sign new products. I have a colum "info" which can contain Strings like "upload", "update" or "delete". Then i fetch all Items from the database which contain these special strings and upload them. After that i set them to null or an empty String. Whatever you wish. Hope i explained it not to complicated :)
You can create your own database listener whenever something is updated to the database it will fetch the information and send to the network. I think will clear some idea for implementing this thing.
Now we can use Room to achieve this.
database.getInvalidationTracker().addObserver(new InvalidationTracker.Observer(tableToObserve) {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
}
});
database is an instance of RoomDatabase, which is the base class for all Room databases. All classes that are annotated with "#Database" (which is the way to use the Room Libary) must extend this class.
In the creator method of InvalidationTracker.Observer, you can pass in an array of String (or in the form of varargs) to indicate the tables you'd like to observe, if any update happen to those tables, the callback method onInvalidated is invoked.
Some links to refer to:
https://developer.android.com/reference/android/arch/persistence/room/InvalidationTracker.html#addObserver(android.arch.persistence.room.InvalidationTracker.Observer)
hope so this will help you
private Handler h;
// in create time
h = new Handler();
// call where you want
h.postDelayed(myRunnable2, 1000); // after 1000 millisecond this function call automatically
// this function
private Runnable myRunnable2 = new Runnable() {
public void run() {
// do some thing
h.postDelayed(myRunnable2, 1000);
}
};

Categories

Resources