Android Espresso onData with doesNotExist - android

I am trying to verify that a ListView does not contain a particular item. Here's the code I'm using:
onData(allOf(is(instanceOf(Contact.class)), is(withContactItemName(is("TestName")))))
.check(doesNotExist());
When the name exists, I correctly get an error because of check(doesNotExist()). When the name does not exist, I get the following error, because allOf(...) doesn't match anything:
Caused by: java.lang.RuntimeException: No data found matching:
(is an instance of layer.sdk.contacts.Contact and is with contact item name:
is "TestName")
How can I get functionality like onData(...).check(doesNotExist())?
EDIT:
I have a terrible hack to get the functionality I'd like by using try/catch and inspecting the event's getCause(). I would love to replace this with a good technique.

According to Espresso samples you must not use onData(...) to check if view doesn't exists in adapter. Check this out - link. Read "Asserting that a data item is not in an adapter" part. You have to use a matcher together with onView() that finds the AdapterView.
Based on Espresso samples from link above:
matcher:
private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("with class name: ");
dataMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
if (!(view instanceof AdapterView)) {
return false;
}
#SuppressWarnings("rawtypes")
Adapter adapter = ((AdapterView) view).getAdapter();
for (int i = 0; i < adapter.getCount(); i++) {
if (dataMatcher.matches(adapter.getItem(i))) {
return true;
}
}
return false;
}
};
}
then onView(...), where R.id.list is the id of your adapter ListView:
#SuppressWarnings("unchecked")
public void testDataItemNotInAdapter(){
onView(withId(R.id.list))
.check(matches(not(withAdaptedData(is(withContactItemName("TestName"))))));
}
And one more suggestion - to avoid writing is(withContactItemName(is("TestName")) add below code to your matcher:
public static Matcher<Object> withContactItemName(String itemText) {
checkArgument( itemText != null );
return withContactItemName(equalTo(itemText));
}
then you'll have more readable and clear code is(withContactItemName("TestName")

Related

In Espresso, how can I perform an action on all matching views without triggering an AmbiguousViewMatcherException?

I'm using Espresso to run automated UI tests on an Android app. I want to perform an action on all views that match specified conditions. Espresso does use an allOf() method to find all views that a matcher matches. However, commands such as onView(withText("some text")).perform(click()) would throw an AmbiguousViewMatcherException if there is more than one match.
I do have a method for getting the nth matching view when there are multiple matches.
private static Matcher<View> getElementFromMatchAtPosition(final Matcher<View> matcher, final int position) {
return new BaseMatcher<View>() {
int counter = 0;
#Override
public boolean matches(final Object item) {
if (matcher.matches(item)) {
if(counter == position) {
counter++;
return true;
}
counter++;
}
return false;
}
#Override
public void describeTo(final Description description) {
description.appendText("Element at hierarchy position " + position);
}
};
}
Sure, I could use this method to loop through every view. But is there a more elegant solution?
And what if I don't know how many matching views there are?
It sounds like you may have been using allOf() incorrectly.
As you noted in your comment, allOf() should be used with matchers. You can then pick all the unique aspects of the view you want to select, comma separated.
Here's an example:
onView(allOf(isDescendantOfA(withId(R.id.input)), withText("jeff#bob.com"))).check(matches(isDisplayed()));

Searching a LiveData of PagedList in RecyclerView by Observing ViewModel

With android Paging library it is really easy to load data from Database in chunks and ViewModel provides automatic UI update and data survival. All these frameworks modules help us create a great app in android platform.
A typical android app has to show a list of items and allows user to search that list. And this what I want to achieve with my app. So I have done an implementation by reading many documentations, tutorials and even stackoverflow answers. But I am not so sure whether I am doing it correctly or how I supposed to do it. So below, I have shown my way of implementing paging library with ViewModel and RecyclerView.
Please, review my implementation and correct me where I am wrong or show me how I supposed to do it. I think there are many new android developers like me are still confused how to do it correctly as there is no single source to have answers to all your questions on such implementation.
I am only showing what I think is important to show. I am using Room. Here is my Entity that I am working with.
#Entity(tableName = "event")
public class Event {
#PrimaryKey(autoGenerate = true)
public int id;
public String title;
}
Here is DAO for Event entity.
#Dao
public interface EventDao {
#Query("SELECT * FROM event WHERE event.title LIKE :searchTerm")
DataSource.Factory<Integer, Event> getFilteredEvent(String searchTerm);
}
Here is ViewModel extends AndroidViewModel which allows reading and searching by providing LiveData< PagedList< Event>> of either all events or filtered event according to search text. I am really struggling with the idea that every time when there is a change in filterEvent, I'm creating new LiveData which can be redundant or bad.
private MutableLiveData<Event> filterEvent = new MutableLiveData<>();
private LiveData<PagedList<Event>> data;
private MeDB meDB;
public EventViewModel(Application application) {
super(application);
meDB = MeDB.getInstance(application);
data = Transformations.switchMap(filterEvent, new Function<Event, LiveData<PagedList<Event>>>() {
#Override
public LiveData<PagedList<Event>> apply(Event event) {
if (event == null) {
// get all the events
return new LivePagedListBuilder<>(meDB.getEventDao().getAllEvent(), 5).build();
} else {
// get events that match the title
return new LivePagedListBuilder<>(meDB.getEventDao()
.getFilteredEvent("%" + event.title + "%"), 5).build();
}
}
});
}
public LiveData<PagedList<Event>> getEvent(Event event) {
filterEvent.setValue(event);
return data;
}
For searching event, I am using SearchView. In onQueryTextChange, I wrote the following code to search or to show all the events when no search terms is supplied meaning searching is done or canceled.
Event dumpEvent;
#Override
public boolean onQueryTextChange(String newText) {
if (newText.equals("") || newText.length() == 0) {
// show all the events
viewModel.getEvent(null).observe(this, events -> adapter.submitList(events));
}
// don't create more than one object of event; reuse it every time this methods gets called
if (dumpEvent == null) {
dumpEvent = new Event(newText, "", -1, -1);
}
dumpEvent.title = newText;
// get event that match search terms
viewModel.getEvent(dumpEvent).observe(this, events -> adapter.submitList(events));
return true;
}
Thanks to George Machibya for his great answer. But I prefer to do some modifications on it as bellow:
There is a trade off between keeping none filtered data in memory to make it faster or load them every time to optimize memory. I prefer to keep them in memory, so I changed part of code as bellow:
listAllFood = Transformations.switchMap(filterFoodName), input -> {
if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
synchronized (this) {
//check data is loaded before or not
if (listAllFoodsInDb == null)
listAllFoodsInDb = new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
}
return listAllFoodsInDb;
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch("%" + input + "%"), config)
.build();
}
});
Having a debouncer helps to reduce number of queries to database and improves performance. So I developed DebouncedLiveData class as bellow and make a debounced livedata from filterFoodName.
public class DebouncedLiveData<T> extends MediatorLiveData<T> {
private LiveData<T> mSource;
private int mDuration;
private Runnable debounceRunnable = new Runnable() {
#Override
public void run() {
DebouncedLiveData.this.postValue(mSource.getValue());
}
};
private Handler handler = new Handler();
public DebouncedLiveData(LiveData<T> source, int duration) {
this.mSource = source;
this.mDuration = duration;
this.addSource(mSource, new Observer<T>() {
#Override
public void onChanged(T t) {
handler.removeCallbacks(debounceRunnable);
handler.postDelayed(debounceRunnable, mDuration);
}
});
}
}
And then used it like bellow:
listAllFood = Transformations.switchMap(new DebouncedLiveData<>(filterFoodName, 400), input -> {
...
});
I usually prefer to use DataBiding in android. By using two way Data Binding you don't need to use TextWatcher any more and you can bind your TextView to the viewModel directly.
BTW, I modified George Machibya solution and pushed it in my Github. For more details you can see it here.
I will strong advice to start using RxJava and you it can simplify the entire problem of looking on the search logic.
I recommend in the Dao Room Class you implement two method, one to query all the data when the search is empty and the other one is to query for the searched item as follows. Datasource is used to load data in the pagelist
#Query("SELECT * FROM food order by food_name")
DataSource.Factory<Integer, Food> loadAllFood();
#Query("SELECT * FROM food where food_name LIKE :name order by food_name")
DataSource.Factory<Integer, Food> loadAllFoodFromSearch(String name);
In the ViewModel Class we need to two parameter that one will be used to observed searched text and that we use MutableLiveData that will notify the Views during OnChange. And then LiveData to observe the list of Items and update the UI.
SwitchMap apply the function that accept the input LiveData and generate the corresponding LiveData output. Please find the below Code
public LiveData<PagedList<Food>> listAllFood;
public MutableLiveData<String> filterFoodName = new MutableLiveData<>();
public void initialFood(final FoodDao foodDao) {
this.foodDao = foodDao;
PagedList.Config config = (new PagedList.Config.Builder())
.setPageSize(10)
.build();
listAllFood = Transformations.switchMap(filterFoodName, outputLive -> {
if (outputLive == null || outputLive.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
return new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch(input),config)
.build();
}
});
}
The viewModel will then propagate the LiveData to the Views and observe the data onchange. In the MainActivity then we call the method initialFood that will utilize our SwitchMap function.
viewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
viewModel.initialFood(FoodDatabase.getINSTANCE(this).foodDao());
viewModel.listAllFood.observe(this, foodlistPaging -> {
try {
Log.d(LOG_TAG, "list of all page number " + foodlistPaging.size());
foodsactivity = foodlistPaging;
adapter.submitList(foodlistPaging);
} catch (Exception e) {
}
});
recyclerView.setAdapter(adapter);
For the first onCreate initiate filterFoodName as Null so that to retrieve all items.
viewModel.filterFoodName.setValue("");
Then apply TextChangeListener to the EditText and call the MutableLiveData that will observe the Change and update the UI with the searched Item.
searchFood.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i,
int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int
i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
//just set the current value to search.
viewModel.filterFoodName.
setValue("%" + editable.toString() + "%");
}
});
}
Below is my github repo of full code.
https://github.com/muchbeer/PagingSearchFood
Hope that help

