Spinner setOnItemSelectedListener does not work properly with Room DB calls
The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.
This issue can be summed up as follows:
- the Spinner is somehow retaining the previous value from itself and
setting it - the Spinner's OnItemClicked function is called whenever it's bound
to the Spinner, which has to be a bug within Android's Spinner
object, because this should not happen and is not how the Spinner is
explained in the Android docs
(https://developer.android.com/guide/topics/ui/controls/spinner)
For example, I have this list of types:
- Type "X"
- Type "Y"
- Type "Z"
Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.
The issue (I think) here is setOnItemSelectedListener
is called whenever the user scrolls, and makes changes according to the currently-selected item.
I tried many different solutions; most of them are included here.
But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.
Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.
public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {
private static final String TAG = "TypeFragment";
private TypeAdapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_type_list, container, false);
SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
refreshLayout.setOnRefreshListener(() -> {
if(refreshLayout.isRefreshing()){
refreshLayout.setRefreshing(false);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();
ArrayList<String> typeOptions = new ArrayList<>();
typeOptions.add(getString(R.string.type_x));
typeOptions.add(getString(R.string.type_y));
typeOptions.add(getString(R.string.type_z));
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
R.layout.support_simple_spinner_dropdown_item, typeOptions);
adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
getListView().setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
class TypeAdapter extends BaseAdapter {
List<dbType> typesList;
ArrayList<String> typeOptions;
ArrayAdapter<String> arrayAdapter;
public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
this.typesList = typesList;
this.typeOptions = typeOptions;
this.arrayAdapter = arrayAdapter;
}
public int getCount() {
if (this.typesList != null) {
return this.typesList.size();
}
return 0;
}
public Object getItem(int position) {
return this.typesList.get(position);
}
public long getItemId(int position) {
return this.typesList.get(position).id;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
}
TextView typeTV = convertView.findViewById(R.id.type_tv_label);
Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);
typeSpinner.setAdapter(this.arrayAdapter);
dbType currentType = this.typesList.get(position);
// here I ideally would set the Type to the one currently in the DB
//typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));
// most popular solution from StackOverflow questions:
typeSpinner.setSelected(false);
typeSpinner.setSelection(Adapter.NO_SELECTION, true);
// set a spinner with its' listener on each row in the list
typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
int item = typeSpinner.getSelectedItemPosition();
Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
}
@Override
public void onNothingSelected(AdapterView<?> parent1) {}
}));
return convertView;
}
}
}
The database files:
dbType:
@Entity(tableName="types")
public class dbType {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public int id;
@ColumnInfo(name = "type")
public String type;
TypeDao:
@Dao
public interface TypeDao {
@Query("SELECT * FROM types;")
List<dbType> getTypes();
@Query("SELECT type FROM types WHERE id = :typeID;")
String getType(int typeID);
@Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
void updateType(String typeStr, int typeID);
ExampleDatabase excluding Migrations for brevity:
@Database(entities = {dbType.class})
@TypeConverters({dbTypeConverters.class})
public abstract class ExampleDatabase extends RoomDatabase {
public abstract TypeDao getTypeDao()
private static ExampleDatabase INSTANCE;
public static CoriolisDatabase getCoriolisDatabase() {
return INSTANCE;
}
types_row_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/type_tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_gravity="center_vertical"
android:text="@string/runs_list_runvolerror_column"
app:layout_constraintStart_toEndOf="@id/netvolume_column"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>
<android.support.constraint.Barrier
android:id="@+id/barrier_run_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>
<Spinner
android:id="@+id/type_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />
</android.support.constraint.ConstraintLayout>
Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:
// before update check:
Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
// update happens
Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
This logging revealed that the type was/is changed when scrolling, and the database update stuck.
Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type X
Again, after tapping and changing the value to Type Y:
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type Y
Looks like it updated just fine...
Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):
DB type before updating: type ID is 57, type is Type Y
DB type after updating: type ID is 57, type is Type X
android database android-spinner android-room
add a comment |
The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.
This issue can be summed up as follows:
- the Spinner is somehow retaining the previous value from itself and
setting it - the Spinner's OnItemClicked function is called whenever it's bound
to the Spinner, which has to be a bug within Android's Spinner
object, because this should not happen and is not how the Spinner is
explained in the Android docs
(https://developer.android.com/guide/topics/ui/controls/spinner)
For example, I have this list of types:
- Type "X"
- Type "Y"
- Type "Z"
Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.
The issue (I think) here is setOnItemSelectedListener
is called whenever the user scrolls, and makes changes according to the currently-selected item.
I tried many different solutions; most of them are included here.
But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.
Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.
public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {
private static final String TAG = "TypeFragment";
private TypeAdapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_type_list, container, false);
SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
refreshLayout.setOnRefreshListener(() -> {
if(refreshLayout.isRefreshing()){
refreshLayout.setRefreshing(false);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();
ArrayList<String> typeOptions = new ArrayList<>();
typeOptions.add(getString(R.string.type_x));
typeOptions.add(getString(R.string.type_y));
typeOptions.add(getString(R.string.type_z));
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
R.layout.support_simple_spinner_dropdown_item, typeOptions);
adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
getListView().setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
class TypeAdapter extends BaseAdapter {
List<dbType> typesList;
ArrayList<String> typeOptions;
ArrayAdapter<String> arrayAdapter;
public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
this.typesList = typesList;
this.typeOptions = typeOptions;
this.arrayAdapter = arrayAdapter;
}
public int getCount() {
if (this.typesList != null) {
return this.typesList.size();
}
return 0;
}
public Object getItem(int position) {
return this.typesList.get(position);
}
public long getItemId(int position) {
return this.typesList.get(position).id;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
}
TextView typeTV = convertView.findViewById(R.id.type_tv_label);
Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);
typeSpinner.setAdapter(this.arrayAdapter);
dbType currentType = this.typesList.get(position);
// here I ideally would set the Type to the one currently in the DB
//typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));
// most popular solution from StackOverflow questions:
typeSpinner.setSelected(false);
typeSpinner.setSelection(Adapter.NO_SELECTION, true);
// set a spinner with its' listener on each row in the list
typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
int item = typeSpinner.getSelectedItemPosition();
Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
}
@Override
public void onNothingSelected(AdapterView<?> parent1) {}
}));
return convertView;
}
}
}
The database files:
dbType:
@Entity(tableName="types")
public class dbType {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public int id;
@ColumnInfo(name = "type")
public String type;
TypeDao:
@Dao
public interface TypeDao {
@Query("SELECT * FROM types;")
List<dbType> getTypes();
@Query("SELECT type FROM types WHERE id = :typeID;")
String getType(int typeID);
@Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
void updateType(String typeStr, int typeID);
ExampleDatabase excluding Migrations for brevity:
@Database(entities = {dbType.class})
@TypeConverters({dbTypeConverters.class})
public abstract class ExampleDatabase extends RoomDatabase {
public abstract TypeDao getTypeDao()
private static ExampleDatabase INSTANCE;
public static CoriolisDatabase getCoriolisDatabase() {
return INSTANCE;
}
types_row_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/type_tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_gravity="center_vertical"
android:text="@string/runs_list_runvolerror_column"
app:layout_constraintStart_toEndOf="@id/netvolume_column"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>
<android.support.constraint.Barrier
android:id="@+id/barrier_run_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>
<Spinner
android:id="@+id/type_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />
</android.support.constraint.ConstraintLayout>
Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:
// before update check:
Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
// update happens
Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
This logging revealed that the type was/is changed when scrolling, and the database update stuck.
Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type X
Again, after tapping and changing the value to Type Y:
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type Y
Looks like it updated just fine...
Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):
DB type before updating: type ID is 57, type is Type Y
DB type after updating: type ID is 57, type is Type X
android database android-spinner android-room
add a comment |
The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.
This issue can be summed up as follows:
- the Spinner is somehow retaining the previous value from itself and
setting it - the Spinner's OnItemClicked function is called whenever it's bound
to the Spinner, which has to be a bug within Android's Spinner
object, because this should not happen and is not how the Spinner is
explained in the Android docs
(https://developer.android.com/guide/topics/ui/controls/spinner)
For example, I have this list of types:
- Type "X"
- Type "Y"
- Type "Z"
Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.
The issue (I think) here is setOnItemSelectedListener
is called whenever the user scrolls, and makes changes according to the currently-selected item.
I tried many different solutions; most of them are included here.
But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.
Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.
public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {
private static final String TAG = "TypeFragment";
private TypeAdapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_type_list, container, false);
SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
refreshLayout.setOnRefreshListener(() -> {
if(refreshLayout.isRefreshing()){
refreshLayout.setRefreshing(false);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();
ArrayList<String> typeOptions = new ArrayList<>();
typeOptions.add(getString(R.string.type_x));
typeOptions.add(getString(R.string.type_y));
typeOptions.add(getString(R.string.type_z));
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
R.layout.support_simple_spinner_dropdown_item, typeOptions);
adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
getListView().setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
class TypeAdapter extends BaseAdapter {
List<dbType> typesList;
ArrayList<String> typeOptions;
ArrayAdapter<String> arrayAdapter;
public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
this.typesList = typesList;
this.typeOptions = typeOptions;
this.arrayAdapter = arrayAdapter;
}
public int getCount() {
if (this.typesList != null) {
return this.typesList.size();
}
return 0;
}
public Object getItem(int position) {
return this.typesList.get(position);
}
public long getItemId(int position) {
return this.typesList.get(position).id;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
}
TextView typeTV = convertView.findViewById(R.id.type_tv_label);
Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);
typeSpinner.setAdapter(this.arrayAdapter);
dbType currentType = this.typesList.get(position);
// here I ideally would set the Type to the one currently in the DB
//typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));
// most popular solution from StackOverflow questions:
typeSpinner.setSelected(false);
typeSpinner.setSelection(Adapter.NO_SELECTION, true);
// set a spinner with its' listener on each row in the list
typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
int item = typeSpinner.getSelectedItemPosition();
Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
}
@Override
public void onNothingSelected(AdapterView<?> parent1) {}
}));
return convertView;
}
}
}
The database files:
dbType:
@Entity(tableName="types")
public class dbType {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public int id;
@ColumnInfo(name = "type")
public String type;
TypeDao:
@Dao
public interface TypeDao {
@Query("SELECT * FROM types;")
List<dbType> getTypes();
@Query("SELECT type FROM types WHERE id = :typeID;")
String getType(int typeID);
@Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
void updateType(String typeStr, int typeID);
ExampleDatabase excluding Migrations for brevity:
@Database(entities = {dbType.class})
@TypeConverters({dbTypeConverters.class})
public abstract class ExampleDatabase extends RoomDatabase {
public abstract TypeDao getTypeDao()
private static ExampleDatabase INSTANCE;
public static CoriolisDatabase getCoriolisDatabase() {
return INSTANCE;
}
types_row_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/type_tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_gravity="center_vertical"
android:text="@string/runs_list_runvolerror_column"
app:layout_constraintStart_toEndOf="@id/netvolume_column"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>
<android.support.constraint.Barrier
android:id="@+id/barrier_run_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>
<Spinner
android:id="@+id/type_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />
</android.support.constraint.ConstraintLayout>
Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:
// before update check:
Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
// update happens
Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
This logging revealed that the type was/is changed when scrolling, and the database update stuck.
Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type X
Again, after tapping and changing the value to Type Y:
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type Y
Looks like it updated just fine...
Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):
DB type before updating: type ID is 57, type is Type Y
DB type after updating: type ID is 57, type is Type X
android database android-spinner android-room
The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.
This issue can be summed up as follows:
- the Spinner is somehow retaining the previous value from itself and
setting it - the Spinner's OnItemClicked function is called whenever it's bound
to the Spinner, which has to be a bug within Android's Spinner
object, because this should not happen and is not how the Spinner is
explained in the Android docs
(https://developer.android.com/guide/topics/ui/controls/spinner)
For example, I have this list of types:
- Type "X"
- Type "Y"
- Type "Z"
Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.
The issue (I think) here is setOnItemSelectedListener
is called whenever the user scrolls, and makes changes according to the currently-selected item.
I tried many different solutions; most of them are included here.
But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.
Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.
public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {
private static final String TAG = "TypeFragment";
private TypeAdapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_type_list, container, false);
SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
refreshLayout.setOnRefreshListener(() -> {
if(refreshLayout.isRefreshing()){
refreshLayout.setRefreshing(false);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();
ArrayList<String> typeOptions = new ArrayList<>();
typeOptions.add(getString(R.string.type_x));
typeOptions.add(getString(R.string.type_y));
typeOptions.add(getString(R.string.type_z));
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
R.layout.support_simple_spinner_dropdown_item, typeOptions);
adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
getListView().setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
class TypeAdapter extends BaseAdapter {
List<dbType> typesList;
ArrayList<String> typeOptions;
ArrayAdapter<String> arrayAdapter;
public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
this.typesList = typesList;
this.typeOptions = typeOptions;
this.arrayAdapter = arrayAdapter;
}
public int getCount() {
if (this.typesList != null) {
return this.typesList.size();
}
return 0;
}
public Object getItem(int position) {
return this.typesList.get(position);
}
public long getItemId(int position) {
return this.typesList.get(position).id;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
}
TextView typeTV = convertView.findViewById(R.id.type_tv_label);
Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);
typeSpinner.setAdapter(this.arrayAdapter);
dbType currentType = this.typesList.get(position);
// here I ideally would set the Type to the one currently in the DB
//typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));
// most popular solution from StackOverflow questions:
typeSpinner.setSelected(false);
typeSpinner.setSelection(Adapter.NO_SELECTION, true);
// set a spinner with its' listener on each row in the list
typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
int item = typeSpinner.getSelectedItemPosition();
Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
}
@Override
public void onNothingSelected(AdapterView<?> parent1) {}
}));
return convertView;
}
}
}
The database files:
dbType:
@Entity(tableName="types")
public class dbType {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public int id;
@ColumnInfo(name = "type")
public String type;
TypeDao:
@Dao
public interface TypeDao {
@Query("SELECT * FROM types;")
List<dbType> getTypes();
@Query("SELECT type FROM types WHERE id = :typeID;")
String getType(int typeID);
@Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
void updateType(String typeStr, int typeID);
ExampleDatabase excluding Migrations for brevity:
@Database(entities = {dbType.class})
@TypeConverters({dbTypeConverters.class})
public abstract class ExampleDatabase extends RoomDatabase {
public abstract TypeDao getTypeDao()
private static ExampleDatabase INSTANCE;
public static CoriolisDatabase getCoriolisDatabase() {
return INSTANCE;
}
types_row_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/type_tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_gravity="center_vertical"
android:text="@string/runs_list_runvolerror_column"
app:layout_constraintStart_toEndOf="@id/netvolume_column"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>
<android.support.constraint.Barrier
android:id="@+id/barrier_run_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>
<Spinner
android:id="@+id/type_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />
</android.support.constraint.ConstraintLayout>
Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:
// before update check:
Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
// update happens
Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
This logging revealed that the type was/is changed when scrolling, and the database update stuck.
Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type X
Again, after tapping and changing the value to Type Y:
DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type Y
Looks like it updated just fine...
Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):
DB type before updating: type ID is 57, type is Type Y
DB type after updating: type ID is 57, type is Type X
android database android-spinner android-room
android database android-spinner android-room
edited Jan 2 at 18:17
Fantômas
32.8k156490
32.8k156490
asked Jan 2 at 16:37
star_tracstar_trac
63114
63114
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009963%2fspinner-setonitemselectedlistener-does-not-work-properly-with-room-db-calls%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009963%2fspinner-setonitemselectedlistener-does-not-work-properly-with-room-db-calls%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown