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.