Live Data Observer called only once _ Android - android

Live Data Observer called only once. It is not updating the data from server when api is called again to update UI.
Here is my activity:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onClick(){
callAPI(binding.edtEMail.text.toString(), binding.edtPasswords.text.toString())
}
}
private fun callAPI(userName: String, password: String) {
var factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LoginViewModel(
networkAvailable,
application,
getLoginUseCase,
userName,
password
) as T
}
}
val loginViewModel: LoginViewModel by lazy {
ViewModelProvider(this, factory)[LoginViewModel::class.java]
}
loginViewModel.loginMainEntity.observe(this, Observer {
when (it) {
is Resource.Success -> {
it.data?.let { it ->
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
is Resource.Error -> {
it.message?.let { it ->
when (it) {
getString(R.string.invalid_login) -> {
Toast.makeText(this, R.string.error_user_pass, Toast.LENGTH_LONG)
.show()
}
getString(R.string.service_not_available) -> {
Toast.makeText(
this,
R.string.error_service_not_available,
Toast.LENGTH_LONG
)
.show()
}
else -> {
Toast.makeText(this, it, Toast.LENGTH_LONG)
.show()
}
}
}
}
}
})
}
}
And here is LoginViewModel:
class LoginViewModel constructor
(
private val networkAvailable: NetworkAvailable,
private val app: Application,
private val getLoginUseCase: GetLoginUseCase,
private val userName: String,
private val password: String
) : ViewModel() {
private val _loginMainEntity = MutableLiveData<Resource<LoginMainEntity>>()
val loginMainEntity: LiveData<Resource<LoginMainEntity>>
get() = _loginMainEntity
init {
loginValues()
}
private fun loginValues() {
viewModelScope.launch {
try {
_loginMainEntity.postValue(Resource.Loading())
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.unknown)))
}
try {
if (networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(getLoginUseCase.execute(userName, password))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
} else if (!networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
}
}
} catch (e: Exception) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
}
}
}
}

Related

android parallel API requests with retrofit and coroutines

I have multiple API requests that need to be called in parallel, the sequence doesn't matter.
What really matters is that all the calls should be requested to implement the UI.
The problem is that sometimes some requests don't get called which returns null values, in other words, NO GUARANTEE THAT ALL THE REQUESTS WILL BE CALLED.
I have read a lot about parallel API requests and launchers but still don't know what I have missed or what I did wrong.
here is my view model class that has all functions
#HiltViewModel
class MatchDetailsViewModel #Inject constructor(
val api:FootballApi,
val app: Application,
): AndroidViewModel(app) {
private val _matchDetailsMutableLiveData = MutableLiveData<ResponseState<FixtureById>>()
private val _matchStatsMutableLiveData = MutableLiveData<ResponseState<Stats>>()
private val _matchLineupsMutableLiveData = MutableLiveData<ResponseState<Lineups>>()
private val _matchBenchMutableLiveData = MutableLiveData<ResponseState<Bench>>()
private val _matchSideLinesMutableLiveData = MutableLiveData<ResponseState<SideLine>>()
private val _matchStandingsMutableLiveData = MutableLiveData<ResponseState<Standings>>()
val matchStandingsLiveData: LiveData<ResponseState<Standings>> = _matchStandingsMutableLiveData
val matchSideLinesLiveData: LiveData<ResponseState<SideLine>> = _matchSideLinesMutableLiveData
val matchStatsLiveData: LiveData<ResponseState<Stats>> = _matchStatsMutableLiveData
val matchDetailsLiveData: LiveData<ResponseState<FixtureById>> = _matchDetailsMutableLiveData
val matchLineupsLiveData: LiveData<ResponseState<Lineups>> = _matchLineupsMutableLiveData
val matchBenchLiveData: LiveData<ResponseState<Bench>> = _matchBenchMutableLiveData
fun callAll() {
viewModelScope.launch {
val getMatchDetailsCall = async { getMatchDetails(1582601) }
val getMatchStatsCall = async { getMatchStats(1582601) }
val getMatchLineupsCall = async { getMatchLineups(1582601) }
val getMatchBenchCall = async { getMatchBench(1582601) }
val getMatchSideLineCall = async { getMatchSideLine(15006543) }
val getMatchStandingsCall = async { getMatchStandings(12880) }
try {
getMatchDetailsCall.await()
getMatchStatsCall.await()
getMatchLineupsCall.await()
getMatchBenchCall.await()
getMatchSideLineCall.await()
getMatchStandingsCall.await()
}
catch (_: Exception){}
}
}
suspend fun getMatchStandings(seasonId: Int) = viewModelScope.launch(Dispatchers.IO) {
_matchStandingsMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchStandings(seasonId = seasonId)
Log.i("getMatchStanding()", response.body().toString())
_matchStandingsMutableLiveData.postValue(ResponseState.Success(response.body()!!))
}
catch (exception: Exception) {
Log.e("getMatchStanding()", exception.toString())
}
}
suspend fun getMatchSideLine(id: Int) = viewModelScope.launch(Dispatchers.IO) {
_matchSideLinesMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchSideLines(id = id)
Log.i("getMatchSideline()", response.body().toString())
_matchSideLinesMutableLiveData.postValue(ResponseState.Success(response.body()!!))
} catch (exception: Exception) {
Log.e("getMatchSideline()", exception.toString())
}
}
fun getMatchDetails(id: Int) = viewModelScope.launch(Dispatchers.IO) {
_matchDetailsMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchDetails(id = id)
Log.i("getMatchDetails()", response.body().toString())
_matchDetailsMutableLiveData.postValue(ResponseState.Success(response.body()!!))
} catch (exception: Exception) {
Log.e("getMatchDetails()", exception.toString())
}
}
suspend fun getMatchStats(id: Int) = viewModelScope.launch(Dispatchers.IO) {
_matchStatsMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchStats(id = id)
Log.i("getMatchStats()", response.body().toString())
_matchStatsMutableLiveData.postValue(ResponseState.Success(response.body()!!))
} catch (exception: Exception) {
Log.e("getMatchStats()", exception.toString())
}
}
suspend fun getMatchLineups(id: Int = 6) = viewModelScope.launch(Dispatchers.IO) {
_matchLineupsMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchLineups(id = id)
Log.i("getMatchLineups()", response.body().toString())
_matchLineupsMutableLiveData.postValue(ResponseState.Success(response.body()!!))
} catch (exception: Exception) {
Log.e("getMatchLineups()", exception.toString())
}
}
suspend fun getMatchBench(id: Int) = viewModelScope.launch(Dispatchers.IO) {
_matchBenchMutableLiveData.postValue(ResponseState.Loading())
try {
val response = api.getMatchBench(id = id)
Log.i("getMatchBench()", response.body().toString())
_matchBenchMutableLiveData.postValue(ResponseState.Success(response.body()!!))
} catch (exception: Exception) {
Log.e("getMatchBench()", exception.toString())
}
}
}
And here's my call in activity class
lifecycleScope.launch(Dispatchers.IO) {
matchDetailsViewModel.callAll()
}

