Search code examples
androidsqliteandroid-fragmentscursors

Display appropriate SQLite data in fragment (same entry is appearing for different chosen entry)


Okay what I'm trying to do is have a search query in my app, that will allow the user after finding the correct grocery item result to add it to another data structure (eventually being a recipe). The adding component I have not worked on yet...

Right now I have chosen Button btnaddToRecipe to start a new activity AddGroceryItemActivity after being clicked by the user (See CustomAdapter activity for that code).

This new activity will open its fragment, where some calculations will be done, and when I get around to it a new data structure will be created/modified to allow the user to add that grocery item to the data structure with the required amount of item.

The data regarding the name of the grocery item, as well as the Grams-Cups and Cups-Grams conversion ratio (stored in my database) need to be passed to this new fragment in order to do the calculations (depending on what the user will choose to enter in).

(You can see the database columns in my search query code)

The problem:

When I go to select any of the search results, hit the btnaddToRecipe I get the same search result open up in a new activity (AddGroceryItemActivity, which hosts AddGroceryItemFragment) with one of my database entries - which incorrectly matches up with the one I chose. It is the same incorrect entry EVERY option I choose.

So I think this has something do with the way my cursor is cycling through the options in AddGroceryItemFragment.

Or it's because I'm not passing the correct data about which search result the user chose, to the new activity.

The fragment class to be displayed:

public class AddGroceryItemFragment extends Fragment {
private static final String TAG = "AddGroceryItemFragment";

private OnSaveClicked mSaveListener;
private AppDatabase dbHelper;

interface OnSaveClicked {
    void onSaveClicked();
}


@Override
public void onAttach(Context context) {
    super.onAttach(context);
    Log.d(TAG, "onAttach: starts");

    dbHelper = AppDatabase.getInstance(context);

    Activity activity = getActivity();
    if (!(activity instanceof OnSaveClicked)) {
        throw new ClassCastException(activity.getClass().getSimpleName()
                + " must implement AddGrocerItemFragment.OnSaveClicked interface");
    }

    mSaveListener = (OnSaveClicked) activity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    Log.d(TAG, "onCreateView: starts");
    View view = inflater.inflate(R.layout.fragment_addgroceryitem, container, false);
    final ViewHolder holder = new ViewHolder();
    holder.mCalculate = (Button) view.findViewById(R.id.calculate);
    holder.mAddToList = (Button) view.findViewById(R.id.addToList);
    holder.mCupsToGrams = (TextView) view.findViewById(R.id.CupsToG);
    holder.mGramsToCups = (TextView) view.findViewById(R.id.gToCups);
    holder.name = (TextView) view.findViewById(R.id.name);
    holder.mQuantity = (EditText) view.findViewById(R.id.entry);

    SQLiteDatabase db = dbHelper.getReadableDatabase();
    String selectQuery = "SELECT rowid as " +
            GroceriesContract.Columns._ID + ", " +
            GroceriesContract.Columns.GROCERIES_NAME + ", " +
            GroceriesContract.Columns.GROCERIES_DESCRIPTION + ", " +
            GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS + ", " +
            GroceriesContract.Columns.GROCERIES_CUPSTOGRAMS +
            " FROM " + GroceriesContract.TABLE_NAME;
    Cursor cursor = db.rawQuery(selectQuery, null);


    if (cursor.moveToFirst()) {
        do {
            holder.name.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_NAME)));
            holder.mGramsToCups.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS)));
            holder.mCupsToGrams.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS)));
        } while (cursor.moveToNext());
    }
        cursor.close();

 return view;
}


static class ViewHolder {
    TextView name;
    EditText mQuantity;
    Button mCalculate;
    Button mAddToList;
    TextView mGramsToCups;
    TextView mCupsToGrams;
}

The fragment's actitvity:

public class AddEditRecipeActivity extends AppCompatActivity implements AddEditRecipeActivityFragment.OnSaveClicked {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add_edit_recipe);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    AddEditRecipeActivityFragment fragment = new 
AddEditRecipeActivityFragment();
    Bundle arguments = getIntent().getExtras();
    fragment.setArguments(arguments);

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = 
fragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.fragment, fragment);
    fragmentTransaction.commit();
}

