I'm in need of advice on how to get data from the database, or whatever approach is best, from a RecyclerView adapter onBindViewHolder() method. Basically I have a list of Transactions that the ViewHolder cycles through, which contains an ID for a related entity called Payee, which I can obtain by accessing the Transaction's getPayeeByID method. (That method already exists to pull the info in the repository and Dao and works fine.) The problem is, how do I access that method from this screen? I need to know how get to it from here so I can create a new Payee based on the pulled PayeeId, in order to set the holder.payee.setText field with the name of the associated Payee. I have no idea how to do that from here.
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
final Transaction transaction = tTransactions.get(holder.getAdapterPosition());
holder.payee.setText( ????????? ) ;
holder.date.setText(date);
holder.transAmount.setText(amount);
I am happy to add more code if needed.
I realized the solution to this. I just created an instance of the repository and accessed the DB through that. See below:
AppRepository repository = new AppRepository(tContext);
final Payee payee = repository.getPayeeById(transaction.getPayeeId());
holder.payee.setText(payee.getName());
Related
I am refactoring old application to mvvm pattern, using room, repository, viewmodel, ets.
I have an old code, which contains Content provider helper class with many functions like this:
public static int deleteOldLogs(int NumDays) {
//get NumDays before today, then constract a content provider delete command and run
...
}
or
public static Cursor getTodayLogs() {
//get a day from today, then constract a content provider query and run
...
}
or
public static boolean isActionValid(Context context, int id_order, int id_actionh) {
//get all products from database table, then check if all products match some criteria, then return boolean result
...
}
My question is in what layer to place this logic? Is it a repository or viewmodel should contain? All the examples that I see in the net is very simple and not suit my goals.
View model helps us to provide data between repository and UI . For direct interaction with room database , we use repository . Once we get the data from repo we can perform all sort of computation (i.e sorting , filtering etc ) in ViewModel .
In order to display data from the database, we use an observer who will observe the data changes, LiveData in the ViewModel.
We use ViewModelProvider which is going to create a ViewModel for us. We need to connect our ViewModel with the ViewModelProvider, and then in the onChanged method, we always get our updated data which we can display on the screen.
For eg . We want to get some record from our database .
For this we need to create a repository that will interact directly with database or carrying the logic to fetch data from database .
public class ABCRepository {
#Inject
DrugsDao mABCDao;
#Inject
public ABCRepository(){
}
public LiveData<List<NameModel>> getNameByLetter(String letter) {
return mABCDao.getName(letter);
}
}
Now in View Model
public class SearchViewModel extends ViewModel {
#Inject
ABCRepository mABCRepository;
LiveData<List<GlobalSearchModel>> getNameList(String queryText) {
MutableLiveData<List<GlobalSearchModel>> mGlobalSearchResults = new
MutableLiveData<>();
List<NameModel> synonymsNameList=mABCRepository.getNameByLetter(queryText);
new Thread(() -> {
List<GlobalSearchModel> globalSearchModelList =
mABCRepository.getNameByLetter(queryText)
// this is where you can perform any action on list . either sorting or.
filtering and then return the new list to your UI.
mGlobalSearchResults.postValue(globalSearchModelList);
}).start();
return globalSearchModelList;
}
}
In your fragment or activity you can observe this data ,
getViewModel().getAllCountries().observe(this, this::addSearchResultsInRecycler);
Hope this is helpful . Though not explained good but you can have reference from
https://medium.com/#skydoves/android-mvvm-architecture-components-using-the-movie-database-api-8fbab128d7
I have an ObjectBoxLiveData object with a query that is set at runtime:
private ObjectBoxLiveData<MyObject> myObjectLiveData;
public ObjectBoxLiveData<MyObject> getMyObjectLiveData(Box<MyObject> myObjectBox, String filterTerm)
{
if (myObjectLiveData == null)
myObjectLiveData = new ObjectBoxLiveData<>(myObjectBox.query().equal(MyObject_.filterProperty, filterTerm).build());
return myObjectLiveData;
}
But I also need to be able to change the filterTerm at runtime. My thinking is that I can make a private String currentFilterTerm; object in MyViewModel to see if I need to update the filter term in the LiveData object, but is there a correct way to update the filter term? I worry that setting myObjectLiveData = new ObjectBoxLiveData<> again will leave a memory leak for the previously defined myObjectLiveData or anything tied to it, but I don't see any graceful way to dispose of it or update the query once defined. Is there a way to redefine my query once defined?
I am trying to query my Realm DB such that the output will give an unmanaged object and for that, I changed my RealmList type of object to List.
Now the thing is in addchangeListener I am getting my output object(stories) value as managed. But the type of stories is List. So why my stories object is becoming managed where it should act as an unmanaged object.
List<story> stories = realm.where(story.class).findAllAsync();
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
#Override
public void onChange(RealmResults<story> storydata) {
if (storydata.size() != 0) {
madapter = new StoriesAdapter(stories, getBaseContext(), MR);
mrecyclerview.setNestedScrollingEnabled(false);
mrecyclerview.setLayoutManager(new LinearLayoutManager(getBaseContext()));
mrecyclerview.setAdapter(madapter);
}
}
});
StoriesAdapter
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
StoriesAdapter(List<story> storyList) {
this.storyList = storyList;
}
}
I am saying my List is managed because when i am trying to write below code I am getting Cannot modify managed objects outside of a write transaction.
madapter.storyList.get(3).setTitle("Wonderland"); // where storyList is List which i am pointing to `stories`.
List<story> stories = realm.where(story.class).findAllAsync();
Because specifying the type List<story> just means you'll see the returned list as a List<story>, but technically it's still a RealmResults<story>.
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
This line underneath shouldn't even compile.
Stories should be stored in a field.
private RealmResults<story> stories;
public void ...() {
stories = ...
stories.addChangeListener(...
Anyways, so you are working with RealmResults, which means that in
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
This storyList you provided is a RealmResults<story>, so calling storyList.get(...) will return managed RealmObjects.
Managed RealmObjects are "temporarily immutable", meaning they can only be modified in a transaction. It is also generally not recommended to run write transactions on the UI thread.
The simplest way would be to use realm-android-adapters.
class StoriesAdapter extends RealmRecyclerViewAdapter<story, RecyclerView.ViewHolder> {
StoriesAdapter(OrderedRealmCollection<story> stories) {
super(stories, true, true);
}
}
And when you want to modify an object, you do
story item = getData().get(3);
final String id = item.getId();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
story changedItem = realm.where(story.class).equalTo("id", id).findFirst();
changedItem.setTitle("Wonderland");
}
});
And then Realm will handle automatically updating the RealmResults, the story object, and the RecyclerView.
EDIT: If you intend to use unmanaged objects, then you could use realm.copyFromRealm(results), except that does the read on the UI thread.
You could create a background looper thread and obtain the results from there, but managing that could be tricky. Luckily for you, there's a library I made called Monarchy which lets you do exactly that.
See the relevant sample code for how you'd use it.
The stories is implicitly Managed, the reason is that RealmResults extends the list interface abstractly. Thats why the casting is possible, underneath the same mechanisms for a RealmResults still takes precedence. Also, you should only pass RealmResults instance to an Adapter directly, if you register a RealmChangeListener on it, which will call adapter.notifyDataSetChanged(). Otherwise, writes will update the RealmResults content, and your adapter will be desynchronized.
Realm is not like SQLite or Core Data. If you’re using Realm, take advantage of live objects. Don’t implement any refreshing logic or requerying. Always allow the current class to own its own instance of a realm query.
This fact is true,Realm objects and any child objects are NOT thread-safe. They’re confined to a single thread to ensure that atomic rights are maintained. There is an internal list where every single thread has its own unique Realm instance. If you want to pass objects between a thread–for example, if you create a dog object on the main thread, pass it to the background thread, and then try and access a property–it will trigger an exception straight away.
Also you are using asynchronous query, which puts it on a worker thread.
I'm using FirebaseListAdapter and trying to get a list of open chat rooms from my Firebase Database, the structure is pretty simple:
This is what I get when I query:
{-KY99IS2mLzayD4HDyd6={location={...}, posts={....} , title="chatname"}
And what I'm trying to achieve is to get the data without the ID part, so it would fit my object builder, something like that:
{location={...}, posts={....} , title="chatname"}
I know that ChildEventListener does that, but I don't have an option to use it with the FirebaseListAdapter, so I hope there's another way to do that.
My code is:
FirebaseListAdapter<ChatRoom> firebaseListAdapter = new FirebaseListAdapter<ChatRoom>(this,ChatRoom.class,R.layout.chatslist_row,myRef3) {
#Override
protected void populateView(View v, ChatRoom chatRoom, int position) {
System.out.println(chatRoom.title);
}
};
But it doesn't work because of the key part, it can't use my ChatRoom class object builder.
Thanks!
You can first get the id (KY99IS2mLzayD4HDyd6) from the child event listener.
Then made a new database reference with the id (KY99IS2mLzayD4HDyd6) and call for child event listener again.
mDatabase = databaseRef.getReference("chats/KY99IS2mLzayD4HDyd6");
if you call mDatabase now you will get {location={...}, posts={....} , title="chatname"}.
I have solved my issue like that. But not for FirebaseListAdapter. Make your own listview and adapter this might be more flexible.
I have a RecyclerView with a FirebaseRecyclerAdapter. I want to populate the RecyclerView with a list of names when the user starts typing into the SearchView.
public class SchoolsAdapter extends FirebaseRecyclerAdapter<School, SchoolsAdapter.SchoolViewHolder> {
public SchoolsAdapter(Query ref) {
super(School.class, R.layout.item_school, SchoolViewHolder.class, ref);
}
#Override
public void populateViewHolder(SchoolViewHolder schoolViewHolder, School school, int position) {
schoolViewHolder.name.setText(school.getName());
schoolViewHolder.address.setText(school.getAddress());
}
static class SchoolViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public TextView address;
public SchoolViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.school_item_tview_name);
address = (TextView) itemView.findViewById(R.id.school_item_tview_address);
}
}
}
I'm guessing I need to add a QueryTextListener to the searchview that would update the Query in the adapter. Would this break the FirebaseRecyclerAdapter?
Or should I
#Override
public boolean onQueryTextChange(String newText) {
mRecyclerView.setAdapter(new SchoolAdapter(ref.orderByChild("name").startAt(userQuery).endAt(userQuery+"~"))
return false;
}
whenever the user types something?
Also the docs talk about ordering and sorting firebase queries but don't explicitly say the best way to do string pattern matching. What's the best way to do string matching so that the recycler view shows all results which have the search query as a substring of the database record, and possibly those that are 1 edit distance away as well.
Also a way to ignorecase on queries?
I just finished doing something near to what you're looking for, I'm not sure it's the most elegant solution, but I'll throw out some ideas and if you think my idea will help I can definitely provide some examples.
Firstly, when I extended the base FirebaseAdapter I added a new filter named mFullList, since mItems of the FirebaseAdapter will be used for the display list, I don't want to keep going back to the network when I didn't have to. I then override all the methods in my child class to update mFullList with the values from the Firebase callbacks, sort them, filter them then call super.X() with the new list.
Quickly:
public reset(List)
mFullList = List
Collections.sort(mFullList, Comparator)
getFilter().filter(filterString)
The filterString is a field within the Adapter and is updated during the call to getFilter().filter(). During the perform filter I then loop through the mFullList and do a compare of:
mFullList.get(pos).getName().toLowerCase().contains(filterString.toLowerCase);
Once fitlering is done you have a new list that is passed to Filter.publishResults in the FilterResults object. publishResults calls a method in the class that performs the update and notify.
filterCompleted(List)
getItems().clear
getItems().addAll
notify
Essentially, I didn't want the FirebaseAdapater to stop getting the full list of items, I just wanted the users request to filter that full list and handle their request appropriately. Also, I didn't see a point to the added network requests based the user typing an extra character.
Using this method you can just use:
adapter.getFilter().filter("something")
to filter the list based on your updated field, and
adapter.getFilter().filter("")
to reset the full list (as long as your performFilter() handled it correctly. This way new updates from FireBase will be filtered based on the users selection, as well as when a user types in new values, won't require making new Firebase network requests.