1

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
    }


}

DarkOhms
  • 90
  • 10
  • This is where I started: [https://developer.android.com/codelabs/android-room-with-a-view-kotlin#14] Turns out since then google moved toward single activities and Fragments so I was trying to use this [https://developer.android.com/guide/topics/ui/dialogs.html?hl=en] – DarkOhms Feb 03 '22 at 23:30

2 Answers2

0

Because you are adding the dialog as a Fragment, you should use onCreateView to inflate the view, rather than trying to add a view in onCreateDialog.

Brian Yencho
  • 2,748
  • 1
  • 18
  • 19
  • As you can see, my `onCreateView ` is commented out. When I inflate the view there, it doesn't show up in the alert dialog. I tried a variety of things where I inflate in `onCreateView` and try to add it with `setView` in the `AlertDialog` builder. I've tried not adding to to the builder at all. All result in the layout not being added to the dialog at all. – DarkOhms Feb 03 '22 at 23:18
  • Well you shouldn't be trying to "build" the dialog at all in `onCreateDialog`. Get rid of your code in `onCreateDialog` and just treat this like a normal Fragment that inflates a view into `onCreateView`. You'll want to add buttons etc. to your Fragment layout. Otherwise don't use a Fragment and just build your dialog directly where you need it. – Brian Yencho Feb 04 '22 at 15:12
  • I'm using an alert dialog because I want it to be like a pop up and when I looked up how to do a pop up window that's what I found. Is there a way to use a **Fragment** that also acts like a pop up view? – DarkOhms Feb 07 '22 at 20:14
  • Well you can just subclass `DialogFragment` like you are doing but don't override `onCreateDialog` and provide the view manually yourself OR just build the `AlertDialog` directly in your Activity and how show it there (and skip the Fragment entirely). Anyways, sounds like you've got some suggestions that will work for you. – Brian Yencho Feb 07 '22 at 22:23
0

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.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • That makes more sense to me, passing just what is needed but the Dev guide I was using had it passing the dialog??? Now I tried that before and couldn't get it to work and the dev guide had me second guessing myself. I had also passed the inflated view before instead of the resource ID but it didn't solve the "" problem. I'll give it another try passing the strings. – DarkOhms Feb 03 '22 at 23:38
  • Also, what is the line with Log.d? I'm completely unfamiliar with this and the only place I've seen it has been in Java examples. – DarkOhms Feb 03 '22 at 23:48
  • @LukeMartin The code I posted works, so give it a try. `Log.d()` just records an entry in logcat. It's just for debugging and can be removed. – Cheticamp Feb 03 '22 at 23:56
  • I can't get it to work like that. First, your version does away with DialogInterface.OnClickListener and is passing this. to the listener. Mine wants me to pass a String, and not this. – DarkOhms Feb 04 '22 at 01:28
  • @LukeMartin My mistake. I didn't make all the changes. I updated that line in the answer. – Cheticamp Feb 04 '22 at 01:48
  • Thanks. That's working. I'm still a little confused then about the lamdas and why they want to pass the dialog and why you were able to do away with the DialogInterface.OnClickListener part. – DarkOhms Feb 04 '22 at 02:12
  • @LukeMartin Not sure why the dialog would be passed. As for doing away with "DialogInterface.OnClickListener", Kotlin already knows what it is (it can be only that) so it saves us the effort of writing the boilerplate. – Cheticamp Feb 04 '22 at 02:38