0

I have a list of recipes in a RecyclerView and when an item is selected, an activity displays the details of that recipe (ie. yield, directions, ratings, favorites, etc.).

The general information of each recipe is on one table (Recipes) and the ingredients are in another table (Ingredients), a separate table (RecipeIngredients) is used to link the recipes with ingredients creating a many-to-many relationship.

I'm trying to retrieve a list of ingredients in another RecyclerView when the details activity is displayed by using an AsyncTaskLoader on another thread. However, I just can't seem to figure out how to call the loader and return the list of ingredients for a specific recipe.

The SQL query I have does return a list of ingredients for a specified recipe item (checked in DB Browser for SQLite via the "Execute SQL" tab).

I have the following:

RecipeDisplayActivity.java

public class RecipeDisplayActivity extends AppCompatActivity {

private ArrayList<Ingredient> _ingredientsInRecipe = new ArrayList();
private DBHandler _repository;
private ProgressBar _progressIngredient;
private ProgressBar _progressDirections;
private Recipe _recipe;
private RecipeIngredientAdapter _adapterIngredient;
private RecyclerView _rvIngredient;
private TextView _lblIngredientMessage;
private TextView _lblRecipeCategory;
private TextView _lblRecipeDirections;
TextView _lblRecipeTitle;

private static final int RECIPE_INFORMATION_LOADER_ID = 173281;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_recipe_display);
    initControls(savedInstanceState);
}
private void initControls(Bundle savedInstanceState) {
    this._repository = DBHandler.getInstance(this);
    int recipeId = getIntent().getIntExtra(UIConsts.Bundles.RECIPE_KEY_ID, -1);
    if (recipeId > 0) {
        this._recipe = this._repository.recipes.getById(recipeId);
    } else {
        throw new IllegalStateException("RECIPE_KEY_ID not provided.");
    }

    setTitle(this._recipe.getTitle());
    this._lblRecipeTitle = (TextView) findViewById(R.id.lblRecipeTitle);
    this._lblRecipeTitle.setText(this._recipe.getTitle());
    this._lblRecipeCategory = (TextView) findViewById(R.id.lblRecipeCategory);
    this._lblRecipeCategory.setText(this._recipe.getCategory());
    this._lblRecipeDirections = (TextView) findViewById(R.id.lblRecipeDirections);
    this._lblRecipeDirections.setText(this._recipe.getDirections());
    final RatingBar ratingUser = (RatingBar) findViewById(R.id.ratingUser);
    ratingUser.setRating(this._recipe.displayRating());
    final ImageButton imgbtnFavoriteRecipe = (ImageButton) findViewById(R.id.imgbtnFavoriteRecipe);
    if (this._recipe.isFavorite()) {
        imgbtnFavoriteRecipe.setColorFilter(Utils.CustomColors.FAVORITE);
    } else {
        imgbtnFavoriteRecipe.setColorFilter(Color.rgb(80, 80, 80));
    }
    this._progressIngredient = (ProgressBar) findViewById(R.id.progressIngredient);
    this._lblIngredientMessage = (TextView) findViewById(R.id.lblIngredientMessage);
    this._rvIngredient = (RecyclerView) findViewById(R.id.rvIngredient);
    this._rvIngredient.setHasFixedSize(true);
    this._rvIngredient.setNestedScrollingEnabled(false);
    this._rvIngredient.setLayoutManager(new LinearLayoutManager(this));

    this._adapterIngredient = new RecipeIngredientAdapter(this._ingredientsInRecipe, this);

    this._rvIngredient.setAdapter(this._adapterIngredient);
    this._progressDirections = (ProgressBar) findViewById(R.id.progressDirections);

    if (this._recipe.getId() != null) {
        this._progressIngredient.setVisibility(View.GONE);
        this._lblIngredientMessage.setText(R.string.recipe_no_ingredients);
        this._lblIngredientMessage.setVisibility(View.GONE);
        this._progressDirections.setVisibility(View.GONE);
        loadFullRecipeInformation();
    }
}
public void loadFullRecipeInformation() {

    getSupportLoaderManager().initLoader(RECIPE_INFORMATION_LOADER_ID, null, new LoaderManager.LoaderCallbacks<Recipe>() {

        public Loader<Recipe> onCreateLoader(int id, Bundle args) {
            return new FullRecipeInformationLoader(RecipeDisplayActivity.this.getApplicationContext(), RecipeDisplayActivity.this._recipe.getId());
        }

        public void onLoadFinished(Loader<Recipe> loader, Recipe recipe) {
            if (recipe == null) {
                Exception exception = ((FullRecipeInformationLoader) loader).getException();
                if (exception == null) {
                    return;
                }
                RecipeDisplayActivity.this._progressIngredient.setVisibility(View.GONE);
                RecipeDisplayActivity.this._lblIngredientMessage.setText(R.string.general_msg_something_went_wrong);
                RecipeDisplayActivity.this._lblIngredientMessage.setVisibility(View.VISIBLE);

                Toast.makeText(RecipeDisplayActivity.this, R.string.general_msg_something_went_wrong, Toast.LENGTH_LONG).show();
                return;
            }

            recipe.setId(RecipeDisplayActivity.this._recipe.getId());
            recipe.setRating(RecipeDisplayActivity.this._recipe.getRating());
            recipe.setFavorite(RecipeDisplayActivity.this._recipe.isFavorite());
            recipe.setTimeAdded(RecipeDisplayActivity.this._recipe.getTimeAdded());
            RecipeDisplayActivity.this._recipe = recipe;

            RecipeDisplayActivity.this._adapterIngredient.notifyDataSetChanged();
            RecipeDisplayActivity.this.displayListStateIngredients();
        }

        public void onLoaderReset(Loader<Recipe> loader) {
        }
    }).forceLoad();
}
private void displayListStateIngredients() {
    this._progressIngredient.setVisibility(View.GONE);
    if (this._ingredientsInRecipe.size() < 1) {
        this._rvIngredient.setVisibility(View.GONE);
        this._lblIngredientMessage.setText(R.string.recipe_no_ingredients);
        this._lblIngredientMessage.setVisibility(View.VISIBLE);
        return;
    }
    this._lblIngredientMessage.setVisibility(View.GONE);
    this._rvIngredient.setVisibility(View.VISIBLE);
}
}