@Override
public void onSaveClicked() {
    finish();
}

}

You can see the button that starts the activity - does the cursor information from this need to be pulled out?

public class SearchActivity extends AppCompatActivity {
   private static final String TAG = "SearchActivity";

private Cursor mCursor;
private CustomAdapter mCustomAdapter;
private GroceriesContract mGroceriesContract;
ListView mListView;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_search);


    mGroceriesContract = new GroceriesContract(this);
    mCursor = mGroceriesContract.getGroceryList();
    mCustomAdapter = new CustomAdapter(SearchActivity.this, mCursor, 0);
    mListView = (ListView) findViewById(R.id.lstStudent);
    mListView.setAdapter(mCustomAdapter);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    getMenuInflater().inflate(R.menu.options_menu, menu);
    SearchManager manager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView search = (SearchView) menu.findItem(R.id.search).getActionView();
    search.setSearchableInfo(manager.getSearchableInfo(getComponentName()));

    getSupportActionBar().setDisplayShowTitleEnabled(false);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);


    search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            Log.d(TAG, "onQueryTextSubmit: on");
            mCursor = mGroceriesContract.getGroceryName(query);
            if (mCursor == null) {
                Toast.makeText(SearchActivity.this, "No records found", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SearchActivity.this, mCursor.getCount() + " records found!", Toast.LENGTH_SHORT).show();
            }

            mCustomAdapter.swapCursor(mCursor);

            return false;

        }

        @Override
        public boolean onQueryTextChange(String newText) {
            Log.d(TAG, "onQueryTextChange: ");
            mCursor = mGroceriesContract.getGroceryName(newText);
            if (mCursor != null) {
                mCustomAdapter.swapCursor(mCursor);
            }
            return false;
        }
    });


    return true;

}

@Override
protected void onResume() {
    super.onResume();
 }
}

I'm wondering if this problem is actually the result of not passing data from the activity that started this the activity that then leads to starting AddGroceryItemFragment

This initial class extends CursorAdapter:

public class CustomAdapter extends CursorAdapter {
TextView txtId,txtName, txtDescription;
private LayoutInflater mInflater;
private Context mContext;

public CustomAdapter(Context context, Cursor c, int flags) {
    super(context, c, flags);
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}


@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    View view    =    mInflater.inflate(R.layout.item, parent, false);
    ViewHolder holder  =   new ViewHolder();
    holder.txtId    =   (TextView)  view.findViewById(R.id.txtId);
    holder.txtName    =   (TextView)  view.findViewById(R.id.txtName);
    holder.txtDescription =   (TextView)  view.findViewById(R.id.txtEmail);
    holder.btnaddToRecipe = (Button) view.findViewById(R.id.add);
    view.setTag(holder);


    return view;
}

@Override
public void bindView(View view, final Context context, final Cursor cursor) {

    ViewHolder holder  =   (ViewHolder)    view.getTag();
    holder.txtId.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns._ID)));
    holder.txtName.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_NAME)));
    holder.txtDescription.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_DESCRIPTION)));

    holder.btnaddToRecipe.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(context, AddGroceryItemActivity.class);
            context.startActivity(intent);
        }
    });

}

static class ViewHolder {
    TextView txtId;
    TextView txtName;
    TextView txtDescription;
    Button btnaddToRecipe;
 }
}

NEW EDIT: Thank you for your reply MikeT.

What you wrote makes sense in terms of passing the data, however I still get the same result each time. I changed my query as you wrote it, as well as adding the putExtra() on the intent. I then retrieve the id as you wrote it in the fragment, created a bundle object, put the string in the bundle and add it to the bundle with the setArguments method. I perform the transaction etc an start the fragment. Then from the fragment, I created a new string and called getArguments().getString("id"). Set the whereargs to the new string and wrote the query as you said.

I think it's in how my cursor code is written. I'm realizing a typo on the mCupsToGrams - should be the GROCERIES_CUPSTOGRAMS. But this is irrelevant to the issue.