Android notifyItemRemoved() Wrong Index notified

Good day. I have a small chat app where i want to simply delete the chat item locally.
By debugging around i can reckon that there is something wrong with notifyItemRemoved() as follows :
• I have debugged the returned index from my List and the index was correct for list.
• I have debugged the returned Model Class from the ViewHolder and the returned model class was correct.
• I have debugged my custom equals() method inside my Model and it was comparing correctly returning correct item.
The main complain about my debugging is i dont know why,but the equals() method is being called 2 times...pretty weird though if you would have time please consider this case as well.
The main problem is the notifyItemRemoved() is always removing not the correct item,but always 1 item above the item which must be deleted (in means of deleted i mean deleted from the view as deleting from the list is happening correctly)
So i have no clue what is going on here.
Here is my custom equals() methods
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof ChatModel) {
ChatModel chatModel = (ChatModel) obj;
if (chatModel.getMessageId().equals(messageId)) {
return true;
}
}
return false;
}
#Override
public int hashCode() {
return messageId.hashCode();
}
Here is how i insert items into adapter list.
For the list of models->
public void insertChatModelWithItemNotify(List<ChatModel> chatModel, boolean firstPaging) {
if (firstPaging) {
this.chatModelList.addAll(chatModel);
notifyDataSetChanged();
} else {
this.chatModelList.addAll(0, chatModel);
notifyItemRangeInserted(0, chatModel.size());
}
}
For a single model - >
public void insertChatModel(ChatModel chatModel) {
this.chatModelList.add(chatModel);
notifyDataSetChanged();
}
Here is how i remove the item from the list
public void removeItem(ChatModel chatModel) {
int position = this.chatModelList.indexOf(chatModel);
this.chatModelList.remove(chatModel);
notifyItemRemoved(position);
}
Any ideas?
You are probably adding some kind of header to the RecyclerView which makes the object index in your object list different from the View index in the RecyclerView.

