Home > database >  I cannot get fragment to work in a Kotlin project
I cannot get fragment to work in a Kotlin project

Time:08-04

I have a Java project with three fragments that works perfectly. I am trying to translate the project into Kotlin. Everything works so far, except the first (and only complete so far) fragment. The fragments do load as they should but the first fragment's adapter's functions

onCreateViewHolder(parent: ViewGroup, viewType: Int)

and

onBindViewHolder(viewHolder: MyViewHolder, i: Int)

never execute so the empty fragment is displayed in the user's interface. What am I missing, please?

Here is my FragmentMainEntries:

class FragmentMainEntries() : Fragment() {
    private lateinit var binding: FragmentMainEntriesBinding
    private var foodList = ArrayList<Item>()
    private var tableRowAdapter = MainFragAdapter(foodList)

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

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
    ): View? {

        prepareList()
        //Toast.makeText(activity, "foodList 9 = " foodList.size, Toast.LENGTH_LONG).show()

        binding = FragmentMainEntriesBinding.inflate(layoutInflater)
        val view = binding.root
        val recyclerView = view.findViewById<RecyclerView>(R.id.recycleView)
        recyclerView.layoutManager = LinearLayoutManager(view.context)
        tableRowAdapter = MainFragAdapter(foodList)
        binding.recycleView.adapter = tableRowAdapter

        return inflater.inflate(R.layout.fragment_main_entries, container, false)
    }


    private fun prepareList()
    {

        //Toast.makeText(activity, "prepareList()", Toast.LENGTH_SHORT).show()
        try {
            foodList.clear()
            for (i in Tables.finalTable.indices) {
                val item = Item(Tables.finalTable[i]?.get(0) as Int,
                    Tables.finalTable[i]?.get(2) as String,
                    Tables.finalTable[i]?.get(3).toString(),
                    Tables.finalTable[i]?.get(4).toString().toFloat(),
                    Tables.finalTable[i]?.get(8).toString().toFloat())
                foodList.add(item)
            }
           // Toast.makeText(activity, "foodList8 = " foodList.size, Toast.LENGTH_LONG).show()
        }
        catch(e: Exception)
        {
           Toast.makeText(activity, "MAIN ACTIVITY EXCEPTION002\n"   e.message , Toast.LENGTH_LONG).show()
           Thread.sleep(6000)
        }
    }

    companion object {

        @JvmStatic
        fun newInstance() = FragmentMainEntries().apply {

        }
    }
    }

Here is my adapter:

import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView
    import android.widget.Toast
    import androidx.recyclerview.widget.RecyclerView


    class MainFragAdapter(private var foodArrayList: MutableList<Item>) : 
    RecyclerView.Adapter<MainFragAdapter.MyViewHolder>()
    {
    lateinit var view: View

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {

        view = LayoutInflater.from(parent.context).inflate(R.layout.item_row, parent, false)
        Toast.makeText(view.context, "foodArrayList5 = " foodArrayList.size, Toast.LENGTH_SHORT).show()
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: MyViewHolder, i: Int) {

        if(i<1) // For debugging
        {
            Toast.makeText(view.context, "foodArrayList7 = "   foodArrayList.size, Toast.LENGTH_SHORT).show()
        }

        foodArrayList[i]
        viewHolder.tvIndex.text = foodArrayList[i].index.toString()
        viewHolder.tvDescription.text = foodArrayList[i].description
        viewHolder.tvUnits.text = foodArrayList[i].units
        viewHolder.tvPortion.text = foodArrayList[i].portion.toString()
        viewHolder.tvCalories.text = foodArrayList[i].calories.toString()
    }


    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvIndex: TextView = itemView.findViewById(R.id.tvIndex)
        val tvDescription: TextView = itemView.findViewById(R.id.tvDescription)
        val tvUnits: TextView = itemView.findViewById(R.id.tvUnits)
        val tvPortion: TextView = itemView.findViewById(R.id.tvPortion)
        val tvCalories: TextView = itemView.findViewById(R.id.tvCalories)
    }

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

    companion object{   }
    }

Here is my MainActivity class:

