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
@JvmField
annotation. 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!