kotlin – Android – Store ArrayList persistent-ThrowExceptions

Exception or error:

My app is displaying a list of various categories (herbs, side dishes, ..) in a RecyclerView. Depending on the category you clicked on, a new Activity with a new RecylcerView opens containing all the ingredients.

Right now I have an ArrayList which gets filled with the ingredients via “.add” depending on the choosen category.

The problem im facing right now is, that I want to implement an option for the user to add own Ingredients. I tried storing the ArrayList containing the ingredients in SharedPreferences by using Gson, but I couldn’t manage to add elements, since it always overwrote the current list.

What would be the best way to store the ingredients? A room, sqlite, ..?
Without further explanation, the ingredient list will only contain about 70 items max.

Thanks in advance.

Edit:

CatList.kt

class CatList : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cat_list)


        //Create List for categories
        val cats = ArrayList<IngCat>()

        //Fill categories
        cats.add(IngCat(R.drawable.herbs, "Herbs"))
        cats.add(IngCat(R.drawable.fluessiges, "Liquids"))
        cats.add(IngCat(R.drawable.festes, "Solids"))
        cats.add(IngCat(R.drawable.beilagen, "Sides"))



        //Recyclerview
        id_rv_CatList.layoutManager = LinearLayoutManager(this)
        id_rv_CatList.adapter =
            CatListAdapter(cats) {listItem, position -> //go to Ingredient List Activity
                    goToIngList(position, listItem.name)
                }

        //id_rv_CatList.addItemDecoration(DividerItemDecoration(this,DividerItemDecoration.HORIZONTAL))

        //actionbar
        val actionbar = supportActionBar
        //set actionbar title
        actionbar!!.title = "Ingredient - Categories"


    }
    private fun goToIngList(cat: Int, name: String){
        val intent = Intent(this, IngList::class.java)
        intent.putExtra("Category", cat)
        intent.putExtra("Name", name)
        startActivity(intent)
    }
}

data class IngCat(var mImageResource:Int, var name:String)

IngList.kt

class IngList : AppCompatActivity() {

    companion object {
        var categoryChoosen : Int = 0
        var catName : String = "Err"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ing_list)


        //Initilize Ingredient List
        val ings :ArrayList<IngIng> = ArrayList()


        //Get category and category name
        categoryChoosen = intent.getIntExtra("Kategorie",0)
        catName = intent.getStringExtra("Name")!!

       when (categoryChoosen) {
            0 -> {
                ings.add(IngIng("https://doeel.com/images/thumbnails/1100/900/detailed    /92/Turmeric_Powder___Holud_Gura__.png", "Turmeric Powder"))
            }
            1 -> ings.add(IngIng("https://www.miraherba.de/4923-large_default/bio-ghee-300-g.jpg", "Ghee"))
            2 -> ings.add(IngIng("https://www.organicfacts.net/wp-content/uploads/coriander-1.jpg", "Coriander leaves"))
            3 -> ings.add(IngIng("https://gbc-cdn-public-media.azureedge.net/img75602.1426x713.jpg", "Potatoes"))
        }

        

        //Actionbar Settings
        setSupportActionBar(toolbar)
        val actionbar = supportActionBar
        actionbar!!.title = "Ingredients- $catName"
        actionbar.setDisplayHomeAsUpEnabled(true)


        //Recyclerview
        id_rv_IngList.layoutManager = GridLayoutManager(this,2)
        id_rv_IngList.adapter =
            IngListAdapter(ings) {//ClickListener RecyclerView
                Toast.makeText(this, "Item clicked: ${it.name}", Toast.LENGTH_SHORT).show()
            }





//Actionbar
    }
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.actionbar_ing_list, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.id_menu_action_add -> {
                val intent = Intent(this, AddIngredient::class.java)
                startActivity(intent)
                true
            }
            else -> super.onOptionsItemSelected(item)

    }
}

override fun onSupportNavigateUp(): Boolean {
        onBackPressed()
        return true
    }



}

IngListAdapter.kt

class IngListAdapter (private val ings: ArrayList<IngIng>, val clickListener: (IngIng)->Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>(){


    override fun getItemCount(): Int = ings.size




    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val v: View = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_ing_list_item, parent, false)
        return IngViewHolder(v)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        var currentItem = ings.get(position)
        when (holder) {
            is IngViewHolder -> {
                holder.tvIngList.text = currentItem.name
               // holder.ivIngImage.setImageResource(currentItem.mImageResource)
                Picasso.get().load(currentItem.mImageResource).placeholder(R.drawable.ic_broken_image_black_200dp).error(R.drawable.ic_broken_image_red_24dp).into(holder.ivIngImage)

                holder.cvIngCard.setOnClickListener{
                        clickListener(currentItem)
                    }
                }
            }


        }
    }


    class IngViewHolder (view: View) : RecyclerView.ViewHolder(view) {

        val tvIngList: TextView = view.id_text_ing
        val ivIngImage: ImageView = view.id_img_ing
        val cvIngCard: MaterialCardView = view.id_cv_ing_list
    }
How to solve:

I personally think Json/Gson in a SharedPreference is the easiest way to go if there are so few items. The way I would handle it is to store the list in memory at application startup, and persist the list back to the SharedPreference when the app is shut down. Also when the app gets stopped for good measure because you can’t 100% be sure onDestroy will be called.

So first I’d make a class to store the data. If you were using Fragments that all are in the same Activity, you’d put this in a ViewModel. But since they are separate Activities, you need a singleton for them. (Google doesn’t recommend using multiple Activities because it’s hard to share data between them. But it’s not impossible. It’s what we did before Fragments.)

To do it as a singleton, you could have a class like this:

class IngredientsRepo private constructor (application: Application) {

    companion object {
        private val INSTANCE: IngredientsRepo? = null
        fun getInstance(application: Application) = 
            INSTANCE ?: IngredientsRepo(application).also { INSTANCE = it }

        private const KEY_JSON_PREF = "ingredientsJson"
    }

    private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)

    val herbsList: MutableList<IngCat>
    val liquidsList: MutableList<IngCat>
    val solidsList: MutableList<IngCat>
    val sidesList: MutableList<IngCat>

    init {
        val json = sharedPreferences.getString(KEY_JSON_PREF, null)
        if (json == null) {
            // initialize your list contents for the first time
        } else {
            // convert your json and fill the data into your lists
        }
    }

    fun save {
        val jsonString = // Convert your lists to Json
        sharedPreferences.edit().putString(KEY_JSON_PREF, jsonString).apply()
    }
}

This class becomes responsible for setting up your lists. You can retrieve it from any Activity with IngredientsRepo.getInstance(this) and you can add and remove items from the lists whenever you like. You can also call save on it whenever you like to persist the latest data. It’s probably sufficient to do this in onStop() of any Activity that modifies the list.

More properly, the data in this class would only be exposed with immutable lists, and you’d add functions for adding and removing items, so only this class directly modifies the lists. I didn’t want to overcomplicate the example, but it would be better for encapsulation to not have Activities (which are supposed to be pure UI components) directly modifying data structures.

Leave a Reply

Your email address will not be published. Required fields are marked *