I am trying to convert a Java file to Kotlin just when I encounter this issue coming from the constructor parameter var view: View. I have seen similar issues but have not encountered one that uses a dialog as I have used here. It's important that I pass a view as a contractor parameter because I use that view for an important feature that depends on the dialog behavior.
Before converting to Kotlin
ackage com.indupendo.landing.ui.dialogs;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.indupendo.R;
import com.indupendo.databinding.DialogLoginBinding;
import com.indupendo.globals.utilities.Utils;
public class LoginDialog extends DialogFragment {
DialogLoginBinding binding;
View view;
public LoginDialog(View view) {
this.view = view;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
binding = DialogLoginBinding.inflate(getLayoutInflater());
MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(requireActivity(),
R.style.MaterialAlertDialog_rounded);
dialogBuilder.setView(binding.getRoot());
dialogBuilder.setNegativeButton("Cancel", (dialog, which) -> {
Utils.INSTANCE.showLoginCancellationSnackBar(view, getLayoutInflater());
dialog.cancel();
});
dialogBuilder.setPositiveButton("Login", (dialog, which) -> Toast.makeText(
getActivity(),
"Logged In",
Toast.LENGTH_LONG).show());
return dialogBuilder.create();
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
Utils.INSTANCE.showLoginCancellationSnackBar(view, getLayoutInflater());
dialog.cancel();
}
}
After conversion
package com.indupendo.landing.ui.fragments
import android.app.Dialog
import com.indupendo.globals.utilities.Utils.showLoginCancellationSnackBar
import android.os.Bundle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.indupendo.R
import android.content.DialogInterface
import android.view.View
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import com.indupendo.databinding.DialogLoginBinding
class LoginDialog(var view: View) : DialogFragment() {
var binding: DialogLoginBinding? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogLoginBinding.inflate(layoutInflater)
val dialogBuilder = MaterialAlertDialogBuilder(
requireActivity(),
R.style.MaterialAlertDialog_rounded
)
dialogBuilder.setView(binding!!.getRoot())
dialogBuilder.setNegativeButton("Cancel") { dialog: DialogInterface, which: Int ->
showLoginCancellationSnackBar(
view, layoutInflater
)
dialog.cancel()
}
dialogBuilder.setPositiveButton("Login") { dialog: DialogInterface?, which: Int ->
Toast.makeText(
activity,
"Logged In",
Toast.LENGTH_LONG
).show()
}
return dialogBuilder.create()
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
showLoginCancellationSnackBar(view, layoutInflater)
dialog.cancel()
}
}
How can I rewrite the Kotlin class and achieve the same result but without this bug?
CodePudding user response:
When you are using constructor with properties like this
class LoginDialog(var propertyName: View)
Kotlin is trying to create getters and setters for them. And their names will be getPropertyName() and setPropertyName().
In your example getter getView() is conflicting with the method from one of your base classes - Fragment.java:
/**
* Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
* if provided.
*
* @return The fragment's root view, or null if it has no layout.
*/
@Nullable
public View getView() {
return mView;
}
You have several options how to fix this error:
- Just change the property name
class LoginDialog(var myView: View)
- Replace property initialization with simple parameter and init the property manually
class LoginDialog(view: View) : DialogFragment() {
var myView: View = view
get() = field
set(value) {
field = value
}
- Annotate your property with the
@JvmFieldannotation. This will instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field
class LoginDialog(@JvmField internal val view: View) : DialogFragment() {
And a little off topic:
Though the options above could solve the error, there is more major issue with this class - sending a parameter to the Fragment constructor is a rather dangerous decision. From the Google spec:
All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.
You can follow this thread for more details
CodePudding user response:
I took @DmitryArc's answer above into consideration and thought of a safer solution following their PS: "All subclasses of Fragment must include a public no-argument constructor."
Instead of having to pass a constructor parameter, a way around this is having to access the view of the hosting activity, a button in my case, through requireActivity() function and findViewById(...).
package com.indupendo.landing.ui.dialogs
import android.app.Dialog
import com.indupendo.globals.utilities.Utils.showLoginCancellationSnackBar
import android.os.Bundle
import com.indupendo.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import android.content.DialogInterface
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import com.indupendo.databinding.DialogLoginBinding
class LoginDialog : DialogFragment() {
var binding: DialogLoginBinding? = null
private var snackBarView: Button? = null // This is required by snackBar as an argument
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
snackBarView = requireActivity().findViewById<View>(R.id.btn_login) as Button
binding = DialogLoginBinding.inflate(layoutInflater)
val dialogBuilder = MaterialAlertDialogBuilder(
requireActivity(),
R.style.MaterialAlertDialog_rounded
)
dialogBuilder.setView(binding!!.getRoot())
dialogBuilder.setNegativeButton("Cancel") { dialog: DialogInterface, _: Int ->
showLoginCancellationSnackBar(
snackBarView!!, layoutInflater
)
dialog.cancel()
}
dialogBuilder.setPositiveButton("Login") { _: DialogInterface?, _: Int ->
Toast.makeText(
activity,
"Logged In",
Toast.LENGTH_LONG
).show()
}
return dialogBuilder.create()
}
override fun onResume() {
super.onResume()
snackBarView = requireActivity().findViewById<View>(R.id.btn_login) as Button
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
showLoginCancellationSnackBar(snackBarView!!, layoutInflater)
dialog.cancel()
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
snackBarView = null
}
}
PS: Anyone is welcome to improve this answer!