How to check if a list is empty in this specific response format with null object in Android

I got a response back from the API like this below:
{
"transactions": [null]
}
However, when I tried to debug, List.getTransactionItems().size() is equal 1 rather than 0. I think it considers null as an item. Also, I checked few things as below but none of them work.
if (this.transactionsViewModel.getTransactionItems().size() == 0
|| this.transactionsViewModel.getTransactionItems() == null
|| this.transactionsViewModel.getTransactionItems().isEmpty()
|| this.transactionsViewModel.getTransactionItems().equals(null))
However, when I tried to call something like that below, it actually recognized that there is an null item in the list.
this.transactionsViewModel.getTransactionItems().contains(null)
Any idea in this situation?
Thanks in advance.
As far as i know, json array not supposed to be null like that. You can make "transactions": [] or "transactions": null instead. That behaviour happens because your code recognise that the array is not empty. It has 1 item with null value.
If you can not change the server response.
You can check if the list only contains null by removeAll null object from your list then check the size
(this.transactionsViewModel.getTransactionItems().removeAll(Collections.singleton(null))).size() == 0
// size == 0 your list only contains null
// size > 0 your list is not empty
How about reading the stream into a string, and doing a search and replace on the json string, and remove the null, before you parse the json? You can also extend a stream class, and do it in the stream.
This is the problem about the unsuitable response from the server. To solve your problem, you can only to make some workaround in your code.
I suggest you should keep your application as clear and origin as possible. Don't change the condition call if it is really necessary.
The following is my suggested code ( used some code from Phan Van Linh)
This class used Retrofit library as the example to make the callback abstract
public class TestFragment extends BaseFragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
view = inflater.inflate(R.layout.testlayout, null, false);
RestA rest = UtilRetrofit.build(RestA.class);//from the restful endpoint
Call<YourObject> call = rest.getTransactions();
call.enqueue(new TransactionCallback() {
#Override
public void onResponse(YourObject bean) {
Toast.makeText(getActivity(), "Hello World", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<YourObject> call, Throwable t) {
}
});
return view;
}
public abstract class TransactionCallback implements Callback<YourObject> {
#Override
public void onResponse(Call<YourObject> call, Response<YourObject> response) {
YourObject lNewBean = response.body();
lNewBean.getTransactions().removeAll(Collections.singleton(null));// do some custom action and remove useless thing
onResponse(lNewBean);
}
public abstract void onResponse(YourObject bean);
}
/**
* Created by me on 22/8/2016.
*/
public static class YourObject {
private List<String> transactions;
public List<String> getTransactions() {
return transactions;
}
public void setTransactions(List<String> pTransactions) {
transactions = pTransactions;
}
}
}

