How to synchronously query data from room database? - java

This is my first time doing android dev. Sorry for my lack of knowledge in..well everything.
I am trying to query some data on the main thread of an activity using asynctask. The problem is, the data that I queried is needed immediately in some other query of data, so the asynchronous nature of the query means that every time I need to use the data, the thread has not queried it yet and gives a nullpointer exception.
Is there a way to synchronously query data from room database?
I tried the getValue() function from a LiveData object, but it always returns null as well. I am sure that the data is inserted properly within the database, I have checked multiple times looking into the database while debugging.
This is the code I used to query an entity of Day class:
//load current day
findSpecificDayAsyncTask asyncTask = (findSpecificDayAsyncTask) new findSpecificDayAsyncTask(mDayDao, new findSpecificDayAsyncTask.AsyncResponse() {
#Override
public void processFinish(Day output) {
day1 = output;
}
}).execute(date);
It works in due time, but I need the data immediately so that I can query
some other data:
mBPViewModel = ViewModelProviders.of(this).get(BulletPointViewModel.class);
//the day1 class is used here as a parameter
mBPViewModel.getSpecificDayBulletPoints(day1.day).observe(this, new Observer<List<BulletPoint>>() {
#Override
public void onChanged(#Nullable final List<BulletPoint> bulletPoints) {
// Update the cached copy of the words in the adapter.
mAdapter.setBulletPoints(bulletPoints);
}
});
So is there a way for me to synchronously query data so I don't get
a nullpointer exception?

Why not doing like this
//load current day
findSpecificDayAsyncTask asyncTask = (findSpecificDayAsyncTask) new
findSpecificDayAsyncTask(mDayDao, new findSpecificDayAsyncTask.AsyncResponse() {
#Override
public void processFinish(Day output) {
day1 = output;
mBPViewModel = ViewModelProviders.of(this).get(BulletPointViewModel.class);
//the day1 class is used here as a parameter
mBPViewModel.getSpecificDayBulletPoints(day1.day).observe(this, new Observer<List<BulletPoint>>() {
#Override
public void onChanged(#Nullable final List<BulletPoint> bulletPoints) {
// Update the cached copy of the words in the adapter.
mAdapter.setBulletPoints(bulletPoints);
}
});
}
}).execute(date);

Related

What is the clean way to check a SELECT query result's length in MVVM architecture?

