Class cast exception between parent and child classes - android

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.

Related

Proper way of handle sealed class property in kotlin

Hey I am working in Android Kotlin. I am learning this LatestNewsUiState sealed class example from Android doc. I made my own sealed class example. But I am confused little bit, how can I achieved this. Is I am doing right for my scenario or not?
DataState.kt
sealed class DataState {
data class DataFetch(val data: List<Xyz>?) : DataState()
object EmptyOnFetch : DataState()
object ErrorOnFetch : DataState()
}
viewmodel.kt
var dataMutableStateFlow = MutableStateFlow<DataState>(DataState.EmptyOnFetch)
fun fetchData() {
viewModelScope.launch {
val result = repository.getData()
result.handleResult(
onSuccess = { response ->
if (response?.items.isNullOrEmpty()) {
dataMutableStateFlow.value = DataState.EmptyOnFetch
} else {
dataMutableStateFlow.value = DataState.DataFetch(response?.items)
}
},
onError = {
dataMutableStateFlow.value = DataState.ErrorOnFetch
}
)
}
}
fun fetchMoreData() {
viewModelScope.launch {
val result = repository.getData()
result.handleResult(
onSuccess = { response ->
if (response?.items.isNullOrEmpty()) {
dataMutableStateFlow.value = DataState.EmptyOnFetch
} else {
dataMutableStateFlow.value = DataState.DataFetch(response?.items)
}
},
onError = {
dataMutableStateFlow.value = DataState.ErrorOnFetch
}
)
}
}
Activity.kt
lifecycleScope.launchWhenStarted {
viewModel.dataMutableStateFlow.collectLatest { state ->
when (state) {
is DataState.DataFetch -> {
binding.group.visibility = View.VISIBLE
}
DataState.EmptyOnFetch,
DataState.ErrorOnFetch -> {
binding.group.visibility = View.GONE
}
}
}
}
}
I have some points which I want to achieve in the standard ways.
1. When your first initial api call fetchData() if data is not null or empty then we need to show view. If data is empty or null then we need to hide the view. But if api fail then we need to show an error message.
2. When view is visible and view is showing some data. Then we call another api fetchMoreData() and data is empty or null then I don't want to hide view as per code is written above. And If api fails then we show error message.
Thanks in advance

Implementing Google places autoComplete textfield implementation in jetpack compose android