RecipeIngredientAdapter.java

public class RecipeIngredientAdapter extends RecyclerView.Adapter<RecipeIngredientAdapter.ViewHolder> {
private Activity _activity;
private ArrayList<Ingredient> _ingredient;

// Provide a direct reference to each of the views within a data item
// Used to cache the views within the item layout for fast access
public class ViewHolder extends RecyclerView.ViewHolder {

    // Your holder should contain a member variable
    // for any view that will be set as you render a row
    private CardView cardIngredient;
    private TextView lblIngredientName;
    private Ingredient ingredient;

    // We also create a constructor that accepts the entire item row
    // and does the view lookups to find each subview
    public ViewHolder(View itemView) {
        super(itemView);

        this.cardIngredient = (CardView) itemView.findViewById(R.id.cardIngredient);
        this.lblIngredientName = (TextView) itemView.findViewById(R.id.lblIngredientName);
    }

    void bindViewHolder(Ingredient ingredient) {
        this.ingredient = ingredient;

        this.lblIngredientName.setText(ingredient.getName());
    }
}

public RecipeIngredientAdapter(ArrayList<Ingredient> ingredientInRecipe, Activity activity) {
    this._ingredient = ingredientInRecipe;
    this._activity = activity;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row_ingredient, parent, false));
}

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
    viewHolder.bindViewHolder((Ingredient) this._ingredient.get(position));
}

@Override
public int getItemCount() {
    return this._ingredient.size();
}
 public Context getContext() {
    return this._activity.getApplicationContext();
}
}

FullRecipeInformationLoader.java

