Espresso AutoCompleteTextView click - android

So I recently started messing around with Espresso in one of my existing Android projects.
Everything went pretty decently, until I came to find AutoCompleteTextView in my program. I don't seem to understand how to properly click the first thing in the autocomplete list. I'm actually not even sure which to use, onView() or onData() in this instance.

By some reasons which I don't know, AStupidNoob's solution doesn't work. So I found another one:
onView(withText("Spinner Item"))
.inRoot(RootMatchers.isPlatformPopup())
.perform(click());
The AutoCompleteTextView itself
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<AutoCompleteTextView
android:id="#+id/product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionThreshold="1"
android:hint="#string/product"
android:singleLine="true"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>

I think I found a bit of a cleaner method than the accepted answer!
onData(equalTo("ITEM")).inRoot(RootMatchers.isPlatformPopup()).perform(click());
The breakdown:
onData(x) This will find the view rendering the data object matching x in the drop down. The data is provided by the Adaptor given to the AutoCompleteTextView, so it can be an object of any type that Adaptor provides, it probably won't be a View. You'll want to use the standard hamcrest core matchers for this (equalTo, instanceOf, etc...) rather than (withText, withId, etc...). It might be a pain to try and find what object this is and how to match it, but there isn't a neater way: with a lot of items in your adapter some of the views won't even be in the hierarchy yet, so onView can't work! onData will make sure to load the views that match your data. Checkout here (this what onData returns) and here (this loads the matching data)
inRoot(RootMatchers.isPlatformPopup()) So it turns out the dropdown menu is on another window than the default window your activity runs in. So we have to specify that we want to search that window. The accepted answer uses RootMatchers.withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView()))) which seems to match any window that is not the default one.
Anyways HTH someone else.

For anyone still running into this issue, despite trying out the accepted solution above, I managed to get it working with help from a github issue I uncovered. For reference, I am using Robolectric 4.6, which I believe may be the reason why I am requiring a different solution from non-instrumented tests.
The solution I came up with (to verify an item is appearing in an AutoCompleteTextView popup is:
fun testAutoCompleteTextViewEntry() {
onView(withId(R.id.editText_search))
.perform(typeTextIntoFocusedView("x"), showDropDown())
onView(withText("xyz"))
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(isDisplayed()))
}
// Somewhere else in your code
fun showDropDown(): ViewAction =
object : ViewAction {
override fun getDescription(): String = "Shows the dropdown menu of an AutoCompleteTextView"
override fun getConstraints(): Matcher<View> = allOf(
isEnabled(), isAssignableFrom(AutoCompleteTextView::class.java)
)
override fun perform(uiController: UiController, view: View) {
val autoCompleteTextView = view as AutoCompleteTextView
autoCompleteTextView.showDropDown()
uiController.loopMainThreadUntilIdle()
}
}

So i finally figured it out, thanks to this previous question:
Testing autocomplete textview using espresso tool
Ill just post my version of it for people who might use it in future.
onData(instanceOf("Whatever your arrayadapter contains".class)).inRoot(RootMatchers.withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())))).perform(ViewActions.click());

Following on from the answer from AStupidNoob on 28 July 2017...
To click on a specific row number of the drop-down list, you can use this:
onData(anything())
.atPosition(2)
.inRoot(RootMatchers.isPlatformPopup())
.perform(click());
To click on a specific item in a specific row number of the drop-down list, you can use this:
onData(anything())
.atPosition(2)
.inRoot(RootMatchers.isPlatformPopup())
.onChildView(withId(R.id.button_on_layout))
.perform(click());

Since the place predictions are inside a recyclerview, after typing the name of the placement, if you want to click on the first option in the predicted list, you can go with one of the RecyclerViewActions methods (actionOnItemAtPosition).
The key is to find out the id of the autocompleted places. It is set up by Google Place SDK but not yourself so might not be that straightforward to find out.
This works for my project, see if it can help:
onView(ViewMatchers.withId(com.google.android.libraries.places.R.id.places_autocomplete_list)).perform(RecyclerViewActions.actionOnItemAtPosition(0,click()));