Espresso onData test

I'm writing Espresso tests for a navigation drawer that has a long, dynamic list of entries. I'd like to match a navDrawer menu item by Text and not position number. Looking at Google's DataAdapterSample, I would expect I could use this to get a match:
#Test
public void myTest() {
openDrawer();
onRow("Sign In").check(matches(isCompletelyDisplayed()));
}
private static DataInteraction onRow(String str) {
return onData(hasEntry(equalTo("module_name"), is(str)));
}
I'm not getting a match. But in the log I can see what I'm looking for. I get
No data found matching: map containing ["module_name"->is "Sign In"]
contained values: <[
Data: Row 0: {_id:"0", module_name:"Applications", module_secure:"false", headerCollapsible:1, } (class: android.database.MatrixCursor) token: 0,
Data: Row 1: {_id:"1", module_name:"Sign In", module_lock:"false", module_right_text:null, } (class: android.database.MatrixCursor) token: 1,
...
I think the hasEntry() works only for Maps, and it seems to me that items in your navigation drawer are not Maps but rather MatrixCursors.
Just replace the Person class in the example with MatrixCursor class.
For example something like this:
private static DataInteraction onRow(final String str) {
return onData(new BoundedMatcher<Object, MatrixCursor>(MatrixCursor.class) {
#Override
public void describeTo(Description description) {
description.appendText("Matching to MatrixCursor");
}
#Override
protected boolean matchesSafely(MatrixCursor cursor) {
return str.equals(cursor.getString(1));
}
});
}
Here I assume that second column of the cursor contains the text we need to match to. I am assuming this based on the "No data found matching.." error message.

Categories

Resources