import android.graphics.Color
    import android.os.Bundle
    import android.view.View
    import android.view.WindowInsets
    import android.widget.TextView
    import android.widget.Toast
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.content.ContextCompat
    import androidx.fragment.app.Fragment
    import androidx.fragment.app.FragmentActivity
    import androidx.viewpager2.adapter.FragmentStateAdapter
    import androidx.viewpager2.widget.ViewPager2
    import com.google.android.material.tabs.TabLayout
    import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
    import com.google.android.material.tabs.TabLayoutMediator
    import com.neels.newarcadiafragments.databinding.ActivityMainBinding
    import com.neels.newarcadiafragments.model.Tables
    import java.io.File
    import java.io.FileInputStream
    import java.io.ObjectInputStream

    class MainActivity : AppCompatActivity() {

    private var binding: ActivityMainBinding? = null
    var logTag = "Observer"
    var message: TextView? = null

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

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding!!.getRoot())
        val decorView = window.decorView
        decorView.windowInsetsController!!.hide(WindowInsets.Type.statusBars())
        supportActionBar!!.setDisplayShowCustomEnabled(true)
        supportActionBar!!.setCustomView(R.layout.custom_action_bar)
        supportActionBar!!.elevation = 1f
        val view = supportActionBar!!.customView
        message = view.findViewById(R.id.name)

        message!!.setOnClickListener { v: View? ->
            message!!.text = getString(R.string.message_clicked)
            Toast.makeText(this@MainActivity, "You have clicked message", Toast.LENGTH_LONG).show()
        }

        setContentView(R.layout.activity_main)
        setupTabs()
        readMainTableFromSdCard()
    }



    private fun setupTabs() {

        val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
        val viewPager = findViewById<ViewPager2>(R.id.viewPager)
        viewPager.adapter = ViewPagerAdapter(this)

        TabLayoutMediator(tabLayout, viewPager
        ) { tab: TabLayout.Tab, position: Int ->
            if (position == 0) {
                tab.text = "Main Entries"
                tab.view.setBackgroundColor(Color.parseColor("#A52A2A"))
            } else if (position == 1) {
                tab.text = "Meal Entries"
                tab.view.setBackgroundColor(Color.parseColor("#A52A2A"))
            } else {
                tab.text = "Label Entry"
                tab.view.setBackgroundColor(Color.parseColor("#A52A2A"))
            }
        }.attach()

        tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                tab!!.view.setBackgroundColor(Color.CYAN)
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                tab!!.view.setBackgroundColor(Color.parseColor("#A52A2A"))
            }

            override fun onTabReselected(tab: TabLayout.Tab?) {
            }
        })

    }
 

    private fun readMainTableFromSdCard() {
        val externalStorageVolumes =
            ContextCompat.getExternalFilesDirs(this.applicationContext, null)
        val sdCardStorage = externalStorageVolumes[1]
        try {
            val f2 = File("$sdCardStorage/condensed.mst")
            val readData = FileInputStream(f2)
            val readStream = ObjectInputStream(readData)
            Tables.finalTable = readStream.readObject() as Array<Array<Any?>?>
            readStream.close()
           // Toast.makeText(getApplicationContext(), "Rows = "   Tables.finalTable.size, Toast.LENGTH_SHORT).show()
        } catch (e: Exception) {
            Toast.makeText(applicationContext, "EXCEPTION: "   readMainTableFromSdCard(), Toast.LENGTH_LONG).show()
            return
        }
        //Toast.makeText(getApplicationContext(), "Rows = "   Tables.finalTable.length, Toast.LENGTH_LONG).show();
    }



    companion object {
        class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

            override fun createFragment(position: Int): Fragment {
                return if (position == 0) {
                    FragmentMainEntries.newInstance()
                } else if (position == 1) {
                    FragmentMealEntries.newInstance()
                } else {
                    FragmentLabelEntry.newInstance()
                }
            }

            override fun getItemCount(): Int {
                return 3
            }
        }
    }
    }

Here is the fragment layout file:

<?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@color/very_light_background"
    android:layout_height="match_parent"
    tools:context=".FragmentMainEntries">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@ id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@ id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/viewPager" />

    </androidx.constraintlayout.widget.ConstraintLayout>

Here is my Item class:

class Item internal constructor(
    var index: Int,
    var description: String,
    var units: String,
    var portion: Float,
    var calories: Float,
    )

Here is my item row layout file:

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@ id/itemLayout"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="34dp"
    android:focusable="true"
    android:clickable="true"
    android:layout_margin="0dp">

    <TableLayout
        android:id="@ id/table_heading_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="192dp"
        android:layout_marginTop="0dp"
        android:foreground="?selectableItemBackground">
 

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_gravity="bottom"
            android:background="@color/brown"/>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="34dp">

            <TextView
                android:id="@ id/tvIndex"
                android:layout_width="50dp"
                android:layout_height="34dp"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:layout_column="0"
                android:background="@color/brown"
                android:textAlignment="center" />

            <View
                android:layout_width="1dip"
                android:background="@color/brown"/>

            <TextView
                android:id="@ id/tvDescription"
                android:layout_width="720dp"
                android:layout_height="34dp"
                android:layout_column="1"
                android:textColor="@color/brown"
                android:background="@color/very_light_background"
                android:textAlignment="center"
                android:textAppearance="?android:attr/textAppearanceMedium"/>

            <View
                android:layout_width="1dip"
                android:background="@color/brown"/>

            <TextView
                android:id="@ id/tvPortion"
                android:layout_width="100dp"
                android:layout_height="34dp"
                android:layout_column="2"
                android:textColor="@color/brown"
                android:background="@color/very_light_background"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textAlignment="center" />

            <View
                android:layout_width="1dip"
                android:background="@color/brown"/>

            <TextView
                android:id="@ id/tvUnits"
                android:layout_width="100dp"
                android:layout_height="34dp"
                android:layout_column="3"
                android:textColor="@color/brown"
                android:background="@color/very_light_background"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textAlignment="center" />

            <View
                android:layout_width="1dip"
                android:background="@color/brown"/>

            <TextView
                android:id="@ id/tvCalories"
                android:layout_width="108dp"
                android:layout_height="34dp"
                android:layout_column="4"
                android:textColor="@color/brown"
                android:background="@color/very_light_background"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textAlignment="center" />

            <View
                android:layout_width="1dip"
                android:background="@color/brown"/>
        </TableRow>

        <View
            android:layout_height="1dip"
            android:background="@color/brown"/>

    </TableLayout>
