I have a custom DialogFragment that I'm using to capture user input that I will create a database entry with. I'm using EditText in an AlertDialog. I am trying to use a single activity for my application and the original tutorial I was studying was using multiple activities and intents but that seems outdated for most cases.
When I debug I find that the EditText is returning "" and is showing up as empty when I call TextUtils.isEmpty() in the MainActivity onDialogPositiveClick.
I've done a lot of combing through the forms here and I'm confused by:
1)many of the answers I find are in Java and not Kotlin
2)many mention onCreate but do not specify onCreateView vs. onCreateDialog or if there's just an onCreate that I need to override.
I have researched this and found answers that confuse me a bit about when and if I need to inflate the layout. This current itteration I didn't inflate it at all. I just set it in the AlertDialog builder.
Maybe it's the interface I'm not understanding. How am I supposed to pass information between the dialog and MainActivity? The interface seems to pass the dialog itself but I seem to be missing something when it comes to getting the EditText from the dialog.
My custom DialogFragment
class NewSongFragment : DialogFragment() {
lateinit var listener: NewSongListener
lateinit var editNewSong: EditText
lateinit var editBPM: EditText
interface NewSongListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
/** The system calls this to get the DialogFragment's layout, regardless
of whether it's being displayed as a dialog or an embedded fragment. */
/*
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout to use as dialog or embedded fragment
return inflater.inflate(R.layout.fragment_new_song, container, false)
}
*/
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
listener = context as NewSongListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NewSongListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it)
//add inflater
//val inflater = requireActivity().layoutInflater;
//val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(R.layout.fragment_new_song)
.setCancelable(true)
.setNegativeButton(R.string.cancel,DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save,
DialogInterface.OnClickListener {dialog, _ ->
listener.onDialogPositiveClick(this)
})
// Create the AlertDialog object and return it
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
My MainActivity
class MainActivity : AppCompatActivity(),NewSongFragment.NewSongListener {
private val songViewModel: SongViewModel by viewModels {
SongViewModelFactory((application as SongApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//create view
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
val adapter = ItemAdapter(this,
ItemAdapter.OnClickListener { rating -> songViewModel.insertRating(rating) }
)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
//initialize data
songViewModel.allSongs.observe(this) { song ->
// Update the cached copy of the songs in the adapter.
song.let { adapter.submitList(it) }
}
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
//add song button
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
showNewSongDialog()
}
}
private fun showNewSongDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NewSongFragment()
dialog.show(supportFragmentManager, "NewSongFragment")
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
val editNewSong = dialog.view?.findViewById<EditText>(R.id.newSongTitle)
val editBPM = dialog.view?.findViewById<EditText>(R.id.newSongBpm)
if(TextUtils.isEmpty(editNewSong?.text)){
}else{
val newSong = Song(editNewSong?.text.toString(),100)
songViewModel.insertSong(newSong)
val rating = Rating(System.currentTimeMillis(),newSong.songTitle, 50)
songViewModel.insertRating(rating)
}
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
You are adding the layout with a resource identifier, so your call to get the view is returning null. (Why? The view is inflated internally and just handled differently.) Since you are using the AlertDialog to collect data, you will have to add an inflated view.
I am also going to suggest that you change the interface to hide the details of the dialog; There is no reason for the main activity to know the internal structure of the dialog. It just needs the song title and BPM and maybe some other stuff. You will find the code a little easier to understand and maintain.
Here is a slight rework. This code just captures the song title, but it can easily be extended to include other data as well.
In NewSongFragment:
interface NewSongListener {
fun onDialogPositiveClick(songTitle: String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save)
{ dialog, _ ->
Log.d("Applog", view.toString())
val songTitle = view?.findViewById<EditText>(R.id.newSongTitle)?.text
listener.onDialogPositiveClick(songTitle.toString())
}
In MainActivity.kt
override fun onDialogPositiveClick(songTitle: String) {
// songTitle has the song title string
}
Android dialogs have some quirks. Here are a number of ways to do fragment/activity communication.