I am creating an App that will use a SearchView to let user make queries to filter data. I am using RoomDB, and trying to follow Model-View-ViewModel architecture as recommended in Android Developers' Guidelines.
I have one entity and one DAO (for now my DB has only one table). I have a method in the DAO that looks like this:
#Query("SELECT * FROM table WHERE column1 = :search OR column2 = :search")
LiveData<List<TableRow>> filteredSearch(String search);
So, from an AsyncTask I can use the RoomDatabase's instance, right, and obtain the results of an user's query, like this, right?
// Let's assume search already contains user's input
String search;
// DatabaseClient is a singleton that holds MyRoomDatabase instance
// I am using it as Repository for now... bad call?
LiveData<List<TableRow>> user_query = DatabaseClient
.getInstance(getApplicationContext())
// my DatabaseClient has this method that
// I made to call DAO's query method
.getFilteredList(search);
So, I want to load one fragment or another in my Main Activity depending on this user query's length (if 0 results, fragmentA else fragmentB). This is business logic to some extent? I wonder... should I read the query's length from the Viemodel, or from the View (AKA Activity)? As you can see, I am still struggling with RoomDB and ViewModels at all.
My plan was making a method in the ViewModel that returns the LiveData<List<TableRow>> with the query results by using a code snippet similar to the one above, and then, from the MainActivity:
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
// Should I delegate AsyncTask to the Repository AKA
// DatabaseClient? Maybe... but please bear with me
class UserSearchTask extends AsyncTask<Void, Void, List<TableRows>> {
#Override
protected List<TableRows> doInBackground(Void... voids) {
TableRowsViewModel my_viewmodel = new TableRowsViewModel(getApplication());
LiveData<List<TableRows>> search_results;
search_results = my_viewmodel.getUserSearch(query);
// TODO I will care about type mismatches later
return search_results;
}
#Override
protected void onPostExecute(List<TableRows> found_elements) {
super.onPostExecute(found_elements);
// TODO So, I want to check user's search results from here
if ( found_elements.length > 0) {
showFragmentB();
} else {
showFragmentA();
}
}
}
}
I have been without coding for more than half year, and I am a little rusty. I forgot concepts that I knew before about ViewModels, LiveData and stuff. Am I breaking MVVM's architecture with my approach? What's the role of LiveData in this kind of logic attempt of mine? Can I read the SQL query result's length directly from some LiveData's method, or else, I should retrieve the list from it to do so?
I guess the actual question is: is my approach wrong? What would be the cleanest way to implement my fragment's logic depending on user search's length?
EDIT: I am not asking only about good practices (which are still welcome); I have barely dedicated 7 hours to this app yet and I couldn't still build a first alpha version to start testing it. My first priority in short-term is putting this thing together (and myself together I might add); in other words: first, I want to make it work even if it is not clean. Right now I am a simple-minded monkey which just thinks about this like if this was a normal PC app in which I don't have to struggle with App lifecycles, multithreading and all related stuff,in which I just retrieve the SQL query's result right away. I beg your pardon for my ignorance.
So, in order to add more context about what I am trying to do: If searxh results are zero, fragmentA would be a form for adding a new row to the table; fragmentB would show just the data of the first row, not listing yet (I will reach there eventually, but not yet).
Here is the way I ended up implementing the logic I had in mind when I made the question. But that doesn't mean this is the clean way to do it.
In order to get the size of the User query, I ended up using an Observer (as Teo said in his comment). I am not sure if using Observer and LiveData for a Database that is merely local in the phone's app (and therefore shall only be modified by the App's user himself) for obtaining query results each time the user hits "Search" button, I am not sure if using Oberser and LiveData for this is overkill or not... and the aberration of using DatabaseClient (the RoomDatabase's singleton) as a Repository? Not anymore... I have created a dedicated Repository Class to handle the DatabaseClient and the DAOs.
That said, the relevant part of my Repository class:
public class Repository {
private final TableRowDao tablerow_dao;
public Repository(Application application) {
AppDatabase app_db = DatabaseClient.getInstance(application).getAppDatabase();
tablerow_dao = app_db.tableRowDao();
}
public LiveData<List<TableRow>> getFilteredList(String search) {
return tablerow_dao.filteredSearch(search);
}
// [...]
...here, the ViewModel:
public class TableRowsViewModel extends AndroidViewModel {
private Repository repository;
public TableRowsViewModel(#NonNull Application application) {
super(application);
repository = new Repository(application);
}
public LiveData<List<TableRow>> getUserSearch(String search) {
return repository.getFilteredList(search);
}
No AsyncTasks were used for this purpose.
In MainActivity, within OnCreate method:
search_view.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
my_viewmodel = new TableRowsViewModel(getApplication());
search_results = my_viewmodel.getUserSearch(query);
observeSearchResults(search_results);
return true;
}
// [...]
});
ObserveSearchResults is a private method that I declared in MainActivity as well:
// Having a observer is good and stuff, but am I overdoing it?
private void observeSearchResults(LiveData<List<TableRow>> search_results) {
search_results.observe(this, new Observer<List<TableRow>>() {
#Override
public void onChanged(List<TableRow> rows) {
if ( rows.size() > 0 ) {
// TODO I don't list the results yet, I show the first one right away
profileFragment = ProfileFragment.newInstance(rows.get(0));
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, profileFragment);
transaction.addToBackStack(null);
transaction.commit();
} else {
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, insertFragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
});
}
This worked for me, but that doesn't mean that I am doing this in a clean way at all.

reading Firestore documents with AsyncTask

I have a database in Cloud Firestore where each document has a particular key "last update" with value, a String, representing a date in the form YYYY-MM-DD. Each time a document is updated, the value of "last update" is set as the date of the update.
Now, I want my activity to have a method that checks documents for their last update. As the documents contain fairly big lists of objects, this update check takes a few seconds. So I decided to defer it to an AsyncTask. The doInBackground method of the AsyncTask should create a DocumentReference, noteRef, for the document and read its "last update" with noteRef.get(), equipped with
onSuccess- and onFailure listeners, into a String, which is then returned by the method.
In order to test this, I have created a toy activity, MyTestActivity, which calls the above AsyncTask with String arguments "myCollection" and "myDocument" and
displays the value of this document's last update in a TextView. Now, instead of showing the actual value, "2019-10-03", the TextView displays the value, "1970-01-01", which is the one
used in doInBackground to initialize the String variable which is returned. It's as if doInBackground doesn't bother to wait until the document has been read. The code is as follows.
public class MyTestActivity extends AppCompatActivity{
private Button button;
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_test);
button = findViewById(R.id.update_button);
textView = findViewById(R.id.update_text_view);
}
public void buttonClicked(View view) throws ExecutionException, InterruptedException {
UpdateTask task = new UpdateTask(this, "myCollection", "myDocument");
String date = task.execute().get();
textView.setText("Last update on "+date);
}
private static class UpdateTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<MyTestActivity> activityWeakReference;
String collection;
String document;
String lastUpdate;
UpdateTask(MyTestActivity activity, String collection, String document) {
activityWeakReference = new WeakReference<MyTestActivity>(activity);
this.collection = collection;
this.document = document;
lastUpdate = new String();
}
#Override
protected void onPreExecute() {
super.onPreExecute();
MyTestActivity activity = activityWeakReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
}
#Override
protected String doInBackground(Integer... params) {
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference noteRef = db.collection(collection).document(document);
lastUpdate = "1970-01-01";
noteRef.get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists()) {
Map<String, Object> map = documentSnapshot.getData();
lastUpdate = (String)map.get("last update");
activityWeakReference.get().textView.setText(lastUpdate);
} else {
lastUpdate = "Document doesn't exist";
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
lastUpdate = "Listener failed.";
}
});
return lastUpdate;
}
}
}
Can anyone explain what is going on here?
I have a database in Firebase Firestore where each document has a particular key "last update" with value, a String, representing a date in the form YYYY-MM-DD.
That's uncommon to store the date as a String, instead you should store it as a:
FieldValue.serverTimestamp()
As explained in my answer from the following post:
ServerTimestamp is always null on Firebase Firestore
So I decided to defer it to an AsyncTask.
The Cloud Firestore database client, already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Adding it in an AsyncTask does not give any any benefits at all.
Now, instead of showing the actual value, "2019-10-03", the TextView displays the value, "1970-01-01", which is the one used in doInBackground
This is happening because you are trying to return a message synchronously from a method that is asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to use value of lastUpdate only inside the onSuccess() method, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
Ouch! That's bad design. Firestore calls are asynchronous, so you don't need to put them into asyncTask background method. Also, using an synctask wont execute your code faster. What you need is a "loading message" until your OnSuccessListener fires back.

