Kotlin Android Jetpack Navigation between Fragments in backstack - android

I have a small app. 1 Activity (MainActivity.kt) and two Fragments (MainFragment & CreateNewJobFragment).
Mainfragment has a Recyclerview. CreateNewJobFragment contains a 6 spinners and an edittext. I'm attempting to have the selections from the spinners and edittext to populate the RecyclerView when the user makes their choices and clicks a "create job" button.
The issue is, when this button is clicked, the app will go back up the backstack to the MainFragment, but instead of populating the recyclerview with a new item, it runs through onCreate again and nothing happens.
MainFragment.kt
class MainFragment : Fragment() ,ItemClickedCustomListener{
override fun onCustomSpinnerItemSelected(selectedItems: JobData) {
Log.v("MainFragment","onCustomSpinnerItemSelected")
Log.v("MainFragment","selectedItems --> "
+ selectedItems.companyName + " "
+ selectedItems.location + " "
+ selectedItems.pumpTruck + " "
+ selectedItems.smartPiggers)
jobs.add(selectedItems)
adapter = recyclerView.adapter as JobAdapter
adapter.data(jobs)
}
var jobs = ArrayList<JobData>()
lateinit var adapter : JobAdapter
private lateinit var binding: FragmentMainBinding
lateinit var recyclerView : RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (savedInstanceState == null) {
binding = FragmentMainBinding.inflate(inflater)
//getting recyclerview from xml and binding it
recyclerView = binding.jobRecyclerView
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
//Arraylist to store jobs using the data class JobData.
// TODO: Change this to a user created list from CreateNewJobFragment.kt
jobs = ArrayList()
Log.v("MainFragment", "onCreateView --> ")
//creating adapter
adapter = JobAdapter(jobs)
//add adapter to recyclerView
recyclerView.adapter = adapter
//Setting onClickListener for FAB(floating action button) using Navigation
binding.createNewJobFAB.setOnClickListener { v: View ->
v.findNavController().navigate(R.id.action_mainFragment_to_createNewJobFragment)
}
}
return binding.root
}
}
CreateNewJobFragment.kt
class CreateNewJobFragment : Fragment() {
private lateinit var binding: FragmentCreateNewJobBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentCreateNewJobBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var addJobToRecyclerview = JobData("","","","")
//String array.
//TODO: Move this to res/strings
val companyNames = arrayOf("Company A", "Company B", "Company C", "Company D", "Company E")
var nameSpinner = binding.spinnerCustomerName
//Adapter for spinner
nameSpinner.adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_dropdown_item, companyNames)
//item selected listener for spinner
nameSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(p0: AdapterView<*>?) {
TODO("not implemented yet")
}
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
//user selected spinner choice added
addJobToRecyclerview.companyName = companyNames[p2]
}
}
//String array.
//TODO: Move this to res/strings
val refineryTown = arrayOf("Long Beach", "Houston", "Cherry Point", "Wood River", "Bismark")
var townSpinner = binding.spinnerLocation
//Adapter for spinner
townSpinner.adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_dropdown_item, refineryTown)
//item selected listener for spinner
townSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(p0: AdapterView<*>?) {
TODO("not implemented yet")
}
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
addJobToRecyclerview.location = refineryTown[p2]
}
}
<SNIP>Cutting out 5 repeated spinners and editText to save some space</SNIP>
//Setting onClickListener for 'Create Job' button using Navigation
binding.buttonCreateJob.setOnClickListener { v: View ->
(activity as MainActivity).itemClickedCustomListener.onCustomSpinnerItemSelected(addJobToRecyclerview)
Log.v("CreateNewJobFragment", "Job data added -->$addJobToRecyclerview")
Log.v("CreateNewJobFragment", "Create job button --> clicked")
v.findNavController().navigate(R.id.action_createNewJobFragment_to_mainFragment)
}
}
}
I'm using the following Android documentation as a guide: Implementing Navigation
And this part in particular for the navigation back to MainFragment: Tie destinations to UI widgets
After clicking the "Create Job" button. App does go back to MainFragment. But as mentioned, nothing shows in Recyclerview.
I've some some logging in place. This is from the logs:
Log sample
2018-12-07 22:03:17.959 5806-5806/com.palarran.pigtimer V/MainActivity: onCustomSpinnerItemSelected
2018-12-07 22:03:17.959 5806-5806/com.palarran.pigtimer V/MainActivity: selectedItems --> Company A Long Beach Company 1 Company 4
2018-12-07 22:03:17.960 5806-5806/com.palarran.pigtimer V/CreateNewJobFragment: Job data added -->JobData(companyName=Company A, location=Long Beach, pumpTruck=Company 1, smartPiggers=Company 4)
2018-12-07 22:03:17.960 5806-5806/com.palarran.pigtimer V/CreateNewJobFragment: Create job button --> clicked
2018-12-07 22:03:18.026 5806-5806/com.palarran.pigtimer V/MainFragment: onCreateView -->
2018-12-07 22:03:18.146 5806-5806/com.palarran.pigtimer V/JobAdapter: getItemCount 0
2018-12-07 22:03:18.146 5806-5806/com.palarran.pigtimer V/JobAdapter: getItemCount 0
As you can see, after the log entry for "Create job button --> clicked", it jumps to "onCreateView" in the MainFragment and the item count in my custom adapter(JobAdapter.kt) does not increase.
JobAdapter.kt
class JobAdapter(private var jobList: ArrayList<JobData>) : RecyclerView.Adapter<JobAdapter.ViewHolder>() {
//Returning view for each item in the list
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JobAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.job_list_item, parent, false)
Log.v("JobAdapter","onCreateViewHolder")
return ViewHolder(v)
}
//Binding the data on the list
override fun onBindViewHolder(holder: JobAdapter.ViewHolder, position: Int) {
Log.v("JobAdapter","onBindViewHolder")
holder.bindItems(jobList[position])
}
override fun getItemCount(): Int {
Log.v("JobAdapter","getItemCount " + jobList.size)
return jobList.size
}
fun data(jobs: ArrayList<JobData>) {
Log.v("JobAdapter","DataSetChanged")
jobList = jobs
notifyDataSetChanged()
}
//Class holds the job list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(job: JobData) {
val textViewCompanyName = itemView.findViewById(R.id.tv_companyName) as TextView
val textViewLocation = itemView.findViewById(R.id.tv_job_location) as TextView
textViewCompanyName.text = job.companyName
Log.v("JobAdapter", "bindItems" + textViewCompanyName.text)
textViewLocation.text = job.location
Log.v("JobAdapter", "bindItems" + textViewLocation.text)
}
}
}
Am I missing something in the documents or reading them incorrectly? I'm definitely misunderstanding something.

