Callback from dialog to activity in Kotlin - android

I have been facing some issues while converting my android JAVA code into KOTLIN. I want to show a Material Dialog on some condition and want control back to activity as soon as the user clicks on dialog's button.
My java code:
public static void showAlertPopup(Context context, String title, String message) {
try {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(title != null ? title : context.getString(R.string.alertTitle));
builder.setMessage(message);
builder.setCancelable(false);
builder.setIcon(ContextCompat.getDrawable(context, R.drawable.ic_alert));
builder.setPositiveButton(context.getString(R.string.txtNeutralBtn),
(dialogInterface, i) -> dialogInterface.dismiss());
builder.setBackground(ContextCompat.getDrawable(context, R.drawable.dialog_background));
builder.show();
} catch (Exception e) {
DLiteLogger.WriteLog(MaterialDialogUtility.class, AppSettings.LogLevel.Error, e.getMessage());
}
}
My conversion to Kotlin:
fun showErrorPopup(
context: Context,
message: String?,
callback: OnPositiveButtonClickListener?
) {
MaterialAlertDialogBuilder(context).also {
it.setTitle(context.getString(R.string.errorTitle))
it.setMessage(message)
it.setCancelable(false)
it.setIcon(ContextCompat.getDrawable(context, R.drawable.ic_error))
it.setPositiveButton(context.getString(R.string.txtNeutralBtn))
{ dialogInterface: DialogInterface, _: Int ->
if (callback != null) {
dialogInterface.dismiss()
callback.onPositiveBtnClick(dialogInterface)
} else dialogInterface.dismiss()
}
it.background = ContextCompat.getDrawable(context, R.drawable.dialog_background)
it.show()
}
}
This is the interface I have created in same class:
interface OnPositiveButtonClickListener {
fun onPositiveBtnClick(dialog: DialogInterface?)
}
The issue I am facing currently for below code:
MaterialDialogUtility.showErrorPopup(this#LoginActivity,
getString(R.string.alertIncorrectPassword),
{ dialog ->
Objects.requireNonNull(binding.passwordEt.getText()).clear()
})
is
Type mismatch: inferred type is ([ERROR : ]) -> [ERROR : Cannot infer type variable TypeVariable(_L)] but MaterialDialogUtility.OnPositiveButtonClickListener? was expected

In order to use your interface as a lambda you'll have to convert it to a SAM interface a.k.a functional interface. So something like this:
fun interface OnPositiveButtonClickListener {
fun onPositiveBtnClick(dialog: DialogInterface?)
}
More description about functional interfaces here.
If you don't want to use SAM you can still use your current interface like this:
showErrorPopup(
this,
"Some String",
callback = object: OnPositiveButtonClickListener{
override fun onPositiveBtnClick(dialog: DialogInterface?) {
TODO("Not yet implemented")
}
}
)

Related

Class cast exception between parent and child classes