What is the difference between insert () and createObject()?

I have a setChatsList() method and it has a huge code:
public void setChatsList(final ChatsModel chatsModel) {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
ChatsModel realmChats = realm.createObject(ChatsModel.class);
Response realmResponse = realm.createObject(Response.class);
Item realmItem = realm.createObject(Item.class);
Message realmMessage = realm.createObject(Message.class);
Attachment realmAttachment = realm.createObject(Attachment.class);
Video realmVideo = realm.createObject(Video.class);
Response response = chatsModel.getResponse();
RealmList<Item> items = new RealmList<>();
Integer itemCount = response.getCount();
RealmList<Item> itemList = response.getItems();
if (itemList != null) {
for (Item item : itemList) {
Message message = item.getMessage();
realmMessage.setId(message.getId());
realmMessage.setDate(message.getDate());
realmMessage.setOut(message.getOut());
realmMessage.setUserId(message.getUserId());
realmMessage.setReadState(message.getReadState());
realmMessage.setTitle(message.getTitle());
realmMessage.setBody(message.getBody());
realmMessage.setRandomId(message.getRandomId());
RealmList<Attachment> attachments = message.getAttachments();
RealmList<Attachment> attachmentList = new RealmList<>();
if (attachments != null) {
for (Attachment attachment : attachments) {
String type = attachment.getType();
Video video = attachment.getVideo();
realmVideo.setAccessKey(video.getAccessKey());
realmVideo.setCanAdd(video.getCanAdd());
realmVideo.setCanEdit(video.getCanEdit());
realmVideo.setComments(video.getComments());
realmVideo.setDate(video.getDate());
realmVideo.setDescription(video.getDescription());
realmVideo.setDuration(video.getDuration());
realmVideo.setId(video.getId());
realmVideo.setOwnerId(video.getOwnerId());
realmVideo.setPhoto130(video.getPhoto130());
realmVideo.setPhoto320(video.getPhoto320());
realmVideo.setPhoto640(video.getPhoto640());
realmVideo.setPlatform(video.getPlatform());
realmVideo.setTitle(video.getTitle());
realmVideo.setViews(video.getViews());
realmAttachment.setType(type);
realmAttachment.setVideo(realmVideo);
attachmentList.add(realmAttachment);
}
realmMessage.setAttachments(attachmentList);
}
realmResponse.getItems().add(item);
}
}
realmResponse.setCount(itemCount);
realmChats.setResponse(realmResponse);
}
});
}
Works correctly!
Just read in the official documentation about the method insert(), also for storage in the database. I rewrote the setChatsList() method thus:
public void setChatsList(final ChatsModel chatsModel) {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
realm.insert(chatsModel);
}
});
}
And to my surprise, it worked fine too and the code is less!
But I'm sure that not everything is so smooth, I think that somewhere there is a catch.
Question: What is the difference between insert() and createObject()?
insert()
saves an unmanaged object into the Realm (managed object is no-op), without creating a managed proxy object as return value.
createObject()
creates a managed object in Realm, and returns a proxy to this managed object.
copyToRealm()
saves an unmanaged object into the Realm, with returning a proxy to the created managed object.
The key difference between insert() and copyToRealm() is whether a proxy is returned or not; which means that inserting many items is much more efficient by re-using a single unmanaged object and calling insert() on it with the right parameters.
However, you generally need createObject() if you want to set up relations between objects.
P.S. insert/copyToRealm/createObject(clazz) have a counterpart insertOrUpdate, copyToRealmOrUpdate and createObject(clazz, primaryKeyValue) for objects with primary keys.
Assuming you have primary key as integer
0 is the default value for int fields, so if you have a RealmObject with 0 as the value then, it means that realm.createObject(YourRealmClass.class) will fail with the error mentioned below.
RealmPrimaryKeyConstraintException: Value already exists:
as it will create an object with the default value.
What is the better way to create RealmObjects?
copyToRealmOrUpdate() or insert().
I will recommend you that use copyToRealmOrUpdate() because. it is better to use as it first checks if record exists or not . If record exists then it will update if record does not exits it will insert new record .