Related

Why is the Android ViewPager2 class final?

While using ViewPager2 in my current project, I wanted to use setOnTouchListener, but I was unable to get any events.
I decided to extend the ViewPager2 class to override the touch event methods, but was unable to as the class definition is final.
Why is this class final and is there any way to extend it so that I can override the touch events?
Well I think they f'd up the whole viewpager thing from the getgo. Most liky because all the fragment in fragment code was also messed up back than.
They even ask us to not use the normal viewpager from brand new androidx because it has errors. I think some errors that are reported are now older code trying to fix issues from old viewpager implementations. I think they now got the code stable enough to be useable and don't want us to implement all the now-unnessessary and maybe counter-productive fixes when transitioning to viewpager2.
I am quite mad at how lazy google handled the whole fragments and viewpager stuff in the past. It speaks volumes they never got it working in the main codebase and always suggested using the compat packages.
So in short. Now after years they got it working and they want you to drop all your fixes that are now not needed anymore..
Likely it is final, because it is not the idea to extend on it
... and that keyword is extremely effective in preventing that.
You can use onTouch of recyclerView which inside of viewPage2.
fun ViewPager2.getRecyclerView(): RecyclerView {
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
recyclerViewField.isAccessible = true
return recyclerViewField.get(this) as RecyclerView
}
Usage
pager2.getRecyclerView().setOnTouchListener { view, motionEvent ->
false
}

Which is more recommendable in kotlin?

if i have this button:
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/button" />
which is more advisable in kotlin when it comes to calling views?
this:
val buttonVar: Button = findById(R.id.buuton)
buttonVar.setOnClickListener{
//my code
}
or:
button.setOnClickListener{
//my code
}
When it comes to performance in Kotlin , this is more advisable
button.setOnClickListener{
//my code
}
Because calling views by their ID directly will generate a local view cache.
So the when the view is called the first time kotlin plugin will execute findViewById just a single time, and the next time the view gets called, it will get recovered from the cache . So accesing that view will be faster.
You can refer to this link for more informations enter link description here
I hope this will help you, don't forget to accept the answer if it helps you.
First one is recommended now. The reason is because if you do it the second way, you would be using kotlinx synthetic which is no longer a recommended practice. Source

AutoCompleteTextView item selection programmatically

