currently I am using FirebaseRecyclerAdapter to represent data on a RecyclerView using the following Firebase query:
postsQuery = mDatabase.child("lists_new”).orderByKey().limitToFirst(10);
My RecyclerView has a header with 2 buttons: New List, Old List.
New list is loaded by default, and my question is, when the user taps the Old List button, what is the most efficient way to replace the new list with old list.
The Firebase query for the old list looks like this:
postsQuery = mDatabase.child("lists_old”).orderByKey().limitToFirst(10);
Note: both new list and the old list has the same data types, i.e. they share the same Java POJO class and the the same layout.
You will need a new adapter and attach that to the same RecyclerView.
So after constructing the new query, you create a new adapter and attach it to the view:
adapter = new FirebaseRecyclerAdapter<Chat, ChatHolder>(
Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, postsQuery) {
recyclerView.setAdapter(adapter);
I have a similar need and a similar solution except I am calling cleanup() on the adapter before new'ing up another one. I am thinking without calling cleanup() it will create a leak of adapters and/or listeners?
In onActivityCreated() in my Fragment I am calling a method in the Fragment that manages the recycler view. Call the method to initialize or refresh the list and pass in a leaf node name. If the adapter is not null then call cleanup(). Create a new database reference by concatenating a new leaf node with the parent reference, new-up a new adapter and set it.
I call cleanup() in onDestroy() as well, per usual.
It works fine so far though I've only tested using the emulator and a small data set.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
refreshList(newLeafNode);
}
#Override
public void onDestroy() {
super.onDestroy();
mRVAdapter.cleanup();
}
protected void refreshList(String newLeafNode) {
if (mRVAdapter != null) {
mRVAdapter.cleanup();
}
DatabaseReference newDbRef = mParentRef.child(newLeafNode);
mRVAdapter = new FirebaseRecyclerAdapter<String, MyViewHolder>(String.class, R.layout.single_row, MyViewHolder.class, newDbRef) {
#Override
protected void populateViewHolder(MyViewHolder viewHolder, String model, int position) {
viewHolder.setImage(model);
}
};
mMyRV.setAdapter(mRVAdapter);
}
Related
I want to save data from the API in the RecyclerView so that when rotating the screen is not reloaded
I think I can use onSaveInstanceState but still don't really understand how to use it
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final RecyclerView rvTVShow = view.findViewById(R.id.rv_shows);
rvTVShow.setHasFixedSize(true);
rvTVShow.setLayoutManager(new LinearLayoutManager(getActivity()));
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<MovieResponse> call = apiService.getTVShow(API_KEY);
call.enqueue(new Callback<MovieResponse>() {
#Override
public void onResponse(#NonNull Call<MovieResponse> call, #NonNull Response<MovieResponse> response) {
final List<Movies> movies = Objects.requireNonNull(response.body()).getResults();
TvShowAdapter tvShowAdapter = new TvShowAdapter(movies , R.layout.list_movies);
rvTVShow.setAdapter(tvShowAdapter);
....
}
I will explain how savedInstanceState works while refactoring your code.
First: Create a global Movie object and an Adapter for it
List<Movies> movies = new ArrayList();
TvShowAdapter tvShowAdapter = null;
Re-initialize adapter under activity onCreate
tvShowAdapter = new TvShowAdapter(movies , R.layout.list_movies);
rvTVShow.setAdapter(tvShowAdapter);
Create a new method to handle movie
data population
public void populateRV(List<Movies> movies)
{
this.movies = movies;
//notify adapter about the new record
tvShowAdapter.notifyDataSetChanged();
}
Insert data to Movies object under your Response callback
movies = Objects.requireNonNull(response.body()).getResults();
populateRV(movies);
Everytime the orientation of an activity changes Android resets the states of all views by redrawing them. This causes non persistent data to be lost. But before redrawing views it calls the method onSavedInstanceState
Hence we can prevent state loss by saving the state of our views using the already defined onSavedInstanceState method provided by android.
Add the following block inside the overridden onSavedInstanceState method of your activity
//this saves the data to a temporary storage
savedInstanceState.putParcelableArrayList("movie_data", movies);
//call super to commit your changes
super.onSaveInstanceState(savedInstanceState);
Next is to recover the data after orientation change is completed
Add the following block in your activity onCreate and make sure it comes after initializing your adapter
//...add the recyclerview adapter initialization block here before checking for saved data
//Check for saved data
if (savedInstanceState != null) {
// Retrieve the data you saved
movies = savedInstanceState.getParcelableArrayList("movie_data");
//Call method to reload adapter record
populateRV(movies);
} else {
//No data to retrieve
//Load your API values here.
}
I am using a singleton for fetching data from a web service and storing the resulting data object in an ArrayList. It looks like this:
public class DataHelper {
private static DataHelper instance = null;
private List<CustomClass> data = null;
protected DataHelper() {
data = new ArrayList<>();
}
public synchronized static DataHelper getInstance() {
if(instance == null) {
instance = new DataHelper();
}
return instance;
}
public void fetchData(){
BackendlessDataQuery query = new BackendlessDataQuery();
QueryOptions options = new QueryOptions();
options.setSortBy(Arrays.asList("street"));
query.setQueryOptions(options);
CustomClass.findAsync(query, new AsyncCallback<BackendlessCollection<CustomClass>>() {
#Override
public void handleResponse(BackendlessCollection<CustomClass> response) {
int size = response.getCurrentPage().size();
if (size > 0) {
addData(response.getData());
response.nextPage(this);
} else {
EventBus.getDefault().post(new FetchedDataEvent(data));
}
}
#Override
public void handleFault(BackendlessFault fault) {
EventBus.getDefault().post(new BackendlessFaultEvent(fault));
}
});
}
public List<CustomClass> getData(){
return this.data;
}
public void setData(List<CustomClass> data){
this.data = data;
}
public void addData(List<Poster> data){
this.data.addAll(data);
}
public List<CustomClass> getData(FilterEnum filter){
if(filter == FilterEnum.NOFILTER){
return getData();
}else{
// Filtering and returning filtered data
}
return getData();
}
}
The data is fetched correctly and the list actually contains data after it. Also, only one instance is created, as intended. However, whenever I call getData later, the length of this.data is 0. Because of this I also tried it with a subclass of Application holding the DataHelper object, resulting in the same problem.
Is there a good way of debugging this? Is there something like global watches in Android Studio?
Is there something wrong with my approach? Is there a better approach? I am mainly an iOS developer, so Android is pretty new to me. I am showing the data from the ArrayList in different views, thus I want to have it present in an the ArrayList as long as the application runs.
Thanks!
EDIT: Example use in a list view fragment (only relevant parts):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
filter = FilterEnum.NOFILTER;
data = DataHelper.getInstance().getData(filter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
customClassListAdapter = new customClassListAdapter(getActivity(), data);}
EDIT2: Added code where I fetch the data from Backendless, changed reference of DataHelper to reference of data in first EDIT
EDIT3: I usa a local EventBus for notifying the list view about the new data. This looks like this and works (initially the data gets populated, but after e.g. applying a filter, the ArrayList I get with getData is empty):
#Subscribe
public void onMessageEvent(FetchedDataEvent event) {
customClassListAdapter.notifyDataSetChanged();
}
Try instead of keeping reference to your DataHelper instance, keeping reference to your list of retrieved items. F.e. when you first fetch the list (and it's ok as you say), assign it to a class member. Or itarate through it and create your own array list of objects for future use.
Okay I finally found the problem. It was not about the object or memory management at all. Since I give the reference on getData to my ArrayAdapter, whenever I call clear (which I do when changing the filter) on the ArrayAdapter, it empties the reference. I basically had to create a copy of the result for the ArrayAdapter:
data = new ArrayList<>(DataHelper.getInstance().getData(filter));
I was not aware of the fact that this is a reference at all. So with this the data always stays in the helper entirely. I only did this because this:
customClassListAdapter.notifyDataSetChanged();
does hot help here, it does not call getData with the new filter again.
Thanks everyone for your contributions, you definitely helped me to debug this.
It is likely that getData does get called before the data is filled.
A simple way to debug this is to add (import android.util.Log) Log.i("MyApp.MyClass.MyMethod", "I am here now"); entries to strategic places in fetchData, addData and getData and then, from the logs displayed by adb logcat ensure the data is filled before getData gets called.
Using latest Parse library v1.5.1
Thanks to the update now I can do:
ParseQueryAdapter<ParseObject> mAdapter = new ParseQueryAdapter<ParseObject>(MainActivity.this, new ParseQueryAdapter.QueryFactory<ParseObject>() {
#Override
public ParseQuery<ParseObject> create() {
ParseQuery<ParseObject> query = new ParseQuery<ParseObject>(ParseObject.class);
query.fromLocalDatastore();
return query;
}
});
mListView.setAdapter(mAdapter);
Now I have some pinned objects and they appear correctly, but when I unpin them like so:
//Some ParseObject in the above adapter
object.unpinInBackground(new DeleteCallback() {
#Override
public void done(ParseException e) {
if(e == null) {
//I beleive this would be the correct approach.
mAdapter.notifyDataSetChanged();
}
}
});
Naturally I want that item to disappear from the corresponding ListView, but it doesn't. But say I go back to a different activity and revisit this activity, the ListView is displayed properly without the recently unpinned object.
Is this a bug? If not what am I doing wrong?
I have the same problem) I solve it with invoke method ParseQueryAdapter.loadObjects().
You can try mAdapter.remove(object) before calling notifyDataSetChanged();
unpinInBackground removes the object from the database. Probably the adapter has a local copy of the object.
Looks like there is no remove method in ParseQueryAdapter.
Here is an response from official source:
Since a ParseQueryAdapter is designed to always show the results of a
ParseQuery, you would need to use an API request to reload the query.
https://www.parse.com/questions/delete-a-object-using-parsequeryadapter
I am trying to refresh a ListFragment after Asynctask is finished. In my Asynctask doInBackground method I connect to a database and add elements to an ArrayList. In the onPostExecute method I want to access my ListFragment by something like this to call it's refreshData(method):
//REFRESH ARTISTFRAGMENT
#Override
public void onPostExecute(ArrayList<String> result) {
ArtistFragment artistFrag = new ArtistFragment();
artistFrag = (ArtistFragment) artistFrag.getSherlockActivity().getSupportFragmentManager().findFragmentById(R.id.frame_container);
if (artistFrag != null) {
artistFrag.refreshData(result);
}
}
But getSupportFragmentManager results in a NullPointerException!
The refreshData method in my Fragment looks like this:
public void refreshData(ArrayList<String> data) {
artists = new ArrayList<String>(data);
this.artistAdpater.notifyDataSetChanged();
}
I have found very similar approaches to do the exact thing I want but I can't find a solution to my problem. Basically it's done over here https://stackoverflow.com/a/16388650 - but it doesn't work for me like that.
Has anybody a solution for this or a workaround?
I am assuming that artists is the array list that feeds your listview.
artists = new ArrayList<String>(data);
What you are doing creates a new arraylist object reference to artists. But the list adapter has the older refrence. Thats why the list is not getting update.
Instead of passing the reference of the new arraylist, you can add it to the old one like so:
newArtistsArraylist = new ArrayList<String>(data);
artists .add(newArtistsArraylist );
Here is a nice solution to your problem.
For my Android app, I have subclassed ParseObject to create Food, Pref classes and CustomUser class which extends ParseUser and created a relation "lovers" between 'Food' and 'Users'.
Inside a CustomUser object, I have stored an Pref object using
createWithoutData method in key "pref".CustomUser and its
respective Pref object have one-to-one mapping
So when I want to display all lovers of a particular food in a listview using ParseQueryAdapter,
ParseQueryAdapter<ParseObject> adapter = new ParseQueryAdapter<ParseObject>(getActivity(),
new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery<ParseObject> create() {
ParseQuery<ParseObject> query = food.getRelation("lovers").getQuery();
query.selectKeys(Arrays.asList("pref");
return query;
}
});
adapter.setTextKey(Pref.COLUMN_PROFILE_NAME);
adapter.setImageKey(Pref.COLUMN_PROFILE_PIC_THUMB);
fyi, COLUMN_PROFILE_NAME = "profileName", COLUMN_PROFILE_PIC_THUMB = "profileThumb"
Now the problem is that "pref" is only a reference to the actual object. So when the listView tries to get text and image, it says "ParseObject has no data for this key. Call fetchIfNeeded() to get the data"
My objective is to pass a query to ParseQueryAdapter that will fetch all pref objects nested inside CustomUsers having 'lovers' relation with that particular food.
The parse docs say that 'include' method does not work on relations.
Please help, I have been struggling on this for long now.
To retrieve a relation, I don't believe you can use a query. In your case you would use
ParseRelation<ParseObject> relation = user.getRelation("lovers");
See this documentation
Hope this helps
Below, an example showing off the level of configuration available with this class:
// Instantiate a QueryFactory to define the ParseQuery to be used for fetching items in this
// Adapter.
ParseQueryAdapter.QueryFactory<ParseObject> factory =
new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Customer");
//query.whereEqualTo("activated", true);
//query.orderByDescending("moneySpent");
query.include("your key");
return query;
}
};
// Pass the factory into the ParseQueryAdapter's constructor.
ParseQueryAdapter<ParseObject> adapter = new ParseQueryAdapter<ParseObject>(this, factory);
adapter.setTextKey("name");
// Perhaps set a callback to be fired upon successful loading of a new set of ParseObjects.
adapter.addOnQueryLoadListener(new OnQueryLoadListener<ParseObject>() {
public void onLoading() {
// Trigger any "loading" UI
}
public void onLoaded(List<ParseObject> objects, ParseException e) {
// Execute any post-loading logic, hide "loading" UI
}
});
// Attach it to your ListView, as in the example above
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);