I have a DialogFragment with a listener for when a button gets clicked to call a function in my fragment.
I am getting lateinit property listener has not been initialized when I click the positive button.
DialogFragment
class CreateCollectionDialog: DialogFragment() {
lateinit var listener: CreateCollectionDialogListener
interface CreateCollectionDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment, collectionName: String)
// fun onDialogNegativeClick(dialog: DialogFragment)
}
override fun onAttachFragment(childFragment: Fragment) {
println("onAttachFragment")
super.onAttachFragment(childFragment)
listener = context as CreateCollectionDialogListener
println(listener)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater
builder.setView(inflater.inflate(R.layout.dialog_collection, null))
.setPositiveButton("Create", DialogInterface.OnClickListener { dialog, id ->
// Create new collection
var newCollectionName = view?.findViewById<EditText>(R.id.newCollectionName)?.text.toString()
if (!newCollectionName.equals("") && newCollectionName != null) {
listener.onDialogPositiveClick(this, newCollectionName)
}
})
.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id ->
// User canceled dialog
// listener.onDialogNegativeClick(this)
})
builder.create()
}?: throw IllegalStateException("Activity cannot be null")
}
override fun onStart() {
super.onStart()
val positive: Button = (dialog as AlertDialog?)!!.getButton(AlertDialog.BUTTON_POSITIVE)
positive.setTextColor(resources.getColor(R.color.topColor))
val negative: Button = (dialog as AlertDialog?)!!.getButton(AlertDialog.BUTTON_NEGATIVE)
negative.setTextColor(Color.RED)
}
}
Fragment
class CollectionsFragment: Fragment(), CreateCollectionDialog.CreateCollectionDialogListener {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add -> {
val createDialog = CreateCollectionDialog()
createDialog.show(fragmentManager!!, "")
return true
}
}
return false
}
override fun onDialogPositiveClick(dialog: DialogFragment, collectionName: String) {
addNewCollection(collectionName)
}
}
onAttachFragment is called when a fragment is attached as a child of this fragment, which in this case, never and not required.
Use onAttach(Context context) for current scenario. Dialog fragment has no child so onAttachFragment will never be called.
To initialize the listener from the parent fragment, use:
// inside fragment lifecycle methods like onviewcreated etc
listener = getParentFragment() as CreateCollectionDialogListener
The simplest way to solve this problem would be to assign the listener at the time you create the dialog:
when (item.itemId) {
R.id.add -> {
val createDialog = CreateCollectionDialog()
createDialog.listener = this
createDialog.show(fragmentManager!!, "")
return true
}
}
However, note that this will have problems if the activity is destroyed and recreated due to a configuration change.
To solve that, I would leverage the concept of "target fragments":
when (item.itemId) {
R.id.add -> {
val createDialog = CreateCollectionDialog()
createDialog.setTargetFragment(this, 0)
createDialog.show(fragmentManager!!, "")
return true
}
}
And now, in your other fragment, instead of having a listener field, you can just cast the targetFragment property:
if (!newCollectionName.equals("") && newCollectionName != null) {
val listener = targetFragment as CreateCollectionDialogListener
listener.onDialogPositiveClick(this, newCollectionName)
}
Problem seems to be with your fragmentManager!!
Try using childFragmentManager to open the DialogFragment.
Also, check if lateinit listener is actually initialized or not.
Related
I have a RecyclerView in a fragment. I need to initialize a fragment of adding a new item through a custom dialog. In the dialog, I need to get the text and uri of the image (in this case, for a start, only the text to make it easier to understand). I have problems passing data from Edit Text in Dialog Fragment.
My Activity with recycler view:
class AddExerciseActivity : AppCompatActivity(),NoticeDialogFragment.NoticeDialogListener {
private lateinit var recyclerAdapter: RecyclerViewAdapter
private lateinit var exercisesList: ArrayList<ExercisesModel>
private lateinit var recyclerview: RecyclerView
private lateinit var addNewExerciseButton : ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_new_exercise)
init()
addNewExerciseButton = findViewById(R.id.ib_addNewExercise)
addNewExerciseButton.setOnClickListener {
addNewExercise()
}
}
private fun init(){
exercisesList = ArrayList(ExercisesModel.createBaseList(20))
setupRecyclerView()
}
private fun setupRecyclerView() {
recyclerAdapter = RecyclerViewAdapter(this, exercisesList)
recyclerview = findViewById(R.id.recycler_view)
recyclerview.layoutManager = LinearLayoutManager(this)
recyclerview.adapter = recyclerAdapter
recyclerview.setHasFixedSize(true)
}
private fun addNewExercise(){
val dialog = NoticeDialogFragment()
dialog.show(supportFragmentManager, "NoticeDialogFragment")
//Todo how to add new items from dialog
recyclerAdapter.notifyDataSetChanged()
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
TODO("Not yet implemented")
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
TODO("Not yet implemented")
}
}
My Dialog Fragment:
class NoticeDialogFragment : DialogFragment() {
internal lateinit var listener: NoticeDialogListener
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
// 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 NoticeDialogListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater;
builder.setView(inflater.inflate(R.layout.dialog_signin, null))
// Add action buttons
.setPositiveButton(R.string.addExercise,
DialogInterface.OnClickListener { dialog, id ->
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
getDialog()?.cancel()
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
You need to add a FragmentResultListener to the FragmentManager. This will allow the DialogFragment to communicate with Fragment/Activity that created it.
Here is a great article on the break down https://proandroiddev.com/android-fragments-fragment-result-805a6b2522ea
If you do this you would be able to get rid of the NoticeDialogListener.
Once you have the FragmentResultListener setup then you need to make ExercisesModel Parcelable if not already. In the dialog the user gives all the necessary info to build a ExercisesModel. When they click confirm/ok you then call
val bundle = bundleOf(
"RESPONSE" to RESPONSE_OK, //Let the listener know ok/confirm was clicked.
"EXERCISE_MODEL" to createdExerciseModel
)
parentFragmentManager.setFragmentResult("CREATE_EXERCISE_MODEL", bundle)
Inside the original Fragment/Activity.
FragmentResultListener { requestKey: String, result: Bundle -> {
if ("CREATE_EXERCISE_MODEL".equals(requestKey)) {
var response = result.getCharSequence("RESPONSE")
if (response.equals(RESPONSE_OK)) {
var exerciseModel = result.getParcelable("EXERCISE_MODEL")
// You will need to add an add method to the
// adapter to allow adding to it internal list.
// Notify the changes to the adapter.
recyclerAdapter.add(exerciseModel)
}
}
}
}
I followed the official documentation to create a Dialog prompt on my app. It was working, but when I added the listener to process the data from the calling Fragment, I got a crash. It cannot instantiate the context as a listener. Here is some of my code :
The calling fragment :
class ConfigWifiFragment : Fragment(), BleManagerListener, WifiPasswordDialogFragment.NoticeDialogListener {
companion object {
fun newInstance() = ConfigWifiFragment()
}
private var _binding: FragmentConfigWifiBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var dialogPassword = WifiPasswordDialogFragment()
//... some code
private fun connectToWifiNetwork(ssid: String)
{
Log.i("Connection", ssid)
Log.i("Context", context.toString())
getFragmentManager()?.let { dialogPassword.show(it, "passwordWifi") }
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
TODO("Not yet implemented")
Log.i("ConfigWifi", dialog.config_wifi_password.text.toString())
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
TODO("Not yet implemented")
Log.i("ConfigWifi", "not ok")
}
}
The dialog:
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.DialogFragment
class WifiPasswordDialogFragment : DialogFragment() {
internal lateinit var listener: NoticeDialogListener
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
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 NoticeDialogListener // CRASH
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater;
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(inflater.inflate(R.layout.dialog_wifi_password, null))
// Add action buttons
.setPositiveButton(R.string.validation,
DialogInterface.OnClickListener { dialog, id ->
// sign in the user ...
Log.i("dialog", "ok")
listener.onDialogPositiveClick(this)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
//getDialog().cancel()
Log.i("dialog", "Cancel")
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
The code crash in that line: listener = context as NoticeDialogListener, it cannot get the context as a NoticeDialogListener even if it's implemented in the calling fragment (and then the exception is thrown). I checked the ID of the context before showing the dialogue prompt, and the one inside; it's the same.
The only difference between my code and the example is I'm using a Fragment and not a FragmentActivity. How could I make works my code in my case?
I found a solution, I don't know if it's the more elegant way, but here is it:
1st: I removed the onAttach function in my DialogFragment
2nd: I created a method to set the listener :
fun setListener(_listener : WifiPasswordDialogListener)
{
listener = _listener
}
3rd: I called that method just before the show method:
private fun connectToWifiNetwork(ssid: String)
{
Log.i("Connection", ssid)
Log.i("Context", context.toString())
dialogPassword.setListener(this)
getFragmentManager()?.let { dialogPassword.show(it, "passwordWifi") }
}
I can now access to the data of my DialogBox from the calling fragment like this :
override fun onDialogPositiveClick(dialog: DialogFragment) {
Log.i("ConfigWifi", dialog.dialog?.config_wifi_password?.text.toString())
}
How does one handle button clicks in a Dialog fragment. I would like to run method in fragment once a dialog button is clicked, the example given in dev docs works for activities but not fragments.
MyAlertDialog
class MyAlertDialog : DialogFragment() {
var listener: MyListener
interface MyListener {
onPositiveClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as MyListener
} catch (e: ClassCastException) {
throw ClassCastException((context.toString() + " must implement MyDailogListener")
}
}
override fun onCreateDialog(savedInstanceState:Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage("Do you want to reset the score.")
.setPositiveButton("Confirm",
DialogInterface.OnClickListener { dialog, id ->
...
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
MyFragment
class MyFragment : Fragment(), MyAlertDialog.MyListener {
...
fun launchAlertDialog() {
val dailog = MyAlertDialog().also {
it.show(requireActivity().supportFragmentManager, "DialogInfoFragment")
}
}
override fun onDialogPostiveCLick(dialog: DialogFragment) {
Log.i(TAG, "Listener returns a postive click")
}
}
The DialogFragment is launched from the activity not from the fragment despite being called in the fragment. Linking the interface this way will only work if you are connecting to an activity.
If you are only updating data, you could pass the ViewModel into the DialogFragment through the constructor.
Fragment
fun launchAlertDialog() {
val fragment = MyFragment(viewModel)
fragment.show(requireActivity().supportFragmentManager, "MyAlertDialog")
}
DialogFragment
class MyAlertDialog(val viewModel: MainViewModel) : DialogFragment() {
overrideOnCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(...)
.setPositiveButton("My Text", DialogInterface.OnClickListener { dialog, id ->
viewModel.updateMyData()
})
builder.create()
}
} ?: throw IllegalStateException("Activity cannot be null")
}
You could also use this approach here to run a function in the Fragment.
https://stackoverflow.com/a/65116644/3943340
Keep in mind that setTargetFragment is now deprecated, as of API 28(could be wrong on the API).
Using Anko library is pretty easy, but when i rotate screen, my dialog dismisses. The only way how to avoid this is to use child of DialogFragment() with method show(fm, TAG).
So we need to override method onCreateDialog(savedInstanceState: Bundle?): Dialog which returns Dialog instance. But Anko's alert{ }.build() returns DialogInterface instance
So, is there any way to use anko in this situation?
alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.show()
EDIT
So, that what I did.
I've created abstract BaseDialogFragment:
abstract class BaseDialogFragment : DialogFragment() {
abstract val ankoAlert: AlertBuilder<DialogInterface>
protected var dialogListener: DialogListener? = null
protected val vm by lazy {
act.getViewModel(DialogViewModel::class)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dialogListener = parentFragment as? DialogListener
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
(ankoAlert.build() as? Dialog)
?: error("Anko DialogInterface is no longer backed by Android Dialog")
}
Then I've created some dialogs, like that:
class MyDialogFragment : BaseDialogFragment() {
companion object {
fun create() = MyDialogFragment ()
}
override val ankoAlert: AlertBuilder<DialogInterface>
get() = alert {
negativeButton(R.string.app_canceled) {
dialogListener?.onDismiss?.invoke()
}
customView = createCustomView(vm.data)
}
fun createCustomView(data: Data): View {
//returning view
}
}
Also my DialogListener is sort of that:
interface DialogListener {
var onDismiss: () -> Unit
val onClick: (Data) -> Unit
var onPostClick: (Data) -> Unit
}
And finally, in parent fragment we can use:
MyDialogFragment.create().show(childFragmentManager, MyDialogFragment::class.java.simpleName)
Hope it will help somebody.
From the Android Documentation, Dialog implements DialogInterface. So all known subclasses of Dialog including AlertDialog implement that interface.
You can cast and return the result from the build as follows:
return alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.build() as Dialog
This will work but if Anko ever changes its implementation you will get a ClassCastException. To get a cleaner error you can use the following.
val dialogInterface = alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.build()
return (dialogInterface as? Dialog) ?: error("Anko DialogInterface is no longer backed by Android Dialog")
This gives you a more explicit error, but most likely won't be needed.
If you don't have dynamic UI then you can use android:configChanges="orientation" in your activity in manifest, look likes:
<activity android:name=".MainActivity"
android:configChanges="orientation">
...
</activity>
I am attempting to develop an Android application using Scala.
When my application launches it immediately displays a dialog as follows:
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
competitionSetupDialogFragment.show(getFragmentManager(), HomeScreenActivity.CompetitionDialog)
}
override def onStart() {
super.onStart
resourceHelper.setActivity(this)
}
My fragment is as follows:
class CompetitionSetupDialogFragment extends DialogFragment {
#Inject
var competitionSetupDialogHandler: CompetitionSetupDialogHandler = null
#Inject
var resourceHelper: ResourceHelper = null
override def onCreateDialog(savedInstanceState: Bundle): Dialog = {
val builder = new AlertDialog.Builder(getActivity())
builder.setMessage(getString(R.string.competitionSetupDialogMessage))
.setCancelable(false)
.setView(getActivity().getLayoutInflater.inflate(R.layout.competition_setup, null))
.setNegativeButton(R.string.cancel, new OnClickListener {
def onClick(dialog: DialogInterface, which: Int) {
dismiss()
}
})
.setPositiveButton(R.string.create, new OnClickListener {
def onClick(dialog: DialogInterface, which: Int) {
val competitionNameInput = resourceHelper.findViewById(R.id.competitionNameInput).asInstanceOf[EditText]
val numberOfPlayersInput = resourceHelper.findViewById(R.id.numberOfPlayersInput).asInstanceOf[EditText]
val numberOfSubsInput = resourceHelper.findViewById(R.id.numberOfSubsInput).asInstanceOf[EditText]
val lengthOfHalfInput = resourceHelper.findViewById(R.id.lengthOfHalfInput).asInstanceOf[EditText]
competitionSetupDialogHandler.retrieveValuesAndSave(competitionNameInput, numberOfPlayersInput, numberOfSubsInput, lengthOfHalfInput)
}
})
builder.create()
}
}
Lastly, the ResourceHelper contains the following:
public View findViewById(int viewID) {
return activity.findViewById(viewID);
}
My problem is that my resourceHelper.findViewById always returns null, even though these are valid views.
My thinking is that by the time the onClick() is invoked, the view has been destroyed, or something similar.
How can I get around this issue?
One thing to add here is that my dialog displays fine, it is only when onClick is performed that I have an issue
Resolved this as follows:
Created a dialog view variable:
var dialogView = getActivity().getLayoutInflater.inflate(R.layout.competition_setup, null)
Used this variable to call findViewById:
val competitionNameInput = dialogView .findViewById(R.id.competitionNameInput).asInstanceOf[EditText]