public class FullRecipeInformationLoader extends AsyncTaskLoader<Recipe> {
private Context _context;
private Exception _exception;
private int _recipeId;
private DBHandler _repository;

public FullRecipeInformationLoader(@NonNull Context context, int _recipeId) {
    super(context);
    this._context = context;
    this._recipeId = _recipeId;
}

public Recipe loadInBackground() {

    try {
        // Return list of Ingredients
return this._repository.recipeIngredients.getIngredientForRecipeById(this._recipeId);

    } catch (Exception e) {
        this._exception = e;
        return null;
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    return null;
}

public Exception getException() {
    return this._exception;
}
}

RecipeIngredients class

public class RecipeIngredients {
public ArrayList<Ingredient> getIngredientForRecipeById(int recipeId) {
        if (recipeId < 1) {
            throw new IllegalArgumentException("recipeId");
        }
        Cursor cursor = DBHandler.this._helper.getReadableDatabase().rawQuery("SELECT Ingredients.* FROM Recipes, Ingredients, RecipeIngredient WHERE RecipeIngredient._ingredient_id = Ingredients._ingredient_id AND Recipes._id = " + recipeId, null);
        ArrayList<Ingredient> ingredients = new ArrayList();
        while (cursor.moveToNext()) {
            ingredients.add(createIngredientFromCursor(cursor));
        }
        cursor.close();
        return ingredients;
    }

private Ingredient createIngredientFromCursor(Cursor cursor) {

        boolean z2 = true;
        Ingredient ingredient = new Ingredient();
        int id = cursor.getInt(cursor.getColumnIndex(DBConsts.Table_Ingredient.COLUMN_IngredientId));
        ingredient.setId(Integer.valueOf(id));
        ingredient.setName(cursor.getString(cursor.getColumnIndex(DBConsts.Table_Ingredient.COLUMN_IngredientName)));

        return ingredient;
    }
}

Let me know if you need any information I missed. Thanx in advance...

Chithra B
  • 586
  • 2
  • 9
Ziggy
  • 23
  • 5
  • https://stackoverflow.com/questions/26517855/using-the-recyclerview-with-a-database – OneCricketeer Feb 07 '18 at 07:17
  • @cricket_007 the adapter provided by skyfishjy is really poor: that one is much better: https://gist.github.com/Shywim/127f207e7248fe48400b as it has `FilterQueryProvider` – pskink Feb 07 '18 at 07:19
  • I'm sorry if I missed it, but I don't see how either link suggested and the use of a modified Cursor adapter has to do with the question asked. The question is about querying a DB for a specific ID and returning a list of results and displaying them in a RecyclerView. Forgive my ignorance... – Ziggy Feb 07 '18 at 08:58

1 Answers1

0

Use cursorLoaders with recyclerView.Adapter

Example:

public class RecipeDisplayActivity extends AppCompatActivity {
    Cursror bdCursor;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        RecyclerView newList = findViewById(android.R.id.list);
        BottleDataAdapter adapter = new BottleDataAdapter(bdCursor);
        LinearLayoutManager llm = new LinearLayoutManager(this);
        newList.setLayoutManager(llm);
        newList.setAdapter(adapter);
        //next line call cursorLoader 
        //(CursorLoader is subclass of AsyncTaskLoader, so you can call ATL with next line)
        //and next line you may use whenever you want e.g in onClick etc.
        getSupportLoaderManager().restartLoader(id, null, this);
    }
}

next step is override callbacks(in activity)

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    return new SelectItems(this, i);
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    adapter.changeCursor(cursor);//!!!!! This method was written manualy for changing cursor. You can see it in bottom
}

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) { }

Where SelectItems is :

public class SelectItems extends CursorLoader {

    //you can give your params from activity to query here
    public SelectItems(Context context, int param) {
        super(context);
    }

    @Override
    public Cursor loadInBackground() {
        //ProxyManager is just class for db queries, so next line you must do db query
        Cursor cursor = getProxyManager().getSectionsItems(mTypeSection);
        return cursor;
    }
}

and finish step is write our adapter

public class BottleDataAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<Bottle> bottles = new ArrayList<>();
    private Cursor cursor;

    //in constructor we set our cursor and init our data from this cursor
    public BottleDataAdapter(Cursor cursor) {
        this.cursor = cursor;
        initData();
    }

    private void initData() {
        if (cursor != null) {
            for (int pos = 0; pos < cursor.getCount(); pos++) {
                cursor.moveToPosition(pos);
                bottles.add(new Bottle(cursor.getString(0)));
            }
        }
    }

    public void changeCursor(Cursor cursor) {
        this.cursor = cursor;
        initData();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {...}

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {...}

    @Override
    public int getItemCount(){...}
}

Also you can use CursorAdapter instead of Cursor in RecyclerViewAdapter

no_fate
  • 1,625
  • 14
  • 22
  • I took this example from my proj, so use instead CursorLoader. 1 sec, edited. – no_fate Feb 07 '18 at 09:31
  • I'm still unclear on how to achieve my goal. Does anyone have any source code, examples, or tutorials to recommend? – Ziggy Feb 14 '18 at 21:48
  • @Ziggy, https://stackoverflow.com/questions/26517855/using-the-recyclerview-with-a-database?noredirect=1&lq=1 – no_fate Feb 15 '18 at 08:14
  • I think, perhaps, that my question is misunderstood. I can and have my initial list (RecyclerView) populated with the list of recipes and, when an item is selected, a "details" window shows the basic info for the recipe. How can I query the DB with this recipes ID to get the list of ingredients to populate another RecyclerView? – Ziggy Feb 15 '18 at 10:56