Late responding to this.
I was able to get this figured out. By using Sugar ORM database. Calling it in onResume() in my MainFragment.
It was a dumb question that I should have researched more before posting.

Related

"Empty list doesn't contain element at index 0" error when implementing Android Recycler View with checkbox

I am implementing a recycler view with it's items as checkbox. My data is coming from ROOM database and this recycler view is inside a dialog fragment.
Dialog Fragment :
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ScheduleFloorDialogBinding.inflate(layoutInflater)
createProfileViewModel = CreateProfileViewModel(Application())
floorProfileDialogAdapter = FloorProfileDialogAdapter()
binding.rvFloorsForScheduling.layoutManager = LinearLayoutManager(requireActivity())
binding.rvFloorsForScheduling.adapter = floorProfileDialogAdapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val floorList: MutableList<String> = mutableListOf()
//Getting list of all floors
createProfileViewModel.totalFloors.observe(viewLifecycleOwner) {
Timber.d("List of floors received : $it")
val intList = it.map(String::toInt)
val maxFloorValue = intList.last()
var count = 0
try {
while (count <= maxFloorValue) {
floorList.add(count.toString())
count++
}
} catch (e: Exception) {
Timber.d("Exception: $e")
}
floorProfileDialogAdapter.getAllFloors(floorList)
Timber.d("Floor List : $floorList")
}
}
I am able to send list of data from here to my adapter.
Adapter:
class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() {
var floors = emptyList<String>()
inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false)
return MyViewHolder(binding)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentFloor = floors[position]
Timber.d("Current floor: $currentFloor")
holder.binding.floorCheckBox.text = "Floor $currentFloor"
}
override fun getItemCount(): Int {
return floors.toString().length
}
fun getAllFloors(floorsReceived: List<String>) {
Timber.d("Floors received : $floorsReceived")
this.floors = floorsReceived
}
}
Log inside the Adapter's getAllFloor method shows that list has been received:
But inside onBindViewHolder() when I use the position I get the error saying :
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
The view is already inflated when you initialized the floorProfileDialogAdapter with an empty list, it won't be changed till you use notifyDataSetChange()
it's a solution which is not recommended
or
Using ListAdapter from Androidx Recycler view Package: it has its own submit list so every time you submit a list it notifies data set change and it compares it with the previous one
Check documentation