</LinearLayout>

CodePudding user response:

You're inflating a layout and setting stuff up on the Views in it, but then you inflate another copy of the layout (where nothing's set up) and return that for display. So your stuff might be working, just in the layout you can't see!

override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
    ): View? {

    // inflating layout copy A, storing the Binding class it creates
    binding = FragmentMainEntriesBinding.inflate(layoutInflater)
    // you're storing the `View` you should be returning here!
    val view = binding.root
    
    // setting things up on layout A
    val recyclerView = view.findViewById<RecyclerView>(R.id.recycleView)
    recyclerView.layoutManager = LinearLayoutManager(view.context)
    tableRowAdapter = MainFragAdapter(foodList)
    binding.recycleView.adapter = tableRowAdapter

    // inflating layout copy B and returning that for display!
    return inflater.inflate(R.layout.fragment_main_entries, container, false)
}

You're doing it again in MainActivity too:

binding = ActivityMainBinding.inflate(layoutInflater)
...
// this tells the system to inflate this layout file and display it
setContentView(R.layout.activity_main)


For the Activity, you can either do

binding = ActivityMainBinding.inflate(layoutInflater)
// pass your inflated hierarchy for display
setContentView(binding.root)

or

// inflate and display a view hierarchy
setContentView(R.layout.activity_main)
// now the layout is displayed as 'view' (aka getView()) you can bind to that
// hierarchy - basically looking up all the view IDs in the binding class
binding = ActivityMainBinding.bind(view)

I prefer the first version since you just display what you inflated from the view binding, you don't need to match the correct layout XML file and view binding class, but it's up to you

Also, make your binding field lateinit like you did in your Fragment. Don't make it nullable just to have a temporary null value, using !! in your code is generally bad and a sign something shouldn't be nullable at all!


For the Fragment, do something like this:

override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
    ): View? {

    // inflating layout copy A, storing the Binding class it creates
    binding = FragmentMainEntriesBinding.inflate(layoutInflater)
    // don't use findViewById with view binding, part of the point is you don't
    // have to do that! All your Views with IDs are already in the Binding object.
    // by using 'with(binding)' we can refer to things in binding without having to
    // prefix them all with 'binding.'
    with(binding) {
        // really binding.recyclerView - 'binding' is 'this' inside the 'with' block
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        tableRowAdapter = MainFragAdapter(foodList)
        recyclerView.adapter = tableRowAdapter
    }

    // pass back the inflated layout - 'root' is a special property of the created binding
    // object, either what it inflated through #inflate, or bound to through #bind
    return binding.root
}

edit also just FYI, you can use View Binding with your Adapter too (might as well since you're already using it!)

class MainFragAdapter(private var foodArrayList: MutableList<Item>) : 
    RecyclerView.Adapter<MainFragAdapter.MyViewHolder>()
    {
    // you're only using this as a temp thing in onCreateViewHolder
    // but it shouldn't really be here! Safer to use a local variable in the function
    lateinit var view: View

    // make this take an ItemViewBinding instead, and make the parameter
    // a val so it's a property on the object. Pass the view root to the super constructor
    inner class MyViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root) {
        // no need to look anything up! It's all in the binding object
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemRowBinding.inflate(inflater, parent, false)
        Toast.makeText(parent.context, "foodArrayList5 = " foodArrayList.size, Toast.LENGTH_SHORT).show()
        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(viewHolder: MyViewHolder, i: Int) {
        val item = foodArrayList[i]
        // see the ViewHolder class below - working with a binding object now
        with(viewHolder.binding) {
            tvIndex.text = item.index.toString()
            tvDescription.text = item.description
            tvUnits.text = item.units
            tvPortion.text = item.portion.toString()
            tvCalories.text = item.calories.toString()
        }
    }


    // you can make this an expression if you like, it's neat
    override fun getItemCount() = foodArrayList.size
}
  • Related