I have an AutoCompleteTextView that is filled with cities from an sqlite database that calls an AsyncTask on item click, recently I added an option to detect my location using the gps, so the problem is I can detect the city (i.e Beirut) and set the text for the AutoCompleteTextView but the thing is that the dropdown filter opens showing Beirut (which is correct) but I still need to click on the list item to invoke the listener, how to do so programmatically
How to:
Enter the Activity (DONE)
Detect location (DONE)
set text of text view (DONE)
show textview dropdown list(DONE)
choose the item that will be returned, since it will only return one city (NOT DONE)
To be clear, Tano's solution is sufficient to answer this question. But, in case others run into the same use case I did, here's some more background that may potentially help you...
I had been running into this issue specifically while trying to make a non-editable Material Exposed Dropdown Menu and set it's initial value programmatically. The documentation to create this type of "dropdown" can be found in the Exposed Dropdown Menus section here, which suggests a mechanism using TextInputLayout and AutocompleteTextView (even if you don't want autocomplete functionality).
Failed Solution 1:
At first glance setListSelection() and getListSelection() seemed like they might do the trick. But after many trials, I learned that they may not be sufficient because they only work when the list popup isShowing(). So for example, if you simply want to set the initial selection without having to show the list popup first, this will not work.
Failed Solution 2:
Then I tried setText() which showed the proper text in my textbox. Yay! But wait! When I clicked on the text view, only a subset of options in the list popup were shown for some reason. Why was that? The key thing to keep in mind here is that since this is an autocomplete textview, it by default filters out options based off of the text in the textview. This might not be apparent, especially if you're solely using this control for the sake of making a simple non-editable dropdown selector.
Solution:
This brings us to our actual solution (suggested by Tano)... setText() with filter as false will turn off the filtering capabilities AND it will not change the contents of your list popup.
autoCompleteTextView.setText(myText, false);
I was facing a similar problem and this solved my issue. Important is to call setText(<text>, <filter boolean>) in order not to filter with the given text set the second parameter with false. The text will be got from the dropdown adapter.
Solution snippet:
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(position).toString(), false);
A solution were you don't need to change your API level.
automCompleteTextView.setAdapter(adapter);
// set default selection, filtering is active so all items is not visible in drop-down menu
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString());
// change filtering for the adapter so all items can be visible in drop-down menu
adapter.getFilter().filter(null);
one-liner for the same job but requires higher API level
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString(), false);
I figure out after dig into the AutoCompleteTextView code on android source code:
fun AutoCompleteTextView.selectItem(text: String, position: Int = 0) {
this.setText(text)
this.showDropDown()
this.setSelection(position)
this.listSelection = position
this.performCompletion()
}
autoComplete.setListSelection(position);
I have used autoCompleteTextView.setText(myText, false); solution as well, however it sometimes failed. I mean it was actively filtering results so, when user clicks there was only 1 item at dropdown.
In addition I also needed this to work on custom objects as well, and this is my my solution:
binding.hourEditText.configureDropDownMenu(viewModel.hours) { it.hourString() }
.subscribe {
// Do whatever you need when on click.
}
.addTo(disposables)
fun <T> AutoCompleteTextView.configureDropDownMenu(list: List<T>, toString: ((T) -> String)? = null): Observable<T> {
keyListener = null
val textItems = toString?.let(list::map) ?: list.map { it.toString() }
setAdapter(NonFilterArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, textItems))
return itemClickEvents().map {
list[it.position]
}
}
private class NonFilterArrayAdapter<T>(context: Context, #LayoutRes resource: Int, objects: List<T>) : ArrayAdapter<T>(context, resource, objects) {
override fun getFilter() = NonFilter()
private class NonFilter : Filter() {
override fun performFiltering(constraint: CharSequence?) = FilterResults()
override fun publishResults(constraint: CharSequence?, results: FilterResults?) = Unit
}
}
Note: This also contains a bit of Rx, but it can be removed easily.
Try with adding below after setText() in AutoCompleteTextview:-
autoCompleteTV.setSelection(position);
Updated:
This will work in Spinner and AutoCompleteTextView which has dropdown feature, but it will not work with EditText.
Here you can check docs for AbsSpinner in this link:
https://developer.android.com/reference/android/widget/AbsSpinner.html#setSelection(int)
The problem is that you are setting a text and the AutoCompleteTextView is only showing words that match with that text. A non elegant way of solving this is to set an high threshold (at least the max length of the names of the cities) to force Android to show you all the values of your list (this threshold is the number of characters that the field must have to search similarities).
Using the Nilton Vasques solution it can be so:
with(autoComplete) {
setAdapter(this#YourFragment.adapter)
setText(itemText)
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0 // Because AutoCompleteTextView shows the next row.
performCompletion()
}
Notice, that it will show a drop-down list, otherwise listSelection won't work. If you call dismissDropDown(), the item won't be selected. If you don't want to show the drop-down list, you can use setOnTouchListener to capture opening the list, but it hardly will help (you should resolve a filtering problem).
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0
performCompletion()
requestFocus()
}
false
}

Inverse Boolean using Two Way Databinding