Firebase Auth with Kotlin Flow

I am learning clean architecture and Kotlin Flow. I want to check is user mail exists in the Firebase Auth base. However, when I threw an error to the flow function, app is crash.
CheckUserUseCase.kt
class CheckUserUseCase #Inject constructor(private val repository: SignInRepository) {
operator fun invoke(mail: String): Flow<Status<Boolean, String>> = flow {
emit(Status.Loading(data = null))
try {
repository.isUserExists(mail = mail)
emit(Status.Success(data = true))
} catch (e: Exception) {
emit(Status.Error(message = e.message, data = false))
}
}
}
SignInRepository.kt
interface SignInRepository {
suspend fun isUserExists(mail: String)
}
SignInRepositoryImpl.kt
class SignInRepositoryImpl #Inject constructor(private val firebaseUserActions: FirebaseUserActions) : SignInRepository {
override suspend fun isUserExists(mail: String) {
firebaseUserActions.isUserExists(mail = mail)
}
}
FirebaseAuthentication.kt
class FirebaseAuthentication #Inject constructor(private val auth: FirebaseAuth) : FirebaseUserActions {
override suspend fun isUserExists(mail: String){
auth.fetchSignInMethodsForEmail(mail).addOnCompleteListener { task ->
task.result.signInMethods?.let {
if (it.size != 0) Log.i("App.tag", "True.")
else throw IOException() <-- Crash point.
}
}.addOnFailureListener { e -> e.printStackTrace() }
.await()
}
}
How can I return a state to Kotlin Flow method? Thank you!
Please try the following approach:
override suspend fun isUserExists(mail: String): Status {
return try {
val result = auth.fetchSignInMethodsForEmail(mail).await()
result.signInMethods?.let {
if (it.isNotEmpty()) {
Status.Success(data = true)
} else {
Status.Error(message = "No data", data = false)
}
} ?: Status.Error(message = "No Data", data = false)
} catch (e: Exception) {
Status.Error(message = e.message, data = false)
}
}
In CheckUserUseCase class just emit the result of calling isUserExists():
emit(Status.Loading(data = null))
emit(repository.isUserExists(mail = mail))
Try
it.size != 0 && it.size != null
and
if (task.isSuccessful()) {
[...]
task.result.signInMethods?.let {
[...]
}

Capture suspend lambda parameter with generic result in a mockked class

I want to know if it is possible to capture an suspend lambda with generic result with MockK and JUnit5.
I've tried several ways, and in the most recent I'm having a KotlinNullPointerException when I try to run the test.
Here is the code, with the complete class dependencies and test:
class Test {
data class Result<out T>(val status: ResultStatus, val data: T?, val exception: Exception?) {
companion object {
fun <T> success(data: T?): Result<T> {
return Result(ResultStatus.SUCCESS, data, null)
}
fun <T> error(exception: Exception): Result<T> {
return Result(ResultStatus.ERROR, null, exception)
}
fun <T> loading(data: T? = null): Result<T> {
return Result(ResultStatus.LOADING, data, null)
}
}
}
class Dep1() {
fun <R> methodToMock(viewModelScope: CoroutineScope, block: suspend CoroutineScope.() -> R): MutableLiveData<Result<R>> {
val result = MutableLiveData<Result<R>>()
result.value = Result.loading()
viewModelScope.launch {
try {
var asyncRequestResult: R? = null
withContext(Dispatchers.Default) {
asyncRequestResult = block()
}
result.value = Result.success(asyncRequestResult)
} catch (cancellationException: CancellationException) {
} catch (exception: Exception) {
result.value = Result.error(exception)
}
}
return result
}
}
class Dep2() {
fun methodToAssert(): Boolean {
println("Called")
return true
}
}
class ClassToTest(private val dep1: Dep1, private val dep2: Dep2) {
fun methodToCall(coroutineScope: CoroutineScope): MutableLiveData<Result<Boolean>> {
return dep1.methodToMock(coroutineScope) {
dep2.methodToAssert()
}
}
}
private val dep1: Dep1 = mockk()
private val dep2: Dep2 = mockk(relaxed = true)
private val mViewModelScope: CoroutineScope = GlobalScope
#Test
fun `Check if is calling the required methods correctly`() {
val classToTest = ClassToTest(dep1, dep2)
val transactionLambda = slot<suspend CoroutineScope.() -> Boolean>()
coEvery { dep1.methodToMock(mViewModelScope, capture(transactionLambda)) } coAnswers {
MutableLiveData(Result.success(transactionLambda.captured.invoke(mViewModelScope)))
}
classToTest.methodToCall(mViewModelScope)
verify { dep2.methodToAssert() }
}
}
If anyone is also having this problem, I was able to solve it using every instead of coEvery and calling the coInvoke() slot method:
every { dep1.methodToMock(mViewModelScope, capture(transactionLambda)) } answers {
MutableLiveData(Result.success(transactionLambda.coInvoke(mViewModelScope)))
}

How can I convert LiveData to 2 pieces of liveData?

I have configLiveData:LiveData<Response<ConfigFile>> where Response is
sealed class Response<out T> {
data class Success<out T>(val data: T) : Response<T>()
data class Failure<out T>(val message: String) : Response<T>()
}
now in ViewModel I want to transform configLiveData to two different
LiveDatas 1.LiveData<ConfigFile> and 2. LiveData<String> and as a result of transformation one of them will be empty.
but I want to get ride of LiveData<Response<ConfigFile>> and have instead of it LiveData<ConfigFile>
override suspend fun fetchConfigFile(): Response<ConfigFile> {
return suspendCoroutine { cont ->
EnigmaRiverContext.getHttpHandler().doHttp(AppConstants.getPath().append("config/appConfig.json").toURL(),
JsonHttpCall("GET"), object : JsonReaderResponseHandler() {
override fun onSuccess(jsonReader: JsonReader) {
try {
val successObject = ApiConfigFile(jsonReader)
cont.resume(Response.Success(successObject.toPresenterModel()))
} catch (e: IOException) {
cont.resume(Response.Failure(e.message))
} catch (e: Exception) {
cont.resume(Response.Failure(e.message ))
}
}
override fun onError(error: Error?) {
cont.resume(Response.Failure("error.message"))
}
})
}
}
It is how my Repository looks like
private fun fetchConfig() {
uiScope.launch {
when (val result = homeRepository.fetchConfigFile()) {
is Response.Success<ConfigFile> -> {
postValue(Response.Success(result.data))
}
is Response.Failure -> postValue(Response.Failure(result.message))
}
}
}
class ConfigFileLiveData #Inject constructor(val homeRepository: IHomeRepository) : LiveData<Response<ConfigFile>>() {
private val liveDataJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + liveDataJob)
override fun onActive() {
fetchConfig()
}
private fun fetchConfig() {
viewModelScope.launch {
when (val result = homeRepository.fetchConfigFile()) {
is Response.Success<ConfigFile> -> {
postValue(Response.Success(result.data))
}
is Response.Failure -> postValue(Response.Failure(result.message))
}
}
}
}
I have `ConfigFileLiveData` which is singleton and I want to use this liveData in other viewModels as I need to fetch config once and use it in different ViewModels
class ConfigFileLiveData #Inject constructor(val homeRepository: IHomeRepository) : LiveData<Response<ConfigFile>>() {
override fun onActive() {
fetchConfig()
}
private fun fetchConfig() {
viewModelScope.launch {
when (val result = homeRepository.fetchConfigFile()) {
is Response.Success<ConfigFile> -> {
postValue(Response.Success(result.data))
}
is Response.Failure -> postValue(Response.Failure(result.message))
}
}
}
}
In Viewmodel Define two LiveData variable.
private var configLiveData = MutableLiveData<ConfigFile>()
private var stringLiveData = MutableLiveData<String>()
Modify this method
private fun fetchConfig() {
uiScope.launch {
when (val result = homeRepository.fetchConfigFile()) {
is Response.Success<ConfigFile> -> {
configLiveData.value = Response.Success(result.data)
}
is Response.Failure -> {
stringLiveData.value = Response.Failure(result.message)
}
}
}
}