RecyclerView kotlin don't remember selected items, after add next items

Hello i have problem with my shopping List. I have RoomDatabase and recycler view to adding and reading my list. I create action when user click on button row change background, but after i add new items then don't see selected items. Where is a problem?
My Adapter:
lass ListAdapter() : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var itemList = emptyList<Items>()
lateinit var viewModel: ItemsViewModel
inner class MyViewHolder(itemview: View) : RecyclerView.ViewHolder(itemview) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.items_row, parent, false)
)
}
override fun getItemCount(): Int {
return itemList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = itemList[position]
holder.itemView.name_rec.text = currentItem.itemsName
holder.itemView.amount_rec.text = currentItem.amountItems.toString()
holder.itemView.rowLayout.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btnDeleteee.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToDeleteFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btncheckbox.setOnClickListener {
holder.itemView.rowLayout.setBackgroundColor(Color.parseColor("#FFBA5F"))
}
}
fun setData(items: List<Items>) {
this.itemList = items
notifyDataSetChanged()
}
My ListFragment:
class ListFragment : Fragment() {
lateinit var itemsViewModel: ItemsViewModel
private val args by navArgs<UpdateFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list, container, false)
//menu
setHasOptionsMenu(true)
//RecyclerView
val recyclerView = view.recycler_view_list
val adapter = ListAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//ViewWModel
itemsViewModel = ViewModelProvider(this).get(ItemsViewModel::class.java)
itemsViewModel.readAllData.observe(viewLifecycleOwner, Observer { items ->
adapter.setData(items)
})
view.floatingActionButton2.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
Where is problem with save all actions who do user in list?
Thanks for help
You have to store the selected item and in onBindViewHolder you have to check that the item for this position is previously selected item or not. If this is the selected item then change the background or make if default.
Actually the problem is as you are changing the background in onClickListener so when you are updating the itemList with setData(items: List<Items>) method RecyclerView reload all the visible items and your previously selected item loses its background

notifyDataSetChanged not updating RecyclerView although ArrayList reference is not lost

I appreciate that this question has been asked MANY times in SO but all the solutions refer to a missing reference in my arraylist which currently I (believe) am preserving.
I have an adapter as follows:
/**
* [RecyclerView.Adapter] that can display a [Note].
*/
class MyNoteRecyclerViewAdapter(
private var notes: ArrayList<Note>,
private val onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener
) : RecyclerView.Adapter<MyNoteRecyclerViewAdapter.ViewHolder>() {
private val mNotes: ArrayList<Note> = notes
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_notes_list, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mNotes[position]
holder.bind(item, onNoteSelectedListener)
}
override fun getItemCount(): Int = mNotes.size
fun setItems(notes: ArrayList<Note>) {
mNotes.clear()
mNotes.addAll(notes)
notifyDataSetChanged()
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val idView: TextView = view.findViewById(R.id.item_number)
private val contentView: TextView = view.findViewById(R.id.content)
fun bind(item: Note, onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener) {
idView.text = item.id
contentView.text = item.title
itemView.setOnClickListener(View.OnClickListener { onNoteSelectedListener.onNoteSelected(item) })
}
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
as you can see above, the function setItems() is setting clearing all notes in the list and readding them followed by notifyDataSetChanged()
The view that calls this is here:
class NotesListFragment() : BaseFragment(), MainActivityContract.OnNoteSelectedListener {
var mAdapter: MyNoteRecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_notes_list_list, container, false)
val lvNotes = view.findViewById<RecyclerView>(R.id.lv_notes);
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
return view
}
override fun onNoteSelected(note: Note) {
(activity as MainActivity).onNoteSelected(Note());
}
fun onNotesLoaded(notes: ArrayList<Note>) {
mAdapter?.setItems(notes)
view?.findViewById<RecyclerView>(R.id.lv_notes)?.adapter?.notifyDataSetChanged()
}
}
The function onNotesLoaded is called by an external class that fetches notes. As you can see, I am setting items using mAdapter.setItems() which should notify the list that data has changed but without luck. I tried to also add the second line to see if it's something I'm missing but again, no luck.
I'm not sure if this is a kotlin issue on my part when assigning the list variable but any assistance would be greatly appreciated.
It seem you forgot add LayoutManager for RecyclerView
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
lvNotes.layoutManager = LinearLayoutManager(context)
It seems like the issue lied in other areas of my codebase so I will close this question. Thanks for anyone who tried to help

