Im writing an app that will record and allow the modification of abstract recipes (literally only an ingredient list [and meta], because it will hopefully help a family-member with how their brain works).
I'm using GSON to read and write those recipes to files, and then bring them into the program as an object, so they can be displayed and modified.
Recipe.java
package me.eyrim.foodrecords2;
import com.google.gson.annotations.SerializedName;
public class Recipe {
@SerializedName("recipe_name")
private String recipeName;
@SerializedName("recipe_desc")
private String recipeDesc;
@SerializedName("ingredients")
private Ingredient[] ingredients;
@SerializedName("recipe_id")
private String recipeId;
public String getRecipeName() {
return this.recipeName;
}
public String getRecipeDesc() {
return this.recipeDesc;
}
public Ingredient[] getIngredients() {
return this.ingredients;
}
public String getRecipeId() {
return this.recipeId;
}
}
Ingredient.java
package me.eyrim.foodrecords2;
import com.google.gson.annotations.SerializedName;
public class Ingredient {
@SerializedName("ingredient_name")
private String ingredientName;
@SerializedName("ingredient_drawable_tag")
private int ingredientDrawableTag;
public String getIngredientName() {
return this.ingredientName;
}
public int getIngredientDrawableTag() {
return this.ingredientDrawableTag;
}
}
Below is an example (simple) recipe in the json format GSON produces
{
"recipe_name": "My test recipe 1 updated",
"recipe_id": "0",
"recipe_desc": "this is a test desc for my test recipe 1",
"ingredients": [{
"ingredient_name": "Tomato",
"ingredient_drawable_tag": 700003
},
{
"ingredient_name": "Pepper",
"ingredient_drawable_tag": 700002
}
]
}
I am using a RecyclerView adapter to display the ingredients as a grid. This is my adapter so far.
IngredientRecyclerViewAdapter.java
package me.eyrim.foodrecords2.recipeviewactivity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.eyrim.foodrecords2.Ingredient;
import me.eyrim.foodrecords2.R;
public class IngredientRecyclerViewAdapter extends RecyclerView.Adapter<IngredientRecyclerViewAdapter.IngredientViewHolder> {
private final Ingredient[] ingredients;
public IngredientRecyclerViewAdapter(Ingredient[] ingredients, Context context) {
this.ingredients = ingredients;
}
@NonNull
@Override
public IngredientViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.ingredient_row, parent, false);
return new IngredientViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull IngredientViewHolder holder, int position) {
//imageView.setImageResource(this.ingredients[position]);
holder.imageView.setImageResource(this.ingredients[position].getIngredientDrawableTag()); // THIS LINE HERE
}
@Override
public int getItemCount() {
return this.ingredients.length;
}
public class IngredientViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
public IngredientViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.ingredient_image);
}
}
}
The line marked // THIS LINE HERE
is the offending line.
The drawable tags in the json are gathered from R.drawable.tomato
(etc.).
However, this throws a runtime exception (below):
E/im.foodrecords: Invalid ID 0x000aae63.
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: me.eyrim.foodrecords2, PID: 3368
android.content.res.Resources$NotFoundException: Resource ID #0xaae63
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:225)
at android.content.res.Resources.getValue(Resources.java:1428)
at androidx.appcompat.widget.ResourceManagerInternal.createDrawableIfNeeded(ResourceManagerInternal.java:181)
at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:146)
at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:137)
at androidx.appcompat.content.res.AppCompatResources.getDrawable(AppCompatResources.java:66)
at androidx.appcompat.widget.AppCompatImageHelper.setImageResource(AppCompatImageHelper.java:91)
at androidx.appcompat.widget.AppCompatImageView.setImageResource(AppCompatImageView.java:102)
at me.eyrim.foodrecords2.recipeviewactivity.IngredientRecyclerViewAdapter.onBindViewHolder(IngredientRecyclerViewAdapter.java:35)
at me.eyrim.foodrecords2.recipeviewactivity.IngredientRecyclerViewAdapter.onBindViewHolder(IngredientRecyclerViewAdapter.java:16)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:561)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:536)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23143)
at android.view.ViewGroup.layout(ViewGroup.java:6412)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:798)
at android.view.View.layout(View.java:23143)
E/AndroidRuntime: at android.view.ViewGroup.layout(ViewGroup.java:6412)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3684)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3143)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2126)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8653)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:780)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
My guess is that Android changes the ids at runtime, is there an elegant method of programmatically finding and replacing these? Other than writing in every separate ingredient as a switch to replace the id. (i.e)
switch (ingredientName) {
case "tomato":
ingredient.setDrawableTag(R.drawable.tomato)
// etc.
}
CodePudding user response:
You're almost right. Resource IDs are not stable from build to build. Every time your app is recompiled, they might change, so you cannot put them in anything that you save, such as a JSON resource.
I would make your JSON tag the same name as your drawable resource. For example, use "tomato"
in your JSON for R.drawable.tomato
.
SerializedName("ingredient_drawable_tag")
private String ingredientDrawableTag;
"ingredient_drawable_tag": "tomato"
Then you can find your drawable ID by the String name at runtime like this (make sure you keep that Context reference from your constructor in a member variable so you can use it from onBindViewHolder()
):
int id = context.getResources().getIdentifier(
ingredients[position].getIngredientDrawableTag(),
"drawable",
context.getPackageName()
);
holder.imageView.setImageResource(id);