ArrayList Length gets 0 in Singleton - android

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.

Related

Android adapter dataset instantiation practices with Realm

I was wondering does it matter where you instantiate adapters dataset with Realm? I like to fetch all the data that any adapter needs in the adapters constructor and thus instantiate the dataset there, but almost all examples I've seen fetch the data beforehand in the activity creating the adapter and then pass it to the adapter as a parameter.
With SQLite this seems even more arbitrary, but since I'm using Realm I need to open a realm connection every time I want to access the database and to keep the data available I need to keep the connection open. Keeping this connection open in the activity seems unnecessary since you might need to make queries in the adapter thus having to open a connection to realm within the adapter anyways.
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
since I'm using Realm I need to open a realm connection every time I want to access the database
Wrong, you just need 1 open instance for that given thread in order to access the database.
Keeping this connection open in the activity "seems unnecessary" since you might need to make queries in the adapter
In which case you can have the activity-level Realm instance as a "scoped dependency", that you can share through the Context via getSystemService() if that's what you like to do.
public class MyActivity extends Activity {
Realm realm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
//...
}
#Override
public void onDestroy() {
if(realm != null) {
realm.close();
}
super.onDestroy();
}
...
#Override
public Object getSystemService(String key) {
if("REALM".equals(key)) {
return realm;
}
return super.getSystemService(key);
}
}
public class MyAdapter extends RecyclerView.Adapter<MyModelViewHolder> {
private final Context context;
Realm realm;
RealmResults<MyModel> results;
private final RealmChangeListener listener = new RealmChangeListener() {
#Override
public void onChange(Object element) {
notifyDataSetChanged();
}
}
public MyAdapter(Context context) {
this.context = context;
//noinspection ResourceType
realm = (Realm)context.getSystemService("REALM");
results = realm.where(MyModel.class).findAll();
results.addChangeListener(listener);
}
...
}
thus having to open a connection to realm within the adapter anyways.
wrong
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
It's because your Adapter, which is just supposed to describe how to show the elements of a dataset, become a God that also determines the data that it must show.
Although to be fair, it's actually harder to externally manage the data-set; something must hold a strong reference to the result set anyways. So when I don't really bother with unit-testability, I do obtain the results inside the Adapter itself. It does work.

Best way to change data on FirebaseRecyclerAdapter

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);
}

Query realm data contained on other object

This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}

How to give RecyclerView Adapter new data from AsyncTaskLoader

My AsyncTaskLoader is loading data from a remote server. When new data arrives, naturally a call is made to onLoadFinished. At this point I don't know how to give the new data to the RecyclerView.Adapter. Calling notifyDataSetChanged does not give it the new data: it simply tells it there is new data. So any advice on how I might do this? Right now the best I can think of is to create my own setData method in my implementation of RecyclerView.Adapter as
public void setData(List<MyObject> data){
if(null != data && !data.isEmpty()){
synchronized(mItems){
mItems.clear();
mItems.addAll(data);
}
notifyDataSetChanged();
}
}
Is my idea the best there is? Or is there a more sound way of doing this?
Expose a public method in your adapter to update data.
For example, you could put it like this
public void updateItems(ArrayList<MyObject> myObjects) {
this.data = myObjects;
notifyDataSetChanged();
}
You have two options,
1. Re- instantiate your adapter with your new data and reset the adapter.
2. The way you do it.
I can not think of any other methods.
Ok, I had the same issue and here is the solution what I did.
1st I passed list object from onLoadFinished and in the RecyclerViewAdapter I have created method name setCardInfoList() and there I passed this object to global List object which I define in adapter class.
In onLoadFinished method..
#Override
public void onLoadFinished(android.content.Loader<List<Earthquake>> loader, List<Earthquake> earthquakes) {
if (earthquakes != null && !earthquakes.isEmpty()) {
adapter.setCardInfoList(earthquakes);
adapter.notifyDataSetChanged();
}else {
emptyView.setText("No Earthquake Found...");
}
}
Inside Adapter class
public void setCardInfoList(List<Earthquake> earthquakes){
this.earthquakeList = earthquakes;
}

ParseQueryAdapter; using class in Android

