I'm trying to write a simple runtime user interface that's generated from the contents of files read from the SD card. Am using a RecyclerView to generate buttons, the user can then make a selection (via the button) before moving onto the next menu on the next fragment. This program combines, RecyclerViews, Viewbinding, Fragments and Filehandling (the filehandling being the least important).
For example (I can't get a photo of the first fragment running unfortunately):
would be generated from this text file:
Priory Hotel, Victoria Street, Bristol
Landsdown Hotel, Landsdown, Bristol
The Hilton, Walcot Street, Bristol
MerryFields, Milsom Street, Bristol
Loz's Kitchen, St Jame's Square, Bristol
The top button would contain the text Priory Hotel, Victoria Street, Bristol the second button Landsdown Hotel, Landsdown, Bristol the 3rd The Hilton, Walcot Street, Bristol and so forth, the number of buttons displayed being the same as the number of lines (number of clients) of text.
I'm hoping to generate the 'button/menu interface' at runtime. The next fragment would be another list of buttons, also generated from a different text file (in the same way) and so on and so forth.
Is doing this even a good idea ? Should I be using Fragments ?
Am using Android Studio and developing for Android 5.1 (Lollipop I think). The error comes when I call the onClickListener in the Adapter Class. I just can't get this to work and have tried the following Stack Overflow Answers:
This doesn't work and isn't kotlin,
nor this (again kotlin but I can convert). Can't call supportFragmentManager This is unanswered
I have read this, watched this and read these SO posts: 1, 2, 3, 4. 5. And finally this for good measure.
Adapter Class.kt
// I've not included the imports for brevity
class MyAdapter(private val aList : ArrayList<String>, private val destination: Int) : RecyclerView.Adapter<MyAdapter.MyViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.list_item,
parent, false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
Log.d("Loz", "start onBindViewHolder()")
val currentItem = aList[position]
holder.buttonInRecyclerV.text = currentItem
val context = holder.buttonInRecyclerV.context
Log.d("Loz", "onBindViewHolder() before setOnClickLIstener")
// **************************************************************
// According to the error message provided by Logcat the crash is
// this line below. The 2 log messages in this function are being
// outputted too.
// **************************************************************
holder.buttonInRecyclerV.setOnClickListener {v ->
v.findNavController().navigate(destination)
// The toast message gets printed if I get rid of all the navigation
// and just run 1 fragment
Toast.makeText(context,
"${aList[position]} button pressed", Toast.LENGTH_LONG).show()
}
}
class MyViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView)
{
private val binding = ListItemBinding.bind(itemView)
val buttonInRecyclerV : Button = binding.clientsNameButton
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var clientArrayList: ArrayList<String>
private lateinit var productArrayList: ArrayList<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d("Loz", "before dataInit()")
dataInitialise(view)
Log.d("Loz", "after dataInit()")
replaceFragment(savedInstanceState, FirstFragment(), clientArrayList, "clientsListBundle")
Log.d("Loz", "after 1st frag Init()")
// The following may well be a load of twoddle, I was desparate when I wrote the
// code below. However if I just have:
// replaceFragment(savedInstanceState, SecondFragment(), productArrayList, "productListBundle)
// uncommented and comment out the first call of replaceFragment() the second fragment
// is created no problem...
if (FirstFragment().lifecycle.currentState == Lifecycle.State.RESUMED) {
replaceFragment(
savedInstanceState,
SecondFragment(),
productArrayList,
"productListBundle"
)
}
Log.d("Loz", "after 2nd frag Init()")
}
// This function (again) seems to work without the navigation elements
// in the app, Logs outputted.
private fun replaceFragment(savedInstanceState: Bundle?, aFragment: Fragment, aList: ArrayList<String>, TOKEN: String){
if (savedInstanceState == null) {
val fragmentManager = supportFragmentManager
val bundle = Bundle()
Log.d("Loz", "after replaceFragment()")
bundle.putStringArrayList(TOKEN, aList)
aFragment.arguments = bundle
fragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.frameLayout, aFragment)
}
}
}
private fun listFromFile(view: View, filename :String) : MutableList<String> {
// Had no problems with this, and it works as expected when there's no navigation
// component to this app.
}
private fun dataInitialise(view: View) {
clientArrayList = listFromFile(view, "NEades/Client List.txt") as ArrayList<String>
productArrayList = listFromFile(view, "NEades/Product List.txt") as ArrayList<String>
}
}
activity_main.xml
<?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:layout_height="match_parent"
tools:context=".MainActivity">
<?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:layout_height="match_parent"
tools:context=".MainActivity">
<!-- If I put this in neither fragment works, if I take it out, both -->
<!-- do independently -->
<!-- <androidx.fragment.app.FragmentContainerView-->
<!-- android:id="@+id/fragment_container_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:navGraph="@navigation/nav_graph">-->
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/frameLayout"
app:layout_constraintBottom_toTopOf="@id/frameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
</FrameLayout>
</androidx.fragment.app.FragmentContainerView>
<!-- </androidx.fragment.app.FragmentContainerView-->
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/frameLayout"
app:layout_constraintBottom_toTopOf="@id/frameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
</FrameLayout>
</androidx.fragment.app.FragmentContainerView>
</androidx.constraintlayout.widget.ConstraintLayout>
first_fragment.xml
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="always"
android:id="@+id/FirstFragment">
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="0dp"
tools:context=".FirstFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/list_item"/>
</FrameLayout>
</androidx.core.widget.NestedScrollView>
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="1dp"
android:layout_marginHorizontal="2dp"
android:background="@color/white">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/clientsNameButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:text="@string/default_value"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
android:background="@color/underline"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Eventually, if this is possible, the goal is to refactor even more.
My question: (other than should I be using activities other than fragments ?) is how to 'call' the second fragment from any of the buttons in the first fragment (from the Adapter class ?) so that the second fragment then initialises and displays it's contents (retrieved from a text file)