Android Adapter redrawing view with all the same original items rather than removing selected item

I've been trying to delete an item from my list so that it updates without the removed item, but the list seems to redraw itself and keeps displaying all the original items as before. For a short bit of time it's possible to see the item as if it's being removed, however, due to this redrawing everything gets back to what it was before the removal.
I've tried several combinations of the following methods but none of them seem to work in this case.
adapter.notifyItemRangeChanged(position, adapter.itemCount)
adapter.notifyItemRemoved(position)
adapter.notifyItemChanged(position)
adapter.notifyDataSetChanged()
These are my files. Please notice I'm using the Groupie library as a replacement for the default RecyclerView.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product,
private val onItemClickListener: OnItemClickListener?
) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
ivTrash.setOnClickListener {
if (onItemClickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
onItemClickListener.onClick(viewHolder.adapterPosition)
// deleteProduct(product.id)
}
}
}
}
}
interface OnItemClickListener {
fun onClick(position: Int) //pass your object types.
}
override fun getLayout() = R.layout.recyclerview_item_row
}
And here my fragment:
class ProductsListFragment : Fragment() {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
recyclerView.adapter = adapter
loadProducts()
}
/**
* API calls
*/
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.OnItemClickListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "successful network call")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
}
}
override fun onClick(position: Int) {
Log.i(LOG_TAG, position.toString())
adapter.notifyItemRangeChanged(position,
adapter.itemCount)
adapter.notifyItemRemoved(position)
}
})
}
}
Many thanks.
Simple sample
class GroupAdapter(private val items: MutableList<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun removeByPosition(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}

Android Kotlin can't use spinner inside a fragment

I am trying to use a spinner inside a fragment but whenever I click on it nothing happens (meaning it doesn't show me anything to select)
private fun prioritySpinner() {
//the array with all the string values
val searchPriority = resources.getStringArray(R.array.PriorityArray)
// Initializing an ArrayAdapter
val adapter = context?.let {
ArrayAdapter(
it, // Context
android.R.layout.simple_spinner_item, // Layout
searchPriority // Array
)
}
// Set the drop down view resource
adapter?.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line)
//searchPriority_ShowBugsFragment_spinner is my spinner
// Finally, data bind the spinner object with adapter
searchPriority_ShowBugsFragment_spinner?.adapter = adapter
// Set an on item selected listener for spinner object
searchPriority_ShowBugsFragment_spinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener{
override fun onItemSelected(parent:AdapterView<*>, view: View, position: Int, id: Long){
// Display the selected item text on text view
Toast.makeText(context,"Selected "+ searchPriority[position], Toast.LENGTH_SHORT).show()
}
override fun onNothingSelected(parent: AdapterView<*>){
// Another interface callback
}
}
}
It doesn't change anything if I call the function inside the onCreateView or inside the onCreate
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
prioritySpinner()
return inflater.inflate(R.layout.fragment_show_bugs,container,false)
}
or
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prioritySpinner()
}

Categories

Resources