I waas develop an app in kotlin, when I get to the following error:
rise by the following check in the first line:
(it.responseBase as ValidateOtpResponse).let {resp -> // error rise here
if (resp.code == "200") {
val sucessDialog = GenericDialog(
context = requireContext(),
icon = R.drawable.ic_tick_green,
title = getString(R.string.change_password_title),
subtitle = getString(R.string.password_change_sucess),
buttonText = getString(R.string.understand),
cancelable = true,
clickListener = { (activity as DashboarActivity).redirectToLogin() }
)
sucessDialog.show(requireFragmentManager(), "sucess_otp_dialog")
} else {
showOtpError().also {
(activity as DashboarActivity).redirectToLogin()
}
}
}
and the arquitecture of the clases in the app is this:
data class ValidateOtpResponse(
#SerializedName("code")
val code: String
) : Serializable, ResponseBase()
and their parent:
open class ResponseBase : Serializable
Have this any sense? Because I being using this kind of cast along the app, and it's works until now
So if you can throw some light into this issue, take thanks in advance !
I try to apply the change which suggest Slaw, and Hakshay, and I've done something like this at repository level, which I guess it should works, but it doesn't:
Activity.class
(it.responseBase).let {resp ->
if ((resp as ValidateOtpResponse).code == "200") {
val sucessDialog = GenericDialog(
context = requireContext(),
icon = R.drawable.ic_tick_green,
title = getString(R.string.change_password_title),
subtitle = getString(R.string.password_change_sucess),
buttonText = getString(R.string.understand),
cancelable = true,
clickListener = { (activity as DashboarActivity).redirectToLogin() }
)
sucessDialog.show(requireFragmentManager(), "sucess_otp_dialog")
} else {
showOtpError().also {
(activity as DashboarActivity).redirectToLogin()
}
}
}
Repository.class
override fun onResponse(
call: Call<ValidateOtpResponse>,
response: Response<ValidateOtpResponse>
) {
/**
* We set as response the code, which tells if the call works: 200: OK - 400: KO
*/
if(response.isSuccessful){
response.let {
var value: WrapperResponse<ValidateOtpResponse> = WrapperResponse(response.body() as ValidateOtpResponse, ErrorBase(ErrorBase.ErrorType.NON_ERROR))
(value.responseBase as ValidateOtpResponse).code = response.code().toString()
Log.d("pass", "code after change: ${(value.responseBase as ValidateOtpResponse).code}")
validateChangePasswordOtpLD.postValue(value)
}
}else{
var error : ErrorBase
response.let {
error = ErrorBase(it.errorBody()!!.string(), it.code(), ErrorBase.ErrorType.STRING)
}
validateChangePasswordOtpLD.postValue((WrapperResponse(ResponseBase(), error)))
}
}
override fun onFailure(call: Call<ValidateOtpResponse>, t: Throwable) {
validateChangePasswordOtpLD.postValue(WrapperResponse(ResponseBase(), ErrorBase()))
}
I try to set the response from the API, and then modify the atribute of the response before set to the LD.
Although this changes, I still getting the cast exception when I try to recover the data into the activity.
Your response is of type ResponseBase which is the superclass. You are trying to cast it to type ValidateOtpResponse which is a subclass. You would not be able to cast an object of superclass into an object of the subclass.
For example:
You need to fetch the response of type ValidateOtpResponse.

Not receiving updates in StateFlow observable

I have two classes
class ChildFragment : ParentFragment {
viewModel.location.collectLatest { result ->
when (result) {
is LocationResult.Unit -> {}
is LocationResult.Error -> {}
is LocationResult.Success -> {} // Not recieving callback here.
}
}
}
class ChildViewModel : ParentViewModel
class ParentFragment
class ParentViewModel {
private val _location = MutableStateFlow<LocationResult>(LocationResult.Unit)
val location = _location.asStateFlow()
fun updateLocation(location: Location) {
_location.value = LocationResult.Success(location)
}
}
I am receiving the first Unit result in the flow callback but does not receive it once I call the update method.
Anything, in particular, that is being done wrong? Please help.
I would suggest at your fun updateLocation, implement also with the others like below
fun updateLocation(location: Location) {
try {
_location.value = LocationResult.Success(location)
} catch (e: Exception) {
_location.value = LocationResult.Error(e.message)
}
}
with this LocationResult.Error, you are able to know if there is any error or not. Please not to avoid any exception for futures.

Create a custom dialog with callback in Kotlin

I try to create a custom dialog and call it with a callback. Maybe it's not best practice but i dont have an idea to solve it better. This is the dialog:
private fun showDialog(header: String, message: String, callback: Callback? = null) {
val dialog = Dialog(this)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(R.layout.alert_layout)
val body = dialog.findViewById(R.id.text) as TextView
val title = dialog.findViewById(R.id.title) as TextView
body.text = message
title.text = header
val yesBtn = dialog.findViewById(R.id.button) as Button
//val noBtn = dialog.findViewById(R.id.noBtn) as TextView
yesBtn.setOnClickListener {
dialog.dismiss()
if(callback != null) {
callback // Here i want execute the callback
}
}
//noBtn.setOnClickListener { dialog.dismiss() }
dialog.show()
}
This is my callback and how i call the dialog:
val callback: Callback = object:Callback {
fun run() {
println("Callback executed")
}
}
showDialog("My Title", "My Text", callback)
My opinion was to call the callback as an object like
callback.run()
My question:
Should my code working and how do i call my callback, because callback.run() seems not working.
Instead of a Callback you can pass a Kotlin lambda function.
private fun showDialog(header: String, message: String, callback: (() -> Unit)? = null) {
...
yesBtn.setOnClickListener {
callback?.invoke() // Call that function
dismiss()
}
...
}
You can pass this lambda to showDialog by using a trailing lambda syntax.
showDialog("My Title", "My Text") {
println("Callback executed")
}

