I don't know if I understand the problem correctly, but I have a weird issue. I have list which I am getting from Parse.com server and I filter the data by "tags". Sometimes I change "tags" with DialogFragment and refresh the lists, but after 2-3 change the list is showing wrong data. I have debugged it and it seems that wrong data is being taken from Database, which is weird because in another Fragment window the same method returns correct answers.
Here is how I get list from Parse :
YoursEventsAsync yoursEventsAsync = new YoursEventsAsync(Constants.LIMIT, getActivity());
yoursEventsAsync.asyncResponse = this;
yoursEventsAsync.execute();
Then, in this AsyncTask I am getting data like this :
#SafeVarargs
#Override
protected final ArrayList<Event> doInBackground(ArrayList<Event>... params) {
return ParseAdapter.getInstance().getEventsFromServer(limit, context);
}
And this is my main function responsible for getting sometimes good sometimes bad data :
public ArrayList<Event> getEventsFromServer(int limit, Context context) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("Event");
query.whereContainedIn("tags", DatabaseAdapter.getTagNames(context));
query.setLimit(limit);
query.include("createdBy");
query.orderByDescending("createdAt");
ArrayList<Event> events = new ArrayList<>();
try {
List<ParseObject> parseEvents = query.find();
events = setEventList(parseEvents);
} catch (ParseException e) {
e.printStackTrace();
}
return events;
}
I have debugged the 3rd line and it sometimes works fine and sometimes not.
Here is DatabaseAdapter.getTagNames method :
public static List<String> getTagNames(Context context) {
Realm realm = Realm.getInstance(context);
RealmQuery<Tag> query = realm.where(Tag.class);
RealmResults<Tag> tagResults = query.findAll();
List<String> tags = new ArrayList<>();
for (Tag tag : tagResults) {
tags.add(tag.getName());
}
return tags;
}
As I have said earlier, if I open another fragment, which uses the same method 'getTagNames' it works fine. That's why I think the problem is with context. Is it possible that AsyncTask is somehow remembering the context and retrieveing the old one?
Related
I'm currently learning Xamarin and are at the stage of examining ListView. I've noticed that if I bind ListView to a List and remove an item from it, the listview will not display this change. I get it that I need to use ObservableCollection to have it work automatically (or have a collection implement proper interface), but I just would like to understand why doesn't it work even when i reset the ItemsSource property of ListView after the removal. Here's the code:
namespace Lists
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListsOne : ContentPage
{
List<Contact> _contacts = new List<Contact> {
new Contact{ Name = "Mosh", Number = "1234566", ImageUrl="http://lorempixel.com/100/100/people/1"},
new Contact { Name = "Josh", Number = "1236578" , ImageUrl = "http://lorempixel.com/100/100/people/2"}};
IEnumerable<Contact> GetContacts(string searchText = null)
{
if (String.IsNullOrWhiteSpace(searchText))
{
return _contacts;
}
return _contacts.Where(c => c.Name.StartsWith(searchText));
}
public ListsOne()
{
InitializeComponent();
listView.ItemsSource = GetContacts();
}
private void Delete_Clicked(object sender, EventArgs e)
{
Contact contact = (sender as MenuItem).CommandParameter as Contact;
_contacts.Remove(contact);
listView.ItemsSource = GetContacts();
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
listView.ItemsSource = GetContacts(e.NewTextValue);
}
}
}
As you can see in the Delete_Clicked method, after removing contact from _contacts, I reset the ItemsSource, but it will have no effect on the app, even though the pretty much same implementation for SearchBar_TextChanged works (if I recall correctly, am at work right now). Any insight on how it works? Sorry if it's dumb, but I am just learning.
Replace List with ObservableCollection. Latter one has event that notifies the UI about changes in it's array. (This is when you use MVVM, might not be applicable in in your example).
Also, as far as I know there is an issue with ListView.ItemSource when adding removing items to it. To make it work do this:
private void Delete_Clicked(object sender, EventArgs e)
{
Contact contact = (sender as MenuItem).CommandParameter as Contact;
_contacts.Remove(contact);
listView.ItemsSource = null;
listView.ItemsSource = GetContacts();
}
In the document of listview-ItemsSource, it says:
If you want the ListView to automatically update as items are added,
removed and changed in the underlying list, you'll need to use an
ObservableCollection. ObservableCollection is defined in
System.Collections.ObjectModel and is just like List, except that it
can notify ListView of any changes:
ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
listView.ItemsSource = employees;
//Mr. Mono will be added to the ListView because it uses an ObservableCollection
employees.Add(new Employee(){ DisplayName="Mr. Mono"});
I have a JsonObjectRequest Get Method (volley), it was working fine, I got an JsonObject and used it for further actions but now it doesn't do what I want..
In the same class like the Get Request, I have the Method processResponse, which creates an "Account" Object and adds it to the Manager List:
private void processResponse(JSONObject response) {
try {
Log.e("processResponse", " called");
final Account user = new Account("","","");
String username = response.getString("Username");
String email = response.getString("Email");
String id = response.getString("Id");
user.setUsername(username);
user.setEmail(email);
user.setId(id);
Manager.AddObjectToUserList(user);
Manager Class with List:
class Manager {
private static List<Account> _ListofUsers = new ArrayList<Account>();
static void AddObjectToUserList(Account acc)
{
_ListofUsers.add(acc);
}
List<Account> getListOfUsers(){
return _ListofUsers;
}
}
I get an JsonObject from the GetResponse, it does successfully process the Response and creates an Object but when i want to return the List in Manager to another class, the "return List" is 0, I don't get the problem, since it was working fine before.
In debug mode, it jumps after the return List into the Looper.java and it doesn't leave this class..
This is happening because of asynchronous behaviour. You are trying to add the object to list after you are creating the list, that's why you are getting 0. If you want to use that list, you need to use it in processResponse method. Please see also this post
Hope it helps.
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();
}
}
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.
I'm writing tests for a simple Android application (it's a school project) and I'm having trouble testing the activity ContactListActivity which extends Android's ListActivity.
What I would like to test
Clicking the first item in ContactListActivity's ListView and checking if the ContactDetailActivity was started.
Problem
The list data comes from an SQLite database. For testing, I'm loading test data into the ListView, so the test won't be working with live data. Loading the test data works fine. Watching the emulator while running the test, I can see the activity being started and the test data appearing in the list. However, trying to access the first (and only) list item fails.
Test method
#UiThreadTest
public final void testLoadContactDetail() {
ListView list = activity.getListView();
assertNotNull(list);
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData() // Creates an ArrayList of test data
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
// list.getAdapter().getCount() is expectedly 1
// list.getChildCount() is unexpectedly 0
assertNotNull(list.getChildAt(0)); // Assertion fails
// (...)
}
As can be seen, I'm annotating the test with #UIThreadTest to be able to manipulate view elements. A new ListAdapter is created with test data and set to the list. Then, adapter.notifyDataSetChanged() makes sure that the list knows about the new data.
Question
How can I load test data from within an ActivityInstrumentationTestCase2 into a ListView so that the data will not only be displayed on screen, but actually "be there", meaning the list item can be fetched with list.getChildAt(0) and be clicked?
Entire test case
public class ContactListActivityFunctionalTest extends
ActivityInstrumentationTestCase2<ContactListActivity> {
private ContactListActivity activity;
public ContactListActivityFunctionalTest() {
super(ContactListActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
activity = getActivity();
}
protected void tearDown() throws Exception {
super.tearDown();
}
#UiThreadTest
public final void testLoadContactDetail() {
ListView list = activity.getListView();
assertNotNull(list);
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData()
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
assertNotNull(list.getChildAt(0));
// Anything beyond this point is never executed,
// because the above assertion fails, and I have no idea
// if this test code is correct at all.
ActivityMonitor monitor = getInstrumentation().addMonitor(
ContactDetailActivity.class.getName(), null, false
);
TouchUtils.clickView(this, list.getChildAt(0));
ContactDetailActivity contactDetailActivity =
(ContactDetailActivity)monitor.waitForActivityWithTimeout(2000);
assertNotNull(contactDetailActivity);
assertTrue(getInstrumentation().checkMonitorHit(monitor, 1));
contactDetailActivity.finish();
}
private List<ContactInterface> createData() {
ContactInterface contact = new Contact();
contact.setId(1L);
contact.setName("Unit Test").setPhone("0123456789").setPosition(3);
List<ContactInterface> contacts = new ArrayList<ContactInterface>();
contacts.add(contact);
return contacts;
}
}
It looks like the listView.getChildAt method returns visible views. https://stackoverflow.com/a/6767006/693752
So, my guess is that the item is not visible yet. None are as getChildCount is returning 0. Maybe you should either :
wait a bit before asserting. Ok, it's dirty but UI testing needs it sometime.
post the assert inside a runnable on the ui thread so that it gets executed after the listview is executed. This will turn your test into something a bit more complex as you would have to synchronize the future runnable and the current testing thread a countDownLatch. And for this, you should consider not using #UIThreadTest.
I know I've asked how to load test data from within an ActivityInstrumentationTestCase2, but perhaps the answer to the question is to use ActivityUnitTestCase rather than ActivityInstrumentationTestCase2 in this particular case:
General activity behaviour is being tested, rather than interaction with other components
Well, it works...
Here is the rewritten, working test case that tests whether the ListView exists and whether the correct activity is started after a click on the list's first item.
public class ContactListActivityTest
extends ActivityUnitTestCase<ContactListActivity> {
private ContactListActivity activity;
public ContactListActivityTest() {
super(ContactListActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
Intent intent = new Intent(
getInstrumentation().getTargetContext(), ContactListActivity.class
);
startActivity(intent, null, null);
activity = getActivity();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public final void testItemClick() {
getInstrumentation().callActivityOnStart(activity);
getInstrumentation().callActivityOnResume(activity);
// Check if list exists
ListView list = activity.getListView();
assertNotNull("Intent was null", list);
// Load test data
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData()
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
assertEquals(2, adapter.getCount());
// Check if list has at least one item to click
View firstItem = list.getAdapter().getView(0, null, null);
assertNotNull(firstItem);
// Perform a click on the first item
list.performItemClick(
firstItem,
0,
list.getAdapter().getItemId(0)
);
// Check if the contact details activity got started
Intent intent = getStartedActivityIntent();
assertNotNull(intent);
assertEquals(
ContactDetailActivity.class.getName(),
intent.getComponent().getClassName()
);
}
private List<ContactInterface> createData() {
List<ContactInterface> contacts = new ArrayList<ContactInterface>();
ContactInterface contact = new Contact();
contact.setId(1L);
contact.setName("Jane Doe").setPhone("0123456789").setPosition(1);
contacts.add(contact);
ContactInterface contact2 = new Contact();
contact2.setId(2L);
contact2.setName("John Doe").setPhone("0234567890").setPosition(2);
contacts.add(contact2);
return contacts;
}
}