1

Im trying to create a View model that contains alist of countries in it. The View modal class look like this:

class Country_ViewModel(ctx:Context) :ViewModel(){

    val itemSelected : MutableLiveData<Int> by lazy{
        MutableLiveData<Int>()

    }
    val p = XmlPullParserHandler()
    private var count: MutableList<Country> = p.parse(openCountriesFile(ctx))
    val countryArray =MutableLiveData(count)

    
    // This function will open the XML file and return an input stream that  will be used by the Parse function
    fun openCountriesFile(context: Context): InputStream? {
        val assetManager: AssetManager = context.getAssets()
        var `in`: InputStream? = null
        try {
            `in` = assetManager.open("countries.xml")
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return `in`
    }



    // This function will loop thorough the country list and delete the entry that it got from the position
    fun removeItem(position: Int) {
        count.map { pos ->
            if (pos.compare(count[position]) == 0) {
                count.remove(pos)
                return
            }
        }
    }

The function openCountriesFile will just parse the XML file that contains the Countries and save it in the MutableLiveData object inside the ModelView.

Later I would like to use a Fragment to observe the data that is changed: This fragment will use the Adapter that I created and populate the Fragment with the country data.

The fragment will look like that:

class frag : Fragment(){

    val KEY_COUNTRY = "country"
    val KEY_NAME = "name"
    val KEY_FLAG = "flag"
    val KEY_ANTHEM = "anthem"
    val KEY_SHORT = "short"
    val KEY_DETAILS = "details"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)

    }
    val viewModal:Country_ViewModel by viewModels()

/*
* When creating the view we  would like to do the following:
* Initiate the Adapter.
* When the adapter has been called he will look for the XML file with the country's in it.
      Second one for the anthems
* */
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val v = inflater.inflate(R.layout.fragment_frag, container, false)


        val rv = v.findViewById(R.id.rvTodos) as RecyclerView
        val adapter = countryAdapter(requireContext(),viewModal,this)
        viewModal.itemSelected.observe(viewLifecycleOwner, Observer<Int>{
            val fragment2 = details_frag()
            val fragmentManager: FragmentManager? = fragmentManager
            val fragmentTransaction: FragmentTransaction = fragmentManager!!.beginTransaction()

            fragmentTransaction.apply {
                replace(R.id.fragLand, fragment2)
                commit()
            }
        })
        rv.adapter = adapter



        // Apply the new content into the fragment layout
        val mLayoutManager = LinearLayoutManager(activity);
        rv.layoutManager = mLayoutManager
        return v
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


    }


    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.primary_menu,menu)
    }




}

Theer I would observe if there was a country that has been clicked on and if so I would like to move to my other fragment to get some more details.

My adapter look like that:

class countryAdapter(
    var ctx: Context, var viewModal:Country_ViewModel, var owner: LifecycleOwner
) : RecyclerView.Adapter<countryAdapter.countryViewHolder>() {
//     lateinit var mListener: onItemLongClickListener
//    private lateinit var mSListener: onItemClickListener
    var player: MediaPlayer? =null
    private lateinit var count: MutableList<Country>





/**-----------------------------------INTERFACES --------------------------------------------------*/

//    interface onItemLongClickListener {
//
//        fun onItemLongClick(position: Int)
//    }
//
//    interface onItemClickListener {
//
//        fun onItemClick(position: Int): Boolean
//    }
/**-----------------------------LISTENERS --------------------------------------------------------*/

//    fun setOnItemLongClickListener(listener: onItemLongClickListener) {
//        mListener = listener
//
//    }
//    fun setOnItemClickListener(listener: onItemClickListener) {
//        mSListener = listener
//    }

/**-----------------------------INNER CLASS--------------------------------------------------------*/
    inner class countryViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        val counrtyName = itemView.findViewById<TextView>(R.id.countryName)
        val populationNum = itemView.findViewById<TextView>(R.id.countryPopulation)
        val imageCount = itemView.findViewById<ImageView>(R.id.imageView)


/*
* Defining the listeners in the initialization of the Row in the  adapter
* */
        init {
        count= viewModal.countryArray.value!!
            itemView.setOnLongClickListener {
               viewModal.removeItem(adapterPosition)
                return@setOnLongClickListener true

            }
            itemView.setOnClickListener{
                viewModal.itemSelected.value=adapterPosition
                Log.i("Hello",adapterPosition.toString())
                startPlayer(adapterPosition,ctx)
            }

        viewModal.countryArray.observe(owner, Observer {
            notifyDataSetChanged()
        })
        }




    }
/**---------------------------------------VIEW HOLDER CREATE AND BIND ----------------------------- */
    /*
    * Will inflate the country XML file in the  adapter and then inflate it into the parent that is
    * the fragment.
    * At the end it will return the inner class with all the parameters that was initiated there.
    * */
    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): countryAdapter.countryViewHolder {

        val view = LayoutInflater.from(parent.context).inflate(R.layout.itemcountry, parent, false)
        val context = parent.context
        val inflater = LayoutInflater.from(context)
        val contactView = inflater.inflate(R.layout.itemcountry, parent, false)

        return countryViewHolder(contactView)
    }

/*
* The function will be responsible to get the data that was initiated at the begining in the
* inner class and change the data that is displayed in the XML file to the new data  based on the
* Country that it got
* The position is parameter that is changing every time this function is called and adding all the
* Country that are in the XML into the  fragment
*
* */
    override fun onBindViewHolder(holder: countryViewHolder, position: Int) {

        var countryName1 = holder.counrtyName
        var countryPopulation1 = holder.populationNum
        var imagecount = holder.imageCount
        countryName1.setText(viewModal.countryArray.value?.get(position)?.name_of_country)
        countryPopulation1.setText(count?.get(position)?.shorty_of_country)
        count?.get(position)?.let {
        country_drawable.get(it.name_of_country)?.let {
            imagecount.setBackgroundResource(
                it
            )
        }
    }

    }

/**-----------------------------------------Functions ------------------------------------------- */
    fun startPlayer(position: Int,ctx:Context){
        player?.stop()
         player =
             count?.get(position)
                 ?.let { country_raw.get(it.name_of_country)?.let { MediaPlayer.create(ctx, it) } }
        player?.start()
    }





    override fun getItemCount(): Int {
        return count.size
    }


}

The goal is if the user click on one of the countries in the RecyclyView (OnClickListener) then i would like to move to the second fragment.

Im having an error will creating the viewModal instance the error is:

Cannot create an instance of class com.example.Country_ViewModel

Why is that? what I'm initializing wrong?

Where should i create the instance of the ViewModal? inside the adapter or inside the fragment itself? ( is it ok to pass the viewModal instance to the adapter? or there is another way i can observe the change in the CountryArray?)

Thank you.

Ilan
  • 55
  • 3
  • Does this answer your question? [Cannot create an instance of class ViewModel](https://stackoverflow.com/questions/44998051/cannot-create-an-instance-of-class-viewmodel) – Ivo Beckers Dec 01 '21 at 09:01
  • by the way, it's called a viewmodel, not viewmodal – Ivo Beckers Dec 01 '21 at 09:04
  • No it doesn't help, when trying using activityViewModels() its throws an error that it cant create the class – Ilan Dec 01 '21 at 13:15

0 Answers0