Did anyone implement google autocomplete suggestion text field or fragment in a jetpack compose project? If so kindly guide or share code snippets as I'm having difficulty in implementing it.
Update
Here is the intent that I'm triggering to open full-screen dialog, but when I start typing within it gets closed, and also I'm unable to figure out what the issue is and need a clue about handling on activity result for reading the result of the predictions within this compose function.
Places.initialize(context, "sa")
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete.IntentBuilder(
AutocompleteActivityMode.FULLSCREEN,fields).build(context)
startActivityForResult(context as MainActivity,intent, AUTOCOMPLETE_REQUEST_CODE, Bundle.EMPTY)
I am using the MVVM architecture and this is how I implemented it:
GooglePlacesApi
I've created an api for reaching google api named GooglePlacesApi
interface GooglePlacesApi {
#GET("maps/api/place/autocomplete/json")
suspend fun getPredictions(
#Query("key") key: String = <GOOGLE_API_KEY>,
#Query("types") types: String = "address",
#Query("input") input: String
): GooglePredictionsResponse
companion object{
const val BASE_URL = "https://maps.googleapis.com/"
}
}
The #Query("types") field is for specifiying what are you looking for in the query, you can look for establishments etc.
Types can be found here
Models
So I created 3 models for this implementation:
GooglePredictionsResponse
The way the response looks if you are doing a GET request with postman is:
Google Prediction Response
You can see that we have an object with "predictions" key so this is our first model.
data class GooglePredictionsResponse(
val predictions: ArrayList<GooglePrediction>
)
GooglePredictionTerm
data class GooglePredictionTerm(
val offset: Int,
val value: String
)
GooglePrediction
data class GooglePrediction(
val description: String,
val terms: List<GooglePredictionTerm>
)
I only needed that information, if you need anything else, feel free to modify the models or create your own.
GooglePlacesRepository
And finally we create the repository to get the information (I'm using hilt to inject my dependencies, you can ignore those annotations if not using it)
#ActivityScoped
class GooglePlacesRepository #Inject constructor(
private val api: GooglePlacesApi,
){
suspend fun getPredictions(input: String): Resource<GooglePredictionsResponse>{
val response = try {
api.getPredictions(input = input)
} catch (e: Exception) {
Log.d("Rently", "Exception: ${e}")
return Resource.Error("Failed prediction")
}
return Resource.Success(response)
}
}
Here I've used an extra class I've created to handle the response, called Resource
sealed class Resource<T>(val data: T? = null, val message: String? = null){
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data:T? = null): Resource<T>(data = data, message = message)
class Loading<T>(data: T? = null): Resource<T>(data = data)
}
View Model
Again I'm using hilt so ignore annotations if not using it.
#HiltViewModel
class AddApartmentViewModel #Inject constructor(private val googleRepository: GooglePlacesRepository): ViewModel(){
val isLoading = mutableStateOf(false)
val predictions = mutableStateOf(ArrayList<GooglePrediction>())
fun getPredictions(address: String) {
viewModelScope.launch {
isLoading.value = true
val response = googleRepository.getPredictions(input = address)
when(response){
is Resource.Success -> {
predictions.value = response.data?.predictions!!
}
}
isLoading.value = false
}
}
fun onSearchAddressChange(address: String){
getPredictions(address)
}
}
If you need any further help let me know
I didn't include UI implementation because I assume it is individual but this is the easier part ;)
#Composable
fun MyComponent() {
val context = LocalContext.current
val intentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
when (it.resultCode) {
Activity.RESULT_OK -> {
it.data?.let {
val place = Autocomplete.getPlaceFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
AutocompleteActivity.RESULT_ERROR -> {
it.data?.let {
val status = Autocomplete.getStatusFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
Activity.RESULT_CANCELED -> {
// The user canceled the operation.
}
}
}
val launchMapInputOverlay = {
Places.initialize(context, YOUR_API_KEY)
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete
.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
.build(context)
intentLauncher.launch(intent)
}
Column {
Button(onClick = launchMapInputOverlay) {
Text("Select Location")
}
}
}

Return a value from a Listeners onSuccess() in Kotlin (Android)

I try these tutorials: https://github.com/docusign/mobile-android-sdk/blob/master/README.md, especially the function getUserSignatureInfo. In this function a REST API call (userSignaturesGetUserSignature) is made.
In my code below I try to return a value (userSignatureId) I get from REST API. I understand, it's impossible this way, because onSuccess() will be invoked later as the outer function getUserSignatureInfo() returns.
I want to call getUserSignatureInfo() from a Fragments onActivityCreated() and use this value on creating a RecyclerView.Adapter.
The question is, what is the (best practice) way to do something like this: make a REST API call, wait for response, and use the response in further code.
// my Fragment
...
...
val userSignatureId = getUserSignatureInfo()
recyclerView.adapter = createMyAdapter(userSignatureId)
...
...
// function where the REST API call is made
fun getUserSignatureInfo(context: Context) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
var userSignatureId = ""
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// My problem: this assignment is useless
// because the outer function getUserSignatureInfo()
// returns earlier as onSuccess()
userSignatureId = userSignature.signatureId
}
}
override fun onError(exception: DSRestException) {
// TODO: Handle error
}
}) {
usersApi!!.userSignaturesGetUserSignature(user.accountId, user.userId, "signature")
}
// This is my problem: userSignatureId is empty because
// onSuccess() fires later as this function returns
return userSignatureId
}
Thank you much!
You could pass a callback into getUserSignatureInfo(), for example
fun getUserSignatureInfo(context: Context, callback: (String)->Unit) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// return the value in the callback
callback(userSignature.signatureId)
}
}
When you want to use the string value from another class,
getUserSignatureInfo(context) { id ->
Log.d("test", id)
}

Fragment popbackstack trigger lifecyclescope collect

