NavArgs not returning a value, I just get this when I try to update through the setOnClickListener in update fragment:
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
Caused by: java.lang.IllegalArgumentException: Required argument "currentAlarm" is missing and does not have an android:defaultValue
I need to return the ID from the alarm selected in the RecyclerView, so a default value wouldn't make sense here.
The NavArgs are set in the nav-view correctly, with no default. Could the model and ID be set up wrong with autoGenerate? or is going via the adapter the problem here?
update fragment:
class UpdateFragment : Fragment() {
private val timePickerUtil = TimePickerUtil()
lateinit var binding: FragmentUpdateBinding
private lateinit var alarmViewModel: AlarmViewModel
private val args by navArgs<UpdateFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentUpdateBinding.inflate(inflater, container, false)
alarmViewModel = ViewModelProvider(this)[AlarmViewModel::class.java]
binding.fragmentBtnUpdateAlarm.setOnClickListener {
updateAlarm()
Navigation.findNavController(requireView())
.navigate(R.id.action_updateFragment_to_homeFragment)
}
return binding.root
}
private fun updateDatabase(id: Int, hour: Int, minute: Int, repeat: Boolean) {
val alarm = Alarm(id, hour, minute, repeat)
alarmViewModel.update(alarm)
}
private fun updateAlarm() {
val timePicker = binding.fragmentUpdateAlarmTimePicker
val id = args.currentAlarm.id
val hour = timePickerUtil.getTimePickerHour(timePicker)
val minute = timePickerUtil.getTimePickerMinute(timePicker)
val repeat = binding.fragmentUpdateAlarmRecurring.isChecked
val alarmManager = AlarmManager(
id,
hour,
minute,
true,
binding.fragmentUpdateAlarmRecurring.isChecked
)
updateDatabase(id, hour, minute, repeat)
alarmManager.cancel(requireContext())
alarmManager.schedule(requireContext())
}
}
adapter:
class AlarmListAdapter() :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class MyViewHolder(binding: LayoutAlarmBinding) : RecyclerView.ViewHolder(binding.root)
private var alarmList = ArrayList<Alarm>()
private var onItemClickListener: OnItemClickListener? = null
interface OnItemClickListener{
fun onClick(alarm: Alarm)
fun onLongClick(alarm: Alarm)
}
fun setOnItemClickListener(listener: OnItemClickListener){
onItemClickListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding = LayoutAlarmBinding.inflate(LayoutInflater.from(parent.context))
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val currentItem = alarmList[position]
val minute = currentItem.minute
holder.itemView.findViewById<TextView>(R.id.tv_alarm_time).text =
if (minute >= 10) {
"${currentItem.hour}:${currentItem.minute}"
} else {
"${currentItem.hour}:0${currentItem.minute}"
}
holder.itemView.setOnClickListener{
if(onItemClickListener != null){
onItemClickListener?.onClick(currentItem)
}
}
holder.itemView.setOnLongClickListener {
if(onItemClickListener != null){
onItemClickListener?.onLongClick(currentItem)
}
true
}
}
override fun getItemCount(): Int {
return alarmList.size
}
fun setData(alarm: List<Alarm>) {
alarmList.clear()
alarmList.addAll(alarm)
notifyDataSetChanged()
}
}
HomeFragment:
class HomeFragment : Fragment() {
lateinit var binding: FragmentHomeBinding
private lateinit var alarmViewModel: AlarmViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentHomeBinding.inflate(inflater, container, false)
// RecyclerView
val adapter = AlarmListAdapter()
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//ViewModel
alarmViewModel = ViewModelProvider(this).get(AlarmViewModel::class.java)
alarmViewModel.readAlarmData.observe(viewLifecycleOwner, Observer { alarm ->
adapter.setData(alarm)
})
binding.btnAddAlarm.setOnClickListener {
Navigation.findNavController(requireView())
.navigate(R.id.action_homeFragment_to_newAlarmFragment)
}
adapter.setOnItemClickListener(object : AlarmListAdapter.OnItemClickListener {
override fun onClick(alarm: Alarm) {
Navigation.findNavController(requireView())
.navigate(R.id.action_homeFragment_to_updateFragment)
}
override fun onLongClick(alarm: Alarm) {
val deleteBuilder = AlertDialog.Builder(requireContext())
deleteBuilder.setPositiveButton("Delete") { _, _ ->
alarmViewModel.delete(alarm)
Toast.makeText(context, "Alarm Deleted", Toast.LENGTH_SHORT)
.show()
}
deleteBuilder.setNegativeButton("Cancel") { _, _ ->
}
deleteBuilder.setTitle("Delete Alarm?")
deleteBuilder.create().show()
}
})
return binding.root
}
}
and my alarm model:
@Parcelize
@Entity(tableName = "alarm_table")
data class Alarm(
@PrimaryKey(autoGenerate = true)
val id: Int,
val hour: Int,
val minute: Int,
val repeat: Boolean
): Parcelable
CodePudding user response:
Safe args creates its own classes(like here HomeFragmentDirections). So in homeFragment where you are setting up onclick listeners for adapter, you should use->
adapter.setOnItemClickListener(object : AlarmListAdapter.OnItemClickListener {
override fun onClick(alarm: Alarm) {
Navigation.findNavController(requireView())
.navigate(HomeFragmentDirections.actionHomeFragmentToUpdateFragment(params))
}
override fun onLongClick(alarm: Alarm) {
// preform longclick action here
}
})
And add a param(in your case id may be of type int or String) of the required type in UpdateFragment in the corresponding navgraph.
I hope you got my point and if not don't hesitate to comment. You can read about this on the android developer website here.