Using two-way Android Databinding, is it possible to have a generic inverse boolean converter? For example, I would like to do something like this:
<Switch android:checked="#={!viewModel.myBoolean}" />
When I run this in Android, the switch just rapidly fires back and forth. I tried to create a two way binding app:inverseChecked following some examples from George Mount, but I was not successful (just kept getting error stating cannot find event 'inverseCheckedAttrChanged' on View type 'android.widget.Switch').
As a comparison, using Aurelia this just works as you would expect for two way binding. In WPF, probably the first converter you make is some sort of InverseBooleanConverter to easily tackle these sorts of things. So, am assuming I am just missing something obvious here.
I actually didn't expect it to work at all. I assume, it's switching back and forth all the time, because the bindings don't apply inverse function of your binding expression.
That said, I tested the behavior with the current data binding library version and checked the generated sources. With the simple example of android:checked these show notes how the inverse should look like and apply it appropriately.
Also George Mount wrote a Blog post about it a short while ago: https://medium.com/google-developers/android-data-binding-inverse-functions-95aab4b11873
If you try to implement an app:inverseChecked, you'd also have to implement a #BindingAdapter("inverseChecked") as setter, #InverseBindingAdapter(attribute="inverseChecked") as getter and #BindingAdapter("inverseCheckedAttrChanged") for setting up the change listener.
The latter could look like the following:
#BindingAdapter("inverseCheckedAttrChanged")
public static void setupInverseCheckedAttrChanged(Switch view, InverseBindingListener listener) {
OnCheckedChangeListener newListener = null;
if (listener != null) {
newListener = (v,b) -> listener.onChange();
}
view.setOnCheckedChangeListener(newListener);
}

Testing ViewPager with multiple fragments using android espresso

I am trying to test my app which uses ViewPager. Each page contains fragments but these fragments are not always visible. I want to check visibility of a fragment in the currently visible page.
onView(withId(R.id.container_weather))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
But the problem is that espresso looks are all the pages not just the current page and I get the following error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with id: eu.airpatrol.android:id/container_weather' matches multiple views in the hierarchy...
I had the same problem, however using the condition isCompletelyDisplayed() solved this problem as it only takes into account the on-screen views.
So, something like this should work:
onView(allOf(withId(R.id.container_weather), isCompletelyDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
Note: isDisplayed() works too in some cases but it also takes views off-screen into account and won't work if the ViewPager has any other page pr fragment loaded with the same view id.
Your tests are failing because of multiple elements with the same id. You can combine conditions using allOf(...). Then use isDisplayed() to check that matched view is visible on the screen. Below example can work:
onView(allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.container_weather)))
.check(matches(isDisplayed()));
Ran into this exact same problem. I was fortunate because the view hierarchies in my ViewPager can be easily identified by their siblings, so I was able to solve this using the hasSibling matcher, like so:
onView(
allOf(
hasSibling(withId(R.id.some_sibling)),
withId(R.id.field_to_test)
)
).perform(replaceText("123"));
Not a perfect solution as it can be slightly brittle, but in my case I think it was an acceptable compromise.
I had similar problem, where I was reusing the button layout and it was giving me a matches multiple views in the hierarchy exception.
So the easy work around I did was to create 2 different screens and have 2 different methods with different text.
Withdraw Screen:
public WithdrawScreen clickWithdraw() {
onView(allOf(withId(R.id.save_button), withText("Withdraw")))
.perform(click());
return this;
}
Deposit Screen:
public DepositScreen clickDeposit() {
onView(allOf(withId(R.id.save_button), withText("Deposit")))
.perform(click());
return this;
}
and in my tests, I create a new instance of both screens and call the above methods based on screen reference which is a bit easy to test for.
WithdrawScreen withdrawInstance = new WithdrawScreen();
withdrawInstance.clickWithdraw();
DepositScreen depositInstance = new DepositScreen();
depositInstance.clickDeposit();
The point was they were using same id - R.id.save_button for button and I was replacing text of button based on visibility of the fragment we are on.
Hope it helps.

Categories

Resources