Cannot seem to write custom object to Firebase database?

I'm working on a project using Firebase, which I've never used before, and I know almost nothing about Firebase itself, as the rest of my team has been responsible for most of the dealings with it. I'm writing a parser for some Excel data where I need to extract some specific data and then upload it to Firebase. The parsing is done, but I'm having trouble writing it to Firebase.
We have a sub-database called "families" in our root database that I need to write this data to. I have a class called RegistrationSheet which contains all the data in this particular spreadsheet broken up into objects to represent the structure of the JSON. I'm aware that you can write custom objects to the Firebase database and it will be converted to a JSON format that represents that data. I found a page detailing the different data types that can be written to the database and converted to JSON, and among them were Map and List. So here are my classes that represent the "families" database.
RegistrationSheet.java:
public class RegistrationSheet {
public List<Object> families;
public void addFamily(Family f) { families.add(f); }
public RegistrationSheet() {
families = new ArrayList<>();
}
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(families);
}
public File outputJSON() {
return null;
}
}
Family.java:
public class Family {
public Map<String, Object> fields;
public void addField(String str, Object obj) { fields.put(str, obj); }
public Family() {
fields = new HashMap<>();
}
}
Child.java:
public class Child {
public Map<String, Object> fields;
public void addField(String str, Object obj) { fields.put(str, obj); }
public Child() {
fields = new HashMap<>();
}
}
The families list contains Family objects, and one of the "fields" that can be added to the Map in the Family objects is a List of Child objects. I figured that because these are all objects that are valid to write to Firebase, that simply writing the "families" list in the RegistrationSheet object would be enough:
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(families);
}
Is there something wrong with the structure of any of my classes or how I'm writing the data to Firebase? Because after executing this, the "families" sub-database disappears from Firebase and I have to restore it from my backup. It seems I have the correct DatabaseReference since removeValue() seems to be removing it, but why isn't it then writing the data from the families list?
I would appreciate any help that someone could provide.
Try the following code. It gives you the reason as to why it is not writing the value.
public void writeToFirebase() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("families");
ref.removeValue(); // Delete the data currently in the database so we can rewrite it.
ref.setValue(object, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference reference) {
if (databaseError != null) {
Log.e(TAG, "Failed to write message", databaseError.toException());
}
}
});
}
Then you can debug your code based on the Exception generated

java can't find local variable in reterofit 2 api call

I have done an API call to retrieve a list of messages, then i want to check if each message has a flag of 2 and then if it does do another API call to receive information on whether the message has been "paid" and if it has alter the object message to paid = true;
Here is my failed attempt.
for (int i = 0; i < chatHistory.getData().size(); i++) {
final ChatMessage chatMessage = chatHistory.getData().get(i).getBody();
if (chatMessage.flag.equals("2")) {
RestClient.getInstance().getApiService().getPaymentRequest(chatMessage.payment_request_id, new Callback<SinglePaymentRequest>() {
#Override
public void success(SinglePaymentRequest singlePaymentRequest, Response response) {
Payment payment = singlePaymentRequest.getPayment();
if(payment.getStatus().equals("paid")) {
chatMessage.isPaid=true;
}
}
#Override
public void failure(RetrofitError error) {
System.out.println("fail");
}
});
}
chatMessages.add(chatMessage);
Log.e("chat history", chatMessage.from);
}
addData(chatMessages);
The problem I am facing is that the api call cannot find local variable chatmessage, any ideas as to why this is?
Thanks
Notice the bit of code new Callback<SinglePaymentRequest>() that creates your new Callback object? It does not have access to the variables outside it, for good reason too.
What you should be doing, is calling a setter method that's part of your container class (the one that is the parent of the Callback) that will, in turn manipulate the values that you want to change.

Categories