I'm desperately trying to simulate a click on a spinner item with Espresso.
The spinner is populated with objects from the class Project. This class has a toString() method, which allows the spinner to display String.
private void populateDialogSpinner() {
final ArrayAdapter<Project> adapter2 = new ArrayAdapter<Project>(this, android.R.layout.simple_spinner_item, allProjects);
adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
vm.getAllProjects().observe(this, projects -> {
allProjects.clear();
allProjects.addAll(projects);
adapter2.notifyDataSetChanged();
});
I have found several solutions in order to use this spinner with Espresso but none of them has worked. This is my Test class.
#Test
public void addActivity () throws InterruptedException {
onView(withId(R.id.fab_add_task)).perform(click());
onView(withId(R.id.txt_task_name)).perform(replaceText(textTaskText), closeSoftKeyboard());
onView(withId(R.id.project_spinner)).perform(click());
// A few solutions I have tried :
// 1 onView(withText("Projet Lucidia")).perform(click());
// 2 onData(anything())
// .inAdapterView(withId(R.id.project_spinner))
// .onChildView(withMyValue("Projet L"))
// .perform(click());
// 3 onData(anything()).atPosition(1).perform(click());
// 4 onView(allOf(withId(R.id.project_spinner), withSpinnerText("Project Lucidia"))).perform(click());
withMyValue refers to this class
public static Matcher<View> childAtPosition(final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent) && view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
Each time, the emulator stays with the spinner open like this :
Any idea of how I can handle this?
I'm not sure if it's the best answer but I have found this code thats works perfectly well.
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject spinnerItem = uiDevice.findObject(new UiSelector().text("Projet Lucidia"));
spinnerItem.click();
Related
For our application I had to implement a nested RecyclerView. I'm getting a list of Tables from JSON and every Table has another list with groups from each table. I can get everything on the screen as requested, the problem is the selection.
I have 2 different RecyclerViews on the screen and I can not seem to get a single selection working in this environment, especially after scrolling. Every group and every table has a Toggle Button, and only one can be active at a time.
This is how the main screen looks like
So far I've tried putting a boolean isSelected on the Model but that didn't work out at all. The closest solution I came up with was a helper class that searches every CompoundButton on-screen and deselects them all when one is selected. The problem is this helper class cant get the Buttons which are off-screen.
How I populate ParentAdapter (in MainActivity):
public void setAdapter(List<Table> tableList)
{
RecyclerView recycler_view_parent = findViewById(R.id.recyclerparent);
LinearLayoutManager manager=new LinearLayoutManager(MainActivity.this);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
recycler_view_parent.setLayoutManager(manager);
recycler_view_parent.setHasFixedSize(true);
recycler_view_parent.setItemViewCacheSize(tableList.size());
ParentAdapter parentAdapter=new ParentAdapter(tableList,MainActivity.this);
recycler_view_parent.setAdapter(parentAdapter);
}
How i populate ChildAdapter (in onBindViewHolder of ParentAdapter):
FlexboxLayoutManager manager = new FlexboxLayoutManager(context);
manager.setFlexDirection(FlexDirection.COLUMN);
manager.setJustifyContent(JustifyContent.FLEX_START);
manager.setFlexWrap(FlexWrap.WRAP);
manager.setAlignItems(AlignItems.BASELINE);
holder.recycler_view_child.setLayoutManager(manager);
holder.recycler_view_child.setHasFixedSize(true);
adapter = new ChildAdapter(tableList, tableList.get(position).getGroups(), context);
holder.recycler_view_child.setAdapter(adapter);
The desired output should be only 1 Table OR Group at a time can be toggled (in total, not one from every RecyclerView) and the state should be the same after scrolling/device rotation).
I did a lot of research over the last days on this subject and I can not seem to find a working example of nested RecyclerView with single selection over both RVs.
So does anyone have an idea on how to solve this? I think the biggest issue is telling the Parent that a Button in Child was toggled and vice-versa.
I think for the ParentAdapter it should look something like this:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, final int position) {
final Table table = tablelist.get(position);
ViewHolder viewHolder = (ViewHolder) holder;
if (table.isTableSelected()) {
viewHolder.toggletable.setChecked(true);
lastToggled = position;
} else {
viewHolder.toggletable.setChecked(false);
}
viewHolder.toggletable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
table.setTableSelected(true);
// notify ChildAdapter and group.setGroupSelected(false)
if (lastToggled >= 0) {
tablelist.get(lastToggled).setTableSelected(false);
// notify ChildAdapter and group.setGroupSelected(false)
notifyItemChanged(lastToggled);
}
lastToggled = position;
} else {
table.setTableSelected(false);
}
}
});
}
Thanks in advance.
UPDATE: Managed to come up with a solution myself, although 100% sure, not the best approach.
First of all, implement Greenrobots EventBus:
implementation 'org.greenrobot:eventbus:3.1.1'
Now in the Activity where you hold both RecyclerViews register the Event listener:
#Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
and subscribe 2 methods. One for Parent Events and one for Children Events. This methods will trigger every time an item is selected!
#Subscribe(threadMode = ThreadMode.MAIN)
public void onParentEventClicked(ParentAdapter.ParentEvent event) {
// to access the inner adapter here you must set it to public in the ParentAdapter(public ChildAdapter adapter;)
adapter.adapter.deSelectChild();
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onChildEventClicked(ChildAdapter.ChildEvent event) {
// normal ParentAdapter reference(ParentAdapter adapter;)
adapter.deSelectParent();
}
Inside your ParentAdapter create a method to deselect all parent items and a static class to fire the event:
public void deSelectParent()
{
for (int i=0;i<data.size();i++)
{
data.get(i).setSelected(false);
}
notifyDataSetChanged();
}
public static class ParentEvent {
View view;
int position;
}
Inside your ChildAdapter create a method to deselect all child items and a static class to fire the event:
public void deSelectChild()
{
for (int i=0;i<data.size();i++)
{
datachild.get(i).setSelected(false);
}
notifyDataSetChanged();
}
public static class ChildEvent {
View view;
int position;
}
now in both Parent and Child onBindViewHolders, you need similar logic for your models:
if (item.isSelected()) {
holder.yourbutton.setChecked(true);
} else {
holder.yourbutton.setChecked(false);
}
holder.yourbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ParentEvent event = new ParentEvent();
event.view = holder.yourbutton;
event.position = position;
EventBus.getDefault().post(event);
if (holder.yourbutton.isChecked()) {
for (int i = 0; i < data.size(); i++) {
data.get(i).setSelected(false);
}
data.get(position).setSelected(true);
} else {
data.get(position).setSelected(false);
}
notifyDataSetChanged();
}
});
And thats pretty much it, every click on a ParentItem will trigger the deselect method for ChildAdapter and vice-versa.
Due to the high usage of notifyDataSetChanged() I recommend using this line to get rid of the blinking:
recycler_view_parent.setItemAnimator(null);
Any problems let me know!
I have a ListView with rows with different layouts. So I'm using the pattern of ViewHolder.
If the user clicks on a row, one sub-layout of the same row must be shown/hidden.
viewHolder.btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (int) v.getTag();
Log.d(TAG, "Line in position " + position + " clicked");
if (!checkBoxSendChoice[position]) {
checkBoxSendChoice[position] = true;
viewHolder.layout_choice.setVisibility(View.VISIBLE);
} else {
checkBoxSendChoice[position] = false;
viewHolder.layout_choice.setVisibility(View.GONE);
}
}
});
However I noticed that the entire ListView is refreshed (getView is called multiple times for all rows), because of setVisibility(). If I comment out the two setVisibility() instructions, the ListView isn't refreshed anymore.
Is it possible to optimize and avoid refreshing all the views in the ListView?
I think there is a better way of doing this. Instead of editing the view directly, you should have a Boolean isVisible inside the list item and change that, then notify the adapter that an item has changed. This will make the holder re-bind to the item. And inside the holder's bind function you can set the view's visibility depends on the boolean. Here is a rough example (half pseudo code):
List<MyItem> items;
viewHolder.btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (int) v.getTag();
Log.d(TAG, "Line in position " + position + " clicked");
checkBoxSendChoice[position] != checkBoxSendChoice[position];
items.get(position).isVisible = heckBoxSendChoice[position];
adapter.notifyItemRangeChanged(position, 1);
}
});
class MyItem {
boolean isVisible = true;
}
class holder {
View layout_choice;
private void onBind(MyItem item) {
if (item.isVisible) {
layout_choice.setVisibility(View.VISIBLE);
} else {
layout_choice.setVisibility(View. GONE);
}
}
}
By notifying the adapter with notifyItemRangeChanged, the adapter will know what items have been update and therefore will only refresh them.
If you want i'll be happy to edit my answer with a working tested example. Hope this helps!
I want to perform a click on a Spinner element automatically after my activity has been loaded completely.
I use this code to set up the spinner and adapter:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_data);
getActionBar().setDisplayHomeAsUpEnabled(true);
mSpinnerDay = (Spinner) mTable.findViewById(R.id.spieltag_choice);
mAdapterSpinnerDay = new ArrayAdapter<CharSequence>(this, R.layout.custom_spinner);
mAdapterSpinnerDay.setDropDownViewResource(R.layout.custom_spinner);
mSpinnerDay.setAdapter(mAdapterSpinnerDay);
}
private void setUpSpinnerListener(final IGameData data) {
mSpinnerDay.post(new Runnable() {
public void run() {
mSpinnerDay.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
for (GameDayData d : data.getGameDay()) {
if (d.getName().equals(adapterView.getSelectedItem().toString())) {
TableRow row = (TableRow) mTable.findViewById(R.id.row_punkte_tag);
TextView t = (TextView) row.findViewById(R.id.punkte_tag);
t.setText("Punkte: " + d.getScore());
TableRow row2 = (TableRow) mTable.findViewById(R.id.row_position_tag);
TextView t2 = (TextView) row2.findViewById(R.id.position_tag);
t2.setText("Position: " + d.getPosition());
return;
}
}
}
public void onNothingSelected(AdapterView<?> adapterView) {
return;
}
});
}
});
}
public void onTeamCheckReadComplete(IGameData data) {
for (GameDayData d : data.getGameDay()) {
mAdapterSpinnerDay.add(d.getName());
}
}
I try to perform the click with following code after I have set the adapter to the spinner:
mSpinnerDay.setSelection(0, true);
View view = (View) mSpinnerDay.getChildAt(0);
long id = mSpinnerDay.getAdapter().getItemId(0);
mSpinnerDay.performItemClick(view, 0, id);
But this does not work. Could somebody tell me how I can perfom a click on a spinner element automatically? When I select the spinner item over touch event in the application everything works fine.
Regards,
Sandro
Corrected Solution
As I understand it, you have a spinner with items A, B, C, and D. You want item A pre-selected. You also want the user to be able to select A, B, C, and D and perform an action based on that.
In the onCreate() method:
mSpinner.setAdapter(myAdapter);
mSpinner.setOnItemSelectedListener(this); // have the activity implement
// OnItemSelectedListener interface
doAction(0);
Then implement the onItemSelected action like so:
#Override
void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
doAction(position);
}
You must implement the doAction(int position) method to handle how to update your activity based on the position of the item in your adapter. Clear?
Read more about this item and how to use it here:
http://developer.android.com/reference/android/widget/AdapterView.OnItemSelectedListener.html
I dont think you are calling setUpSpinnerListener from the code that you have posted. Even if you are calling it, i dont think it is advantageous to post a runnable to setuplistener.
Move all the code in runnable to just after setAdapter
I wrote a code for Spinner to bind array of USA States with Spinner in Android. But the problem is it shows reference type data in Spinner item, see the pic
I add android.R.layout.simple_spinner_dropdown_item but not know that what to add in layout. I checked many exemples on google and they add simple_spinner_dropdown_item but i could not find that what to add in layout. below is output and code. I want to show states in list instead of this junky data.
Spinner spStates = new Spinner(this);
spStates.setLayoutParams(new LayoutParams(screenWidth, LayoutParams.WRAP_CONTENT));
final USAStates states[] = new USAStates[51];
states[0] = new USAStates("Alabama", "AL");
states[1] = new USAStates("Alaska", "AK");
states[2] = new USAStates("Arizona", "AZ");
ArrayAdapter<USAStates> adapter = new ArrayAdapter<USAStates>(this, android.R.layout.simple_spinner_item, states);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spStates.setAdapter(adapter);
spStates.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
USAStates d = states[position];
Toast.makeText(getApplicationContext(), d.getStateAbrivation(), Toast.LENGTH_LONG).show();
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
public class USAStates {
private String _Statename;
private String _StateAbrivation;
public USAStates(String pStatename, String pStateAbrivation) {
Statename(pStatename);
StateAbrivation(pStateAbrivation);
}
public void Statename(String pStatename) {
_Statename = pStatename;
}
public void StateAbrivation(String pStateAbrivation) {
_StateAbrivation = pStateAbrivation;
}
public String getStatename() {
return _Statename;
}
public String getStateAbrivation() {
return _StateAbrivation;
}
}
Not sure, just doing this off the top of my head but, in your USAState class override your toString method.As is it maybe that the adapter is using the default toString() hence your weird text displaying (which I presume is the class name of the USAStates class)
for example
#Override
public String toString(){
return _Statename
}
I originally accepted the toString() answer but have since found that this doesn't appear to be right.
I had an ActionBar with spinner/drop down list and my adapter items were rendering with String.toString() value instead of of the title I set in the custom adapter. Adding toString() did fix initially until I tried to set a compound drawable in the same layout.
I needed to override getDropDownView as well as getViewin my adapter.
Having to override toString() is symptomatic that you haven't overridden the right methods in your adapter.
When overriding getDropDownView having to override toString() is no longer required and everything works as expected.
And the answer on the following post is a great way to implement by using the super method:
alternating colors of spinner items
OK, I've read around and see that Java only passes by value, not by reference so I don't know how to accomplish this.
I've 6 Spinners in an Android Activity that are populated with different SQLite queries.
The code to populate each Spinner and set the OnItemSelectedListener is very similiar so I was hoping to refactor to one method and call it 6 times with each Spinner ID and Sqlite query.
How do I get the Spinner onItemSelectedListener to change the right instance member on each different Spinner?
public void fillSpinner(String spinner_name, final String field_name) {
// This finds the Spinner ID passed into the method with spinner_name
// from the Resources file. e.g. spinner1
int resID = getResources().getIdentifier(spinner_name, "id",
getPackageName());
Spinner s = (Spinner) findViewById(resID);
final Cursor cMonth;
// This gets the data to populate the spinner, e.g. if field_name was
// strength = SELECT _id, strength FROM cigars GROUP BY strength
cMonth = dbHelper.fetchSpinnerFilters(field_name);
startManagingCursor(cMonth);
String[] from = new String[] { field_name };
int[] to = new int[] { android.R.id.text1 };
SimpleCursorAdapter months = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item, cMonth, from, to);
months.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s.setAdapter(months);
// This is setting the Spinner Item Selected Listener Callback, where
// all the problems happen
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
// This is the problem area.
object_reference_to_clas_member_of_field_name = theCursor
.getString(theCursor.getColumnIndex(field_name));
}
public void onNothingSelected(AdapterView<?> parent) {
// showToast("Spinner1: unselected");
}
});
}
You call this method like this fillSpinner("spinner1","strength");.
It finds the spinner with id spinner1 and queries the database for the strength field. field_name, which is strength in this example had to be declared a final variable to be used in the onItemSelectedListener or I'd get the error Cannot refer to a non-final variable field_name inside an inner class defined in a different method.
But how do I get the onItemSelectedListener to change the value of a different instance member when each different Spinner is used? This is the all important line of code:
object_reference_to_clas_member_of_field_name = theCursor .getString(theCursor.getColumnIndex(field_name));
I can't use a final String as the variable will obviously change when the user selects a different value. I've read around a good bit and am stumped to a solution. I can just copy and paste this code 6 times and forget about refactoring but I'd really like to know the elegant solution. Post a comment if you don't understand my question, I'm not sure if I explaned myself well.
You can do it, by passing additional class as parameter of fillSpinner method:
A. Create interface
public interface OnSpinnerValueSelected {
void onValueSelected(String selectedValue);
}
B. Change your method a bit:
public void fillSpinner(String spinner_name, final String field_name,
final OnSpinnerValueSelected valueChangeListener) {
// Prepare spinner
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
valueChangeListener.onValueSelected(theCursor
.getString(theCursor.getColumnIndex(field_name)));
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
C. provide listener:
fillSpinner("spinner1","strength", new OnSpinnerValueSelected() {
public void onValueSelected(String selectedValue) {
yourObject.setField(selectedValue);
}
});
Refactor your listener to a new "class". Initialize with the right arguments/instances as required so that the repeated "code" is reusuable.
Right, this is how I managed it but I'm still open to new suggestions for an accepted answer and I also created a bounty.
I didn't create a new class like panzerschreck suggested so I'm posting this as a new answer to my own question. Bit of a hack but I just created an if..then..else statement in the listener to check what spinner was selected and then set a different instance member.
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
if (field_name.equalsIgnoreCase("strength")) {
strength=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("ring")) {
ring_gauge=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("country")) {
country=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("wrapper")) {
wrapper=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("length")) {
length=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("price")) {
price=theCursor.getString(theCursor.getColumnIndex(field_name));
}
// showToast(category);
}
public void onNothingSelected(AdapterView<?> parent) {
// showToast("Spinner2: unselected");
}
});
Here are the class members
private String strength,ring_gauge,country,wrapper,length,price;
Bit of hack but without Java allowing objects to be really passed by reference, it's all I could do.