I m relatively new in kotlin flows and I m creating the Login Module using Flows in android. I have been stuck from past few days in flows as I m collecting it in ViewModels but I m facing problem when requesting with wrong Credentials its caching all the state. After entering the right credentials the user navigate to main Activity but the instance of the MainActivity is being created with every emitted State: Example(User Enter 3 wrong Credential and 1 Right Credential: 4 Instance of MainActivity Created). So, Is there any way that I can cancel the previous emit and only show the latest request. I m using the collectLatest as well but its not working too. Below is the code.
LoginActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mViewBinding.root)
loginListener()
}
override fun onStart() {
super.onStart()
initViews()
handleNetworkChanges()
}
private fun observeLogin() {
lifecycleScope.launchWhenCreated {
mViewModel.loginCredentials.collect { state ->
when(state){
is State.Loading -> {
showLoading()
}
is State.Success -> {
Timber.d("I m in Success" state.data)
val intent = Intent(this@LoginActivity,MainActivity::class.java)
startActivity(intent)
closeLoading()
finish()
}
is State.Error -> {
val errorResponse = gson.fromJson(state.message,LoginResponse::class.java)
showToast(errorResponse.messages)
closeLoading()
}
}
}
}
}
private fun loginListener() {
mViewBinding.neumorphButtonSignIn.setOnClickListener {
observeLogin()
phoneNumber = mViewBinding.edtPhoneNumber.text.toString()
pin = mViewBinding.oldPIN.text.toString()
if (phoneNumber.isValidPhone()) {
sendLoginCredentials(phoneNumber ,pin)
}
else {
mViewBinding.edtPhoneNumber.snack("Please Enter valid phone number") {
action("ok") {
dismiss()
}
}
}
}
}
private fun sendLoginCredentials(phoneNumber: String , pin: String) = mViewModel.postLoginCredentials("03XXXX" , "1234")
LoginViewModel
@ExperimentalCoroutinesApi
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginRepository: LoginRepository,
) : ViewModel() {
private val _loginCredentials: MutableStateFlow<State<LoginResponse>> = MutableStateFlow(State.Empty())
val loginCredentials: StateFlow<State<LoginResponse>> get() = _loginCredentials
fun postLoginCredentials(phoneNumber: String, pin: String) {
Timber.d("postLoginCredentials: $phoneNumber $pin")
_loginCredentials.value = State.loading()
viewModelScope.launch {
loginRepository.login(LoginRequest(phoneNumber,pin))
.map { response -> State.fromResource(response) }
.collect{state -> _loginCredentials.value = state }
}
}
}
LoginRepository
class LoginRepository @Inject constructor(
private val apiInterface: APIInterface
) {
fun login(loginRequest: LoginRequest): Flow<ResponseAPI<LoginResponse>> {
return object : NetworkBoundRepository<LoginRequest, LoginResponse>() {
override suspend fun fetchFromRemote(): Response<LoginResponse> = apiInterface.createLoginRequest(
loginRequest
)
}.asFlow()
}
NetworkBoundRepository
abstract class NetworkBoundRepository<RESULT, REQUEST> {
fun asFlow() = flow<ResponseAPI<REQUEST>> {
val apiResponse = fetchFromRemote()
val remotePosts = apiResponse.body()
if (apiResponse.isSuccessful && remotePosts != null) {
emit(ResponseAPI.Success(remotePosts))
} else {
// Something went wrong! Emit Error state.
emit(ResponseAPI.Failed(apiResponse.errorBody()!!.string()))
}
}.catch { e ->
e.printStackTrace()
emit(ResponseAPI.Failed("Network error! Can't get latest posts."))
}
@MainThread
protected abstract suspend fun fetchFromRemote(): Response<REQUEST>
}
Is there any way that I can create One Instance of MainAcitivity while ignoring the older emitted Responses? Any Operator which can work. Any help in this regard is highly appreciated. Thanks.
CodePudding user response:
Actually, I was calling the observeLogin()
from the login click Listener which was creating this mess in my project when I move this to onCreate()
. Everything works the way as intended. So, posting this for newbie that won't stuck into this.