How to handle errors with liveData

In my app, I have this flow:
ClickListender in my fragment:
search_button.setOnClickListener {
if(search_input.text.isNullOrEmpty())
Toast.makeText(activity, "Input Error", Toast.LENGTH_LONG).show()
else
viewModel.onSearchButtonClicked(search_input.text.toString())
}
onSearchButtonClicked inside viewModel:
fun onSearchButtonClicked(input: String) {
coroutineScope.launch {
repo.insertToDatabase(input)
}
}
insertToDatabase inside Repository:
suspend fun insertToDatabase(string: String) {
withContext(Dispatchers.IO) {
val dataList =
ExternalApi.retrofitCall.getData(string).await()
if (dataList.intialDataResult < 1) {
//show error
} else {
//all good
database.myDataBase.insertAll(dataList)
}
}
}
I need to show error message if intialDataResult is less then one.
I thought about create MutableLiveData inside my repository with initial value of false and listen from the fragment through the viewModel, but it's not good approach because I have no way to set the LiveData to "false" again after I show error message.
I also tried to return bool from the insertToDatabase function and decide if to show error or not, with no success.
Any ideas how can I solve this?
Why not create a LiveData to manage your work's result state?
Create a class to store result of work why sealed class?
sealed class ResultState{
object Success: ResultState() // this is object because I added no params
data class Failure(val message: String): ResultState()
}
Create a LiveData to report this result
val stateLiveData = MutableLiveData<ResultState>()
Make insertToDatabase() return a result
suspend fun insertToDatabase(input: String): ResultState {
return withContext<ResultState>(Dispatchers.IO) {
val dataList =
ExternalApi.retrofitCall.getData(string).await()
if (dataList.intialDataResult < 1) {
return#withContext ResultState.Failure("Reason of error...")
} else {
database.myDataBase.insertAll(dataList)
return#withContext ResultState.Success
}
}
}
Now, report result to UI
fun onSearchButtonClicked(input: String) {
coroutineScope.launch {
val resultState = repo.insertToDatabase(input)
stateLiveData.value = resultState
}
}
In UI,
viewModel.stateLiveData.observe(viewLifeCycleOwner, Observer { state ->
when (state) {
is ResultState.Success -> { /* show success in UI */ }
is ResultState.Failure -> { /* show error in UI with state.message variable */ }
}
})
Similarly, you can add a ResultState.PROGRESS to show that a task is running in the UI.
If you have any queries, please add a comment.

How can I create a function that has when called, an addOnSuccessListener could be added to it

Not sure how to phrase this question. I would like to create a function, that when called, I can add addOnSuccessListener to it before continuing to the next one.
I know that when I have the function return a Task<Void> I can add to it the addOnSuccessListener but in the function itself, I am not sure what to return, as the operation I am executing is a simple process of saving EditText input into variables. Not sure what Task to return.
This is my function:
fun saveInput(): Task<Void> {
email = emailInput.text.toString()
phone = phoneInput.text.toString()
whatsApp = whatsAppInput.text.toString()
return //notSureWhatToReturnHere
}
And I want to be able to do something like this:
saveInput.onSuccess{
//do something
}
Something like this?
class Worker<T> {
private var successListener: ((result: T) -> Unit)? = null
fun onSuccess(result: T) {
successListener?.run { this(result) }
}
fun addSuccessListener(listener: (result: T) -> Unit): Worker<T> {
successListener = listener
return this
}
}
class MyRandomClass {
fun doSomething(variable: String): Worker<String> {
val worker: Worker<String> = Worker()
val result = variable.reversed()
worker.onSuccess(result)
return worker
}
}
//... in code
val randomClass = MyRandomClass()
randomClass.doSomething("Hello World")
.addSuccessListener {
Log.d(TAG, "Result is: $it")
}
}

Categories

Resources