Situation
I submit data setTripDeliver, the collect works fine (trigger LOADING and then SUCCESS). I pressed a button go to next fragment B (using replace). After that, I press back button (using popbackstack). the collect SUCCESS triggered.
Codes Related
These codes at the FragmentA.kt inside onViewCreated.
private fun startLifeCycle() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
}
}
}
These codes when to submit data at a button setOnClickListener.
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.setTripDeliver(
verificationCode,
remark
)
}
Method to collect flow collectTripReattempt()
private suspend fun collectTripReattempt() {
viewModel.tripReattempt.collect {
when (it) {
is Resource.Initialize -> {
}
is Resource.Loading -> {
Log.i("???","collectTripReattempt loading")
handleSaveEarly()
}
is Resource.Success -> {
val error = it.data?.error
if (error == null) {
Tools.showToast(requireContext(), "Success Reattempt")
Log.i("???","collectTripReattempt Success")
} else {
Tools.showToast(requireContext(), "$error")
}
handleSaveEnding()
}
is Resource.Error -> {
handleSaveEnding()
}
}
}
}
Below codes are from ViewModel.
private val _tripDeliver =
MutableStateFlow<Resource<TripDeliverResponse>>(Resource.Initialize())
val tripDeliver: StateFlow<Resource<TripDeliverResponse>> = _tripDeliver
This method to call repository.
suspend fun setTripDeliver(
verificationCode: String?,
remark: String?
) {
_tripDeliver.value = Resource.Loading()
try {
val result = withContext(ioDispatcher) {
val tripDeliverParameter = DeliverParameter(
verificationCode,
remark
)
val response = appRepository.setTripDeliver(tripDeliverParameter)
Resource.getResponse { response }
}
_tripDeliver.value = result
} catch (e: Exception) {
when (e) {
is IOException -> _tripDeliver.value =
Resource.Error(messageInt = R.string.no_internet_connection)
else -> _tripDeliver.value =
Resource.Error("Trip Deliver Error: " + e.message)
}
}
}
Logcat
2021-07-09 19:56:10.946 7446-7446/com.package.app I/???: collectTripReattempt loading
2021-07-09 19:56:11.172 7446-7446/com.package.app I/???: collectTripReattempt Success
2021-07-09 19:56:17.703 7446-7446/com.package.app I/???: collectTripReattempt Success
As you can see, the last Success is called again AFTER I pressed back button (popbackstack)
Question
How to make it trigger once only? Is it the way I implement it is wrong? Thank you in advance.
This is not problem of your implementation this is happening because of stateIn() which use used in your viewModel to convert regular flow into stateFlow
If according to your code snippet the success is triggered once again, then why not loading has triggered?
as per article, it is showing the latest cached value when you left the screen and came back you got the latest cached value on view.
Resource:
https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb
The latest value will still be cached so that when the user comes back to it, the view will have some data immediately.
I have found the solution, thanks to #Nurseyit Tursunkulov for giving me a clue. I have to use SharedFlow.
At the ViewModel, I replace the initialize with these:
private val _tripDeliver = MutableSharedFlow<Resource<TripDeliverResponse>>(replay = 0)
val tripDeliver: SharedFlow<Resource<TripDeliverResponse>> = _tripDeliver
At the replay I have to use 0, so this SharedFlow will trigger once. Next, change _tripDeliver.value to _tripDeliver.emit() like the codes below:
fun setTripDeliver(
verificationCode: String?,
remark: String?
) = viewModelScope.launch {
_tripDeliver.emit(Resource.Loading())
if (verificationCode == null && remark == null) {
_tripDeliver.emit(Resource.Error("Remark cannot be empty if verification is empty"))
return#launch
}
try {
val result = withContext(ioDispatcher) {
val tripDeliverParameter = DeliverParameter(
verificationCode,
remark,
)
val response = appRepository.setTripDeliver(tripDeliverParameter)
Resource.getResponse { response }
}
_tripDeliver.emit(result)
} catch (e: Exception) {
when (e) {
is IOException -> _tripDeliver.emit(Resource.Error(messageInt = R.string.no_internet_connection))
else -> _tripDeliver.emit(Resource.Error("Trip Deliver Error: " + e.message))
}
}
}
I hope this answer will help the others also.
I think this is because of coldFlow, you need a HotFlow. Another option is to try to hide and show fragment, instead of replacing. And yet another solution is to keep this code in viewModel.
In my opinion, I think your way of using coroutines in lifeScope is incorrect. After the lifeScope status of FragmentA is at Started again, the coroutine will be restarted:
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
So I think: You need to modify this way:
private fun startLifeCycle() {
viewLifecycleOwner.lifecycleScope.launch {
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
}
}

retrofit - kotlin - Parameter specified as non-null is null

I'm using mvvm , kotlin , retrofit and courtin in my app . I've done several request and all of them works fine but with this one , I get this error "Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter list"
this is my json
{
"roomslist": [
{
"id": "1"
}
]
}
these are my models
data class RoomsListModel(
#Json(name = "roomslist")
val roomsList: List<Rooms>
)
data class Rooms(
#Json(name = "id")
val id: String
}
this is my api interface :
#FormUrlEncoded
#POST("getPlaceRooms.php")
fun getPlaceRooms2(#Field("amlakid")id:String):Deferred<RoomsListModel>
this is my repository :
fun getRooms(
amlakId: String
): MutableLiveData<RoomsListModel> {
scope.launch {
val request = api.getPlaceRooms2(amlakId)
withContext(Dispatchers.Main) {
try {
val response = request.await()
roomsLiveData.value = response
} catch (e: HttpException) {
Log.v("this", e.message);
} catch (e: Throwable) {
Log.v("this", e.message);
}
}
}
return roomsLiveData;
}
when the app runs , it goes into e: Throwable and returns the error
my viewmodel
class PlacesDetailsViewModel : ViewModel() {
private val repository = PlaceDetailsRepository()
fun getRooms(amlakId: String):MutableLiveData<RoomsListModel> {
return repository.getRooms(amlakId)
}
}
and this my activity request :
viewModel.getRooms(amlakId).observe(this, Observer {
vf.frm_loading.visibility = View.GONE
it?.let {
adapter.updateList(it?.roomsList)
setNoItem(false)
}
})
I'm using moshi
I've tried to clean ,rebuild but it doesn't make any different
could you help me ?
what is going wrong with my code?
You should try adding ? to your Model parameters. Not sure if in your case is the String?. It will ensure that you can have null values on your String
val id: String?
Please double check, whatever value is missing or null in your case
Have you tried removing #Json annotation in your val id: String declaration?

Categories

Resources