The goal of the sample application is to display items from a SQLite database, but hide the second text view if the database record has a hide flag active (otherwise display the second text view).
The problem is that it doesn't hide the right things. And as scroll actions cause items to go out of view, and back into view, the second text view gets hidden and and shown on various list items in an erratic manner.
The hidden flag has been set on items 5, 10, 15, 20, and here is how it comes-up:
Scrolling down, various other strange items are hidden, and it doesn't seem to be the same each time. Entry 14, Entry 16, are hidden, for instance.
After Scrolling to the top, we see the first set of items no longer has the same hidden second lines.
Then a whole new set of entries are hidden scrolling back and forth. Not quite random, but inexplicable. You've got to see it to believe it.
The 'real' application that this sample is based-upon (not shown here) actually is attempting to show and hide an ImageView, but the same kind of problem surrounds hiding a TextView, so that's what's I've shown here.
Below is the application. Everything you need should be included (including sample data), should you wish to run this crazy thing. Or you can find it on github: https://github.com/sengsational/LvCaApp
LvCaActivity.java
:
public class LvCaActivity extends AppCompatActivity {
private SimpleCursorAdapter dataAdapter;
private DbAdapter dbHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lv_ca);
dbHelper = new DbAdapter(this);
dbHelper.open();
dbHelper.deleteAll();
dbHelper.insertSome();
Cursor bCursor = dbHelper.fetchAll(DbAdapter.bColumns);
dataAdapter = new MySimpleCursorAdapter(
this, R.layout.b_item,
bCursor,
DbAdapter.bColumns,
ViewHolder.viewsArray,
0);
ListView listView = (ListView) findViewById(R.id.listView1);
listView.setAdapter(dataAdapter);
}
}
activity_lv_ca.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<ListView android:id="@+id/listView1" android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
b_item.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="6dip"
android:id="@+id/b_item_layout">
<TextView
android:id="@+id/bName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="end"
android:singleLine="true"
android:paddingTop="30dp"/>
<TextView
android:id="@+id/bSecondLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/bName"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/bDbItem"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="gone"
/>
<TextView
android:id="@+id/bHidden"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="gone"
/>
</RelativeLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.company.cpp.lvcaapp"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LvCaActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
DbAdapter.java
:
public class DbAdapter {
private static final String TAG = "DbAdapter";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
private static final String DATABASE_NAME = "adbname";
private static final String SQLITE_TABLE = "atablename";
private static final int DATABASE_VERSION = 1;
private final Context mCtx;
public static final String[] bColumns = new String[] {
"_id",
"NAME",
"SECOND_LINE",
"HIDDEN",
};
private static final String DATABASE_CREATE =
"CREATE TABLE if not exists " + SQLITE_TABLE + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"NAME TEXT, " +
"SECOND_LINE, " +
"HIDDEN" +
");";
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.w(TAG, DATABASE_CREATE);
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE);
onCreate(db);
}
}
public DbAdapter(Context ctx) {
this.mCtx = ctx;
}
public DbAdapter open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close() {
if (mDbHelper != null) {
mDbHelper.close();
}
}
public Cursor fetchAll(String[] fields) {
Cursor mCursor = mDb.query(SQLITE_TABLE, fields, null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
public void insertSome() {
AListItem.getInstance();
String sampleData = "[{\"name\":\"Entry 1\",\"second_line\":\"Second Line 1\",\"hidden\":\"F\"},{\"name\":\"Entry 2\",\"second_line\":\"Second Line 2\",\"hidden\":\"F\"},{\"name\":\"Entry 3\",\"second_line\":\"Second Line 3\",\"hidden\":\"F\"},{\"name\":\"Entry 4\",\"second_line\":\"Second Line 4\",\"hidden\":\"F\"},{\"name\":\"EntryH 5\",\"second_line\":\"Second Line 5\",\"hidden\":\"T\"},{\"name\":\"Entry 6\",\"second_line\":\"Second Line 6\",\"hidden\":\"F\"},{\"name\":\"Entry 7\",\"second_line\":\"Second Line 7\",\"hidden\":\"F\"},{\"name\":\"Entry 8\",\"second_line\":\"Second Line 8\",\"hidden\":\"F\"},{\"name\":\"Entry 9\",\"second_line\":\"Second Line 9\",\"hidden\":\"F\"},{\"name\":\"EntryH 10\",\"second_line\":\"Second Line 10\",\"hidden\":\"T\"},{\"name\":\"Entry 11\",\"second_line\":\"Second Line 11\",\"hidden\":\"F\"},{\"name\":\"Entry 12\",\"second_line\":\"Second Line 12\",\"hidden\":\"F\"},{\"name\":\"Entry 13\",\"second_line\":\"Second Line 13\",\"hidden\":\"F\"},{\"name\":\"Entry 14\",\"second_line\":\"Second Line 14\",\"hidden\":\"F\"},{\"name\":\"EntryH 15\",\"second_line\":\"Second Line 15\",\"hidden\":\"T\"},{\"name\":\"Entry 16\",\"second_line\":\"Second Line 16\",\"hidden\":\"F\"},{\"name\":\"Entry 17\",\"second_line\":\"Second Line 17\",\"hidden\":\"F\"},{\"name\":\"Entry 18\",\"second_line\":\"Second Line 18\",\"hidden\":\"F\"},{\"name\":\"Entry 19\",\"second_line\":\"Second Line 19\",\"hidden\":\"F\"},{\"name\":\"EntryH 20\",\"second_line\":\"Second Line 20\",\"hidden\":\"T\"},{\"name\":\"Entry 21\",\"second_line\":\"Second Line 21\",\"hidden\":\"F\"},{\"name\":\"Entry 22\",\"second_line\":\"Second Line 22\",\"hidden\":\"F\"},{\"name\":\"Entry 23\",\"second_line\":\"Second Line 23\",\"hidden\":\"F\"}]";
String[] items = sampleData.split("\\},\\{");
for(String item: items){
AListItem.clear();
AListItem.load(item);
if(AListItem.getName().contains("Hide")){
AListItem.setHidden("T");
}
mDb.insert(SQLITE_TABLE, null, AListItem.getContentValues());
ContentValues values = AListItem.getContentValues();
Log.v(TAG, "values.toString()" + values.toString());
}
}
public boolean deleteAll() {
int doneDelete = 0;
doneDelete = mDb.delete(SQLITE_TABLE, null , null);
Log.w(TAG, Integer.toString(doneDelete));
return doneDelete > 0;
}
}
AListItem.java
:
public class AListItem {
static String rawInputString;
static String name;
static String second_line;
static String hidden;
static AListItem aListItem;
private AListItem() {
}
public static AListItem getInstance(){
if (aListItem == null) {
aListItem = new AListItem();
}
return aListItem;
}
public static void clear() {
rawInputString = null;
name = null;
second_line = null;
hidden = null;
}
public static ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("NAME", name);
values.put("SECOND_LINE", second_line);
values.put("HIDDEN", hidden);
return values;
}
public static void load(String string) {
StringBuffer buf = new StringBuffer(string);
if (buf.substring(0,2).equals("[{")){
buf.delete(0,2);
}
rawInputString = buf.toString();
parse();
}
public static void parse() {
if (rawInputString == null) {
System.out.println("nothing to parse");
return;
}
rawInputString = rawInputString.replaceAll("\"\\:null,", "\"\\:\"null\",");
String[] nvpa = rawInputString.split("\",\"");
for (String nvpString : nvpa) {
String[] nvpItem = nvpString.split("\":\"");
if (nvpItem.length < 2) continue;
String identifier = nvpItem[0].replaceAll("\"", "");
String content = nvpItem[1].replaceAll("\"", "");
switch (identifier) {
case "name":
setName(content);
break;
case "second_line":
setSecond_line(content);
break;
case "hidden":
setHidden(content);
break;
default:
System.out.println("nowhere to put [" + nvpItem[0] + "] " + nvpString + " raw: " + rawInputString);
break;
}
}
}
public static String getName() {
return name;
}
public static void setName(String name) { AListItem.name = name; }
public static void setSecond_line(String second_line) {
AListItem.second_line = second_line;
}
public static String getSecond_line() {
return second_line;
}
public static void setHidden(String hidden) {
AListItem.hidden = hidden;
}
public static String getHidden() {
return hidden;
}
public String toString() {
return getName() + ", " +
getSecond_line() + ", " +
getHidden();
}
}
MySimpleCursorAdapter.java
:
public class MySimpleCursorAdapter extends SimpleCursorAdapter {
Context context;
Cursor cursor;
public static final String TAG = "MySimpleCursorAdapter";
public MySimpleCursorAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int flags) {
super(context, layout, cursor, from, to, flags);
this.context = context;
this.cursor = cursor;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.v(TAG,"getView() >>>>>>STARTING");
ViewHolder viewHolder;
LayoutInflater inflater = LayoutInflater.from(context);
if (null == convertView || null == convertView.getTag()) {
convertView = inflater.inflate(R.layout.b_item, null);
viewHolder = new ViewHolder(convertView);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
for (int i = 0; i < cursor.getColumnCount(); i++) {
Log.v(TAG, "getView cursor " + i + ": " + cursor.getString(i));
}
String hidden = cursor.getString(ViewHolder.HIDDEN);
if (hidden == null) hidden = "F";
Log.v(TAG,"Hidden State: " + hidden);
switch (hidden) {
case "F":
viewHolder.showSecondLine(); // DRS 20160827 - Added line suggested by aiwiguna
break;
case "T":
Log.v(TAG,">>>>>Hidden was TRUE<<<<<<<: " + cursor.getString(ViewHolder.NAME));
viewHolder.hideSecondLine();
break;
}
convertView.setTag(viewHolder);
View returnView = super.getView(position, convertView, parent);
Log.v(TAG,"getView() ENDING<<<<<<<<<");
return returnView;
}
}
ViewHolder.java
:
class ViewHolder {
public static final String TAG = "ViewHolder";
public static final int DB_ITEM = 0;
public static final int NAME = 1;
public static final int SECOND_LINE = 2;
public static final int HIDDEN = 3;
public static final int[] viewsArray = new int[] {
R.id.bDbItem,
R.id.bName,
R.id.bSecondLine,
R.id.bHidden,
};
public static final TextView[] textViewArray = new TextView[viewsArray.length];
public ViewHolder( final View root ) {
Log.v(TAG, "ViewHolder constructor");
for (int i = 0; i < viewsArray.length; i++) {
textViewArray[i] = (TextView) root.findViewById(viewsArray[i]);
Log.v(TAG, " textViewArray[" + i + "]: " + textViewArray[i]);
}
}
public void hideSecondLine() {
textViewArray[SECOND_LINE].setVisibility(View.INVISIBLE);
}
//DRS 20160827 - Addition recommended by aiwiguna
public void showSecondLine() {
textViewArray[SECOND_LINE].setVisibility(View.VISIBLE);
}
}
In order to get this application working,
ListView
must be replaced by a RecyclerView
, plus SimpleCursorAdapter
implementation needs to be replaced by a RecyclerCursorAdapter
implementation.MyRecyclerCursorAdapter
, a new class in the example, extends RecyclerView.Adapter
:
//DRS 20160829 - Added class. Replaces MySimpleCursorAdapter
public class MyRecyclerCursorAdapter extends RecyclerView.Adapter{
private Cursor cursor;
private Context context;
private static final String TAG = MyRecyclerCursorAdapter.class.getSimpleName();
public MyRecyclerCursorAdapter(Context context, Cursor cursor) {
this.cursor = cursor;
this.context = context;
}
//DRS 20160829 - Critical method within new class
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.v(TAG, "onCreateViewHolder ");
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View itemView = inflater.inflate(R.layout.b_item, parent, false);
ViewHolder viewHolder = new ViewHolder(itemView, cursor);
return viewHolder;
}
//DRS 20160829 - Critical method within new class
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
cursor.moveToPosition(position);
((ViewHolder)holder).bindFields(cursor);
}
@Override
public int getItemCount() {
return cursor.getCount();
}
}
Note that this class carries a Cursor
object, which is the link to the SQLite database entries that will be populating the list.
Also, in order to gain access to the Recycler View, a dependency must be added to build.gradle
:
// DRS 20160829 - Added recyclerview
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
}
b_item.xml
required no changes.
activity_lv_ca.xml
required a RecyclerView
in place of the old ListView
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<!-- DRS 20160829 - Commented ListView, Added RecyclerView
<ListView android:id="@+id/listView1" android:layout_width="fill_parent"
android:layout_height="fill_parent" / -->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</LinearLayout>
The ViewHolder
class now extends Recycler.ViewHolder
. Beyond the standard ViewHolder
implementation, this customized ViewHolder
also has a Cursor
which is used to set the text for the TextViews
that appear on each row of the list. And this is where the visibility is managed (in a method I called bindFields()
:
public class ViewHolder extends RecyclerView.ViewHolder {
public static final String TAG = "ViewHolder";
private final Cursor cursor;
public TextView bDbItem;
public TextView bName;
public TextView bSecondLine;
public TextView bHidden;
public static final int DB_ITEM = 0;
public static final int NAME = 1;
public static final int SECOND_LINE = 2;
public static final int HIDDEN = 3;
public ViewHolder(View root, Cursor cursor ) {
super(root);
this.cursor = cursor;
Log.v(TAG, "ViewHolder constructor");
bDbItem = (TextView) itemView.findViewById(R.id.bDbItem);
bName = (TextView) itemView.findViewById(R.id.bName);
bSecondLine = (TextView) itemView.findViewById(R.id.bSecondLine);
bHidden = (TextView) itemView.findViewById(R.id.bHidden);
}
public void bindFields(Cursor cursor) {
bDbItem.setText("" + cursor.getInt(DB_ITEM));
bName.setText(cursor.getString(NAME));
bSecondLine.setText(cursor.getString(SECOND_LINE));
String hidden = cursor.getString(HIDDEN);
bHidden.setText(hidden);
if ("F".equals(hidden)) {
bSecondLine.setVisibility(View.VISIBLE);
} else {
bSecondLine.setVisibility(View.INVISIBLE);
}
}
}
AListItem.java
required no changes.
DBAdapter.java
required no changes.
The working application may be found on github: RecyclerViewSqlite