I feel like a broken record.
After many attempts, I have failed at getting a listview through Parse data to display a specific set of information.
Here is my model...this is all data from users:
#ParseClassName("Midwifefirm")
public class Midwifefirm extends ParseObject {
public Midwifefirm() {
// A default constructor is required.
}
//practice name
public String getPracticeName() {
return getString("practicename");
}
public void setPracticeName(String practicename) {
put("practicename", practicename);
}
//education
public String getEducation() {
return getString("education");
}
public void setEducation(String education) {
put("education", education);
}
//years in practice
public String getYearsinPractice() {
return getString("yearsinpractice");
}
public void setYearsinPractice(String yearsinpractice) {
put("yearsinpractice", yearsinpractice);
}
//practice philosophy
public String getPracticePhilosophy() {
return getString("practicephilosophy");
}
public void setPracticePhilosophy(String practicephilosophy) {
put("practicephilosophy", practicephilosophy);
}
I have this adapter; I am wondering what to place in the query section, as I just want to pull the data into the ListView that is defined in the data model:
public class CustomMidwifeAdapter extends ParseQueryAdapter<Midwifefirm> {
public CustomMidwifeAdapter(Context context) {
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery("Midwives");
return query;
}
});
}
#Override
public View getItemView(Midwifefirm midwifefirm, View view, ViewGroup parent) {
if (view == null) {
view = View.inflate(getContext(), R.layout.activity_midwife_result_list, null);
}
//use midwifefirm as item view/list
super.getItemView(midwifefirm, view, parent);
// find in layout the practice name
TextView titleTextView = (TextView) view.findViewById(R.id.practicename);
//in the midwifefirm data model, call getPracticename
titleTextView.setText(midwifefirm.getString("practicename"));
// Add education view
TextView EducationView = (TextView) view.findViewById(R.id.education);
EducationView.setText(midwifefirm.getString("education"));
// Add yearsexperience view
TextView ExperienceView = (TextView) view.findViewById(R.id.yearsinpractice);
ExperienceView.setText(midwifefirm.getString("yearsinpractice"));
//Add practice philosophy view
TextView PracticePhilosophyView = (TextView) view.findViewById(R.id.practicephilosophy);
PracticePhilosophyView.setText(midwifefirm.getString("practicephilosophy"));
return view;
}
}
And here is the Main Activity:
public class MidwifeResultList extends ListActivity {
private ParseQueryAdapter<ParseObject> mainAdapter;
private CustomMidwifeAdapter midwifeListAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialize main ParseQueryAdapter
mainAdapter = new ParseQueryAdapter<ParseObject>(this, Midwifefirm.class);
//which keys in Midwife object
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
// Initialize the subclass of ParseQueryAdapter
midwifeListAdapter = new CustomMidwifeAdapter(this);
// Default view is all meals
setListAdapter(mainAdapter);
}
Every time I run this, I get no results.
Thanks in advance for any help
Michael
I can tell you why I think it fails now and I can tell you why I'm very sure it will fail after you sort out the current issue.
It seems that you're trying to use different classes
#ParseClassName("Midwifefirm")
public class Midwifefirm extends ParseObject {
and
ParseQuery query = new ParseQuery("Midwives");
You need to be consistent and use the same name. Either use Midwives or Midwifefirm for both. Let's assume you picked the latter. You're also saying
all that is stored in the user table...wasn't sure if I needed to create new tables.
The query above wants to get all entries of type Midwives. If there's no such type, it'll return nothing. So you have two options:
In you Parse dashboard, reate a class Midwifefirm (don't forget to update the String inside #ParseClassName above) and store your Midwifefirm data in there. You don't need to change your query for this.
Add a column to your ParseUser class, such as type, that you can set to Midwifefirm or whatever if that user is a Midwifefirm or whatever. Then in your query you need to add:
ParseQuery query = new ParseQuery("Midwives");
query.whereEquals("type", "Midwifefirm");
I greatly prefer the former.
Anyway, once your done that, the issue is that you're not using a custom view for this. You're relying on the one provided by Android by default for ListActivity. I am fairly sure it doesn't have any of the fields you're after, so you should create a custom view for this, then at the top of onCreate in your Activity make sure you use it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_custom_view);
By the way, the following are redundant if you populate then in getItemView anyway:
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
One final advice: if you're still having issues, set breakpoints and do some investigations first. What you need to check is:
Whether you get anything at all from Parse when you do your query. Your adapter has an useful addOnQueryLoadListener that you may use to check whether anything's been retrieved at all.
If stuff is retrieved successfully, you need to check whether the list view is populated correctly. Again, use breakpoints, this time in getItemView maybe.
I'm going to do a wild guess here using the lovely brainwrecking API help of Parse.com about ParseQueryAdapters
Before continuing, may I mind you that my experience with ParseQueryAdapters is a minimum but I think I have a basic knowledge about them + I have some experience with Parse on its own. ANYHOW,
As an example they use both these
final ParseQueryAdapter adapter = new ParseQueryAdapter(this, "Midwives");
adapter.setTextKey("name");
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
and
// 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("Midwives");
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);
To start of, the reason why I think nothing is loading inside your list has to do with a little mixup between the initilization of your ParseQueryAdapter and your custom adapter.
You configure the basic adapter, and also initialize a custom adapter but you don't do anything with the custom adapter, tho the custom adapter seems to contain the logics to load your data model.
I think what you're looking for is something like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initialize main ParseQueryAdapter
mainAdapter = new CustomMidwifeAdapter<ParseObject>(this);
//which keys in Midwife object
mainAdapter.setTextKey("practicename");
mainAdapter.setTextKey("education");
mainAdapter.setTextKey("yearsinpractice");
mainAdapter.setTextKey("practicephilosophy");
// Default view is all meals
setListAdapter(mainAdapter);
}
All you need to pass is the context (aka "this"), and the constructor of your custom class will handle the factory internal
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery("Midwives");
return query;
}
});
Tho to be honest since you do:
new ParseQueryAdapter<ParseObject>(this, Midwifefirm.class);
I wonder if you'd need to change your "QueryFactory" to
super(context, new ParseQueryAdapter.QueryFactory<Midwifefirm>() {
public ParseQuery<Midwifefirm> create() {
// Here we can configure a ParseQuery to display
// only top-rated meals.
ParseQuery query = new ParseQuery(MidWifefirm.class);
return query;
}
});
Where you pass a class to the the query rather than the tableName, but I could be wrong on that one.
Either way I hope this has helped in some way!

Categories

Resources