When I logd the data passed in my onClick method, the same cursor is being passed no matter what button I press. I think this is because I have to mark cursor as final in order to access it from the anonymous class. Does that make sense?


Solution

  • It would appear that AddGroceryItemFragment extracts a Cursor consisting of all rows from the table as per :-

    SQLiteDatabase db = dbHelper.getReadableDatabase();
    String selectQuery = "SELECT rowid as " +
            GroceriesContract.Columns._ID + ", " +
            GroceriesContract.Columns.GROCERIES_NAME + ", " +
            GroceriesContract.Columns.GROCERIES_DESCRIPTION + ", " +
            GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS + ", " +
            GroceriesContract.Columns.GROCERIES_CUPSTOGRAMS +
            " FROM " + GroceriesContract.TABLE_NAME;
    Cursor cursor = db.rawQuery(selectQuery, null);
    

    It then goes on to loop through every extracted row setting the text to be displayed resulting in the display eventually displaying the data for the last extracted row.

    I believe that you would need a WHERE clause in the query so that it could select the appropriate Grocery Item as opposed to the last extracted (potentially any) grocery item.

    The query could be :-

    String[] columns = new String[]{
        "rowid AS " +
            GroceriesContract.Columns._ID,
        GroceriesContract.Columns.GROCERIES_NAME,
        GroceriesContract.Columns.GROCERIES_DESCRIPTION,
        GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS,
        GroceriesContract.Columns.GROCERIES_CUPSTOGRAMS
    };
    String whereclause = GroceriesContract.Columns._ID + "=?";
    String[] whereargs = new String[]{the_id_passed_to_the_fragment};
    Cursor cursor =db.query(GroceriesContract.TABLE_NAME,columns,whereclause,whereargs,null,null,null);
    
    • Notes
      • This uses the preferred query method as opposed to the rawQuery query
      • The variable the_id_passed_to_the_fragment is as it's name suggests, as you suspect by saying Or it's because I'm not passing the correct data about which search result the user chose, to the new activity. and or I'm wondering if this problem is actually the result of not passing data from the activity that started this the activity that then leads to starting AddGroceryItemFragment

    You would the follow this with :-

    if (cursor.moveToFirst()) {
        holder.name.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_NAME)));
        holder.mGramsToCups.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS)));
        holder.mCupsToGrams.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_GRAMSTOCUPS)));
    }
    else {
        //... do something just in case Grocery Item wasn't found here.
    }
    cursor.close();
    

    I believe that you would pass the id from the staring activity by adding an intent extra along the lines of (see 1 line that has been added):-

    @Override
    public void bindView(View view, final Context context, final Cursor cursor) {
    
        ViewHolder holder  =   (ViewHolder)    view.getTag();
        holder.txtId.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns._ID)));
        holder.txtName.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_NAME)));
        holder.txtDescription.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_DESCRIPTION)));
    
        holder.btnaddToRecipe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(context, AddGroceryItemActivity.class);
                intent.putExtra("GROCERYITEMID",cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns._ID))); //<<<< ADDED
                context.startActivity(intent);
            }
        });
    }
    

    You the retrieve this in the started activity using :-

     String retrieved_id = getIntent().getStringExtra(
                "GROCERYITEMID");
    

    You then need to make retrieved_id available to the fragment as the_id_passed_to_the_fragment.

    Regarding comment :-

    When I logd the data passed in my onClick method, the same cursor is being passed no matter what button I press. I think this is because I have to mark cursor as final in order to access it from the anonymous class. Does that make sense?

    You do not have to mark cursor as final instead use something like :-

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
    
        ViewHolder holder  =   (ViewHolder)    view.getTag();
        holder.txtId.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns._ID)));
        holder.txtName.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_NAME)));
        holder.txtDescription.setText(cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns.GROCERIES_DESCRIPTION)));
        final String id_to pass = cursor.getString(cursor.getColumnIndex(GroceriesContract.Columns._ID));
    
        holder.btnaddToRecipe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(context, AddGroceryItemActivity.class);
                intent.putExtra("GROCERYITEMID",id_to pass); //<<<< ADDED
                context.startActivity(intent);
            }
        });
    }