Android ViewState using RxJava or kotlin coroutines

I'm trying to learn how to use RxJava in Android, but have run into a dead end. I have the following DataSource:
object DataSource {
enum class FetchStyle {
FETCH_SUCCESS,
FETCH_EMPTY,
FETCH_ERROR
}
var relay: BehaviorRelay<FetchStyle> = BehaviorRelay.createDefault(FetchStyle.FETCH_ERROR)
fun fetchData(): Observable<DataModel> {
return relay
.map { f -> loadData(f) }
}
private fun loadData(f: FetchStyle): DataModel {
Thread.sleep(5000)
return when (f) {
FetchStyle.FETCH_SUCCESS -> DataModel("Data Loaded")
FetchStyle.FETCH_EMPTY -> DataModel(null)
FetchStyle.FETCH_ERROR -> throw IllegalStateException("Error Fetching")
}
}
}
I want to trigger an update downstream, whenever I change the value of relay, but this doesn't happen. It works when the Activity is initialized, but not when I'm updating the value. Here's my ViewModel, from where I update the value:
class MainViewModel : ViewModel() {
val fetcher: Observable<UiStateModel> = DataSource.fetchData().replay(1).autoConnect()
.map { result -> UiStateModel.from(result) }
.onErrorReturn { exception -> UiStateModel.Error(exception) }
.startWith(UiStateModel.Loading())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
fun loadSuccess() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadEmpty() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_EMPTY)
}
fun loadError() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_ERROR)
}
}
This is the code from the Activity that does the subsciption:
model.fetcher
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
uiState -> mainPresenter.loadView(uiState)
})
Ended up using kotlin coroutines instead, as I was unable to re-subscribe to ConnectableObservable and start a new fetch.
Here's the code for anyone interested.
The presenter:
class MainPresenter(val view: MainView) {
private lateinit var subscription: SubscriptionReceiveChannel<UiStateModel>
fun loadSuccess(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadError(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_ERROR)
}
fun loadEmpty(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_EMPTY)
}
suspend fun subscribe(model: MainViewModel) {
subscription = model.connect()
subscription.subscribe { loadView(it) }
}
private fun loadView(uiState: UiStateModel) {
when(uiState) {
is Loading -> view.isLoading()
is Error -> view.isError(uiState.exception.localizedMessage)
is Success -> when {
uiState.result != null -> view.isSuccess(uiState.result)
else -> view.isEmpty()
}
}
}
fun unSubscribe() {
subscription.close()
}
}
inline suspend fun <E> SubscriptionReceiveChannel<E>.subscribe(action: (E) -> Unit) = consumeEach { action(it) }
The view:
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
launch(UI) {
mainPresenter.subscribe(model)
}
btn_load_success.setOnClickListener {
mainPresenter.loadSuccess(model)
}
btn_load_error.setOnClickListener {
mainPresenter.loadError(model)
}
btn_load_empty.setOnClickListener {
mainPresenter.loadEmpty(model)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("View", "onDestroy()")
mainPresenter.unSubscribe()
}
...
The model:
class MainViewModel : ViewModel() {
val TAG = this.javaClass.simpleName
private val stateChangeChannel = ConflatedBroadcastChannel<UiStateModel>()
init {
/** When the model is initialized we immediately start fetching data */
fetchData()
}
override fun onCleared() {
super.onCleared()
Log.d(TAG, "onCleared() called")
stateChangeChannel.close()
}
fun connect(): SubscriptionReceiveChannel<UiStateModel> {
return stateChangeChannel.openSubscription()
}
fun fetchData() = async {
stateChangeChannel.send(UiStateModel.Loading())
try {
val state = DataSource.loadData().await()
stateChangeChannel.send(UiStateModel.from(state))
} catch (e: Exception) {
Log.e("MainModel", "Exception happened when sending new state to channel: ${e.cause}")
}
}
internal fun loadStyle(style: DataSource.FetchStyle) {
DataSource.style = style
fetchData()
}
}
And here's a link to the project on github.

Categories

Resources