Firebase Remote Config never gets activated with .await() - android

I have a base Remote Config initiallizer class
abstract class RemoteConfig: KoinComponent {
protected val config = get<FirebaseRemoteConfig>()
protected val dispatchers = get<AppDispatchers>()
protected abstract fun initValues()
init {
CoroutineScope(dispatchers.io).launch {
initConfig()
}
}
private suspend fun initConfig() {
config.fetchAndActivate().await().let { activated ->
if (activated) {
initValues()
}
}
}
}
where FirebaseRemoteConfig set as single
single {
Firebase.remoteConfig.apply {
val interval = if (BuildConfig.DEBUG) 0 else 900
setConfigSettingsAsync(
remoteConfigSettings { minimumFetchIntervalInSeconds = interval.toLong() }
)
}
}
and this part of code
config.fetchAndActivate().await().let { activated ->
if (activated) {
initValues()
}
}
doesn't work - activated variable never equals true
but if I change this part of code to
config.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
initValues()
}
}
it works fine
Can you help me to understand, what's wrong with my initial way?

Related

How can I call same API multiple time using MVVM in android?

I have implemented MVVM architecture in one of my projects. In which I have created a ViewModel, Repository, and ViewModelFactory to make API call. I'm showing a list of products on the wish list. There is a facility where you can remove a particular product from the wish list. When my activity opens its API calls and renders the data but when I remove the product from the recycler view list need to make an API call which gets the remaining list of products available in the wishlist. When I change the configuration of the device it also makes API calls. How can I survive the configuration changes?
Below is the code from Activity
val repository = WishlistRepository(activity)
wishlistViewModel = ViewModelProvider(
this,
WishlistViewModelFactory(repository)
)[WishlistViewModel::class.java]
wishlistObservables()
if (preferenceUtils.getPrefBoolean(Constants.IS_LOGGED_IN)) {
if (wishlistViewModel.wishListLiveData.value == null) {
prepareParams()
}
} else {
val intent = Intent(activity, LoginActivity::class.java)
intent.putExtra("fromScreen", 2)
startActivity(intent)
}
prepareParams method
private fun prepareParams() {
if (Utils.isNetworkAvailable(activity)) {
val params = mutableMapOf<String, String>()
params["customer_id"] = preferenceUtils.getPreString(Constants.USER_ID)
params["store"] = preferenceUtils.getPreInt(Constants.STORE).toString()
wishlistViewModel.getWishList(params)
} else {
Utils.snackBar(binding.coordinatorWishlist, getString(R.string.network))
}
}
ViewModel Code
val wishListLiveData: LiveData<Resource<WishlistModel>> get() = repository.wishLiveData
fun getWishList(params: Map<String, String>) {
repository.getWishlistApiCall(params)
}
Repository Code
private val wishListLiveData = MutableLiveData<Resource<WishlistModel>>()
val wishLiveData:LiveData<Resource<WishlistModel>> get() = wishListLiveData
fun getWishlistApiCall(params:Map<String,String>){
if (Utils.isNetworkAvailable(activity)) {
wishListLiveData.postValue(Resource.Loading())
retroService.getWishListApiCall(params).enqueue(object : Callback<WishlistModel> {
override fun onResponse(call: Call<WishlistModel>, response: Response<WishlistModel>) {
try {
if (response.body() != null) {
if (response.isSuccessful) {
response.body()?.let {
if (it.code == 200) {
if (it.status == "1") {
wishListLiveData.postValue(Resource.Success(it))
} else {
wishListLiveData.postValue(Resource.Error(it.message))
}
} else if (it.code == 404) {
wishListLiveData.postValue(
Resource.Error(
activity.getString(
R.string.four_hundred_four_not_found
)
)
)
} else if (it.code == 500) {
wishListLiveData.postValue(
Resource.Error(
activity.getString(
R.string.five_hundred_server_error
)
)
)
} else {
wishListLiveData.postValue(Resource.Error(it.message))
}
}
} else {
wishListLiveData.postValue(Resource.Error(response.message()))
}
} else {
wishListLiveData.postValue(Resource.Error(response.message()))
}
} catch (e: Exception) {
wishListLiveData.postValue(Resource.Error(e.message.toString()))
}
}
override fun onFailure(call: Call<WishlistModel>, t: Throwable) {
try {
wishListLiveData.postValue(Resource.Error(t.message.toString()))
} catch (e: Exception) {
wishListLiveData.postValue(Resource.Error(e.message.toString()))
}
}
})
} else {
wishListLiveData.postValue(Resource.Error(activity.getString(R.string.network)))
}
}
ViewModelFactory Code
class WishlistViewModelFactory(private val repository: WishlistRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return WishlistViewModel(repository) as T
}
}
Remove wishlist observables
private fun removeObservables() {
removeWishlistViewModel.removeWishListLiveData.observe(this) {
when (it.status) {
Status.LOADING -> {
Utils.showDialog(activity)
}
Status.SUCCESS -> {
Utils.hideDialog()
if (it.data != null) {
it.data.let { removeWishModel ->
Utils.snackBar(
binding.coordinatorWishlist,
removeWishModel.message, Constants.SNACK_SUCCESS_COLOR
)
val activityFragmentMessageEvent =
Events.ActivityFragmentMessage("Wishlist")
EventBus.getDefault().post(activityFragmentMessageEvent)
prepareParams()
}
}
}
Status.ERROR -> {
Utils.hideDialog()
Utils.snackBar(binding.coordinatorWishlist, it.errorMessage.toString())
}
}
}
}
When I call prepareParams method again it's will make an API call of getting the wish-listed products. After that when I change the configuration from portrait to landscape it's starting again to make API calls again and again.
How can I resolve this issue? Please help me to resolve this issue with MVVM. I'm begginner in MVVM architecture.
Thank you

How to until wait 2 parallel retrofit calls both finish?

I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.
I have a viewModel where I have defined my services:
var config= List<Configuration>
fun getClientProducts() {
getClientClientConfigUseCase
.build(this)
.executeWithError({ config ->
config = config
}, {
})
}
var therapies = List<DtoTherapy>
fun getTherapies() {
getTherapiesUseCase
.build(this)
.executeWithError({ config ->
therapies = it
}, {
})
}
And then I want to call both services in parallel in my fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi(view)
loadUserData()
viewModel.getClientProducts()
viewModel.getTherapies()
}
And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.
Here is the class I use to build the use case call:
abstract class SingleUseCase<T> : UseCase() {
private lateinit var single: Single<T>
private lateinit var useCaseInterface: UseCaseInterface
private var withLoader: Boolean = false
private var withErrorMessage: Boolean = false
internal abstract fun buildUseCaseSingle(): Single<T>
fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
this.withLoader = false
this.withErrorMessage = false
this.useCaseInterface = useCaseInterface
this.single = buildUseCaseSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterSuccess { useCaseInterface.onSuccess(it) }
return this
}
fun withLoader(): SingleUseCase<T> {
this.withLoader = true
return this
}
fun withErrorMessage(): SingleUseCase<T> {
this.withErrorMessage = true
return this
}
fun single(): Single<T> {
return this.single
}
fun execute(onSuccess: ((t: T) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess)
}
private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess, onError)
}
private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
onError(mapError(it))
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
private fun mapError(t: Throwable): ApiError {
return if(t is HttpException) {
val apiError = t.response()?.errorBody()?.string()
try {
ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
} catch(e: Exception) {
ApiError (-2, "Unkown error")
}
} else ApiError (-1, "Unkown error")
}
}
And this is a specific usecase class:
class GetClientConfigUseCase #Inject constructor(private val repository: UserRepository) :
SingleUseCase<ClientConfigResponse>() {
override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
return repository.getUserConfig()
}
}
I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.
Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...
Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {
#Override
public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
{
// here you have both of your data
// do operations on products and therapies
// then return the result
return result;
}
});

How to synchonize executing http requests (kotlin, android)?

I am debugging an application that communicates with an IoT device via http.
In response to commands, the device sends information in xml format.
An application can also receive binary data on a GET request.
In the functionality of the application, filling the RecyclerView from the list and loading images to fill the RecyclerView and executing individual commands to change modes.
The problem is that the device does not have the most powerful processor, and when a large number of http commands are received, the service cannot cope and hangs for a long time until the WiFi channel fails.
I can’t figure out how to organize interaction so that each next command waits for the previous one to complete. The solution is complicated by the fact that populating the RecyclerView, loading images, and executing commands are in different parts of the code, and each is executed asynchronously.
Populating RecyclerView:
private fun initViewModel(filter: String) {
val st = Storage(requireContext())
val cache = "${st.externalCacheDir}/$filter/"
val viewModel = ViewModelProvider(this).get(DeviceListViewModel::class.java)
viewModel.getRecycerListObserver().observe(requireActivity(), Observer<ResponseData> {
if (it != null) {
val media = it.mediaData?.filter { it.mediaData?.fPath!!.contains(filter, false) }
mediaList = arrayListOf()
if (media != null) {
for (i in media.sortedByDescending { it.mediaData?.fTimeCode }) {
i.mediaData?.let { it1 -> mediaList.add(it1) }
}
}
viewModel.recyclerListLiveData = MutableLiveData()
ThumbDownloader(dataAdapter, mediaList, cache, swipeLayout).execute()
} else {
Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeApiCall()
}
}
ViewModel:
class DeviceListViewModel : ViewModel() {
var recyclerListLiveData: MutableLiveData<ResponseData>
init {
recyclerListLiveData = MutableLiveData()
}
fun getRecycerListObserver(): MutableLiveData<ResponseData> {
return recyclerListLiveData
}
fun makeApiCall() {
viewModelScope.launch(Dispatchers.IO) {
try {
val retroInstance =
RetroInstance.getRetroInstance(MainActivity.BaseUrl).create(RetroService::class.java)
val response = retroInstance.getDataFromApi(1, Cmd.WIFIAPP_CMD_FILELIST)
recyclerListLiveData.postValue(response)
} catch (e: Exception) {
var response: ResponseData? = null
when (e) {
is ConnectException -> {
recyclerListLiveData.postValue(response)
}
is SocketTimeoutException -> {
recyclerListLiveData.postValue(response)
}
}
}
}
}
}
Service to make a command (processing results in the Handler):
class DeviceService {
private val handler: Handler
private var mJob: Job? = null
constructor(handler: Handler) {
this.handler = handler
}
fun sendCommand(cmd: Int) {
val service = RetroInstance.buildService(MainActivity.BaseUrl, RetroService::class.java)
mJob = CoroutineScope(Dispatchers.IO).launch {
val response = when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
try {
service.getLinkFromApi(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
else -> {
try {
service.makeCommand(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
}
withContext(Dispatchers.Main) {
try {
when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
handler.obtainMessage(Msg.MESSAGE_LINK_FORMAT, response).sendToTarget()
}
else -> {
handler.obtainMessage(Msg.MESSAGE_PAR_FUNCTION, response).sendToTarget()
}
}
} catch (e: Exception) {
when (e) {
is ConnectException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
is SocketTimeoutException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
}
}
mJob?.cancelAndJoin()
}
}
}
}
Downloading a images:
class ThumbDownloader(dataAdapter: DeviceAdapter, data: ArrayList<MediaData>, file_path: String, swipe: SwipeRefreshLayout) : CoroutineScope {
private var job: Job = Job()
private var file_path: String
private var dataAdapter: DeviceAdapter
private var data: ArrayList<MediaData>
private var swipe: SwipeRefreshLayout
init {
this.data = data
this.file_path = file_path
this.dataAdapter = dataAdapter
this.swipe = swipe
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun cancel() {
job.cancel()
}
fun execute() = async {
var item: File? = null
for (i in data) {
val task = async(Dispatchers.IO) {
val url = i.fPath!!
val real_url = "${MainActivity.BaseUrl}$url"
item = NetworkUtil.downloadFile(real_url, file_path, false)
}
task.await()
if (item != null) {
dataAdapter.insertItem(i)
}
}
cancel()
swipe.isRefreshing = false
}
}
Any ideas how to come up with their synchronization while waiting for the previous commands to complete?

Calling multiple viewmodel methods from launchWhenStarted does not work [duplicate]

This question already has answers here:
Collect from several stateflows
(4 answers)
Closed 1 year ago.
This code invokes two methods on the same viewmodel and listens for updates. But only the first method completes, the second does not event trigger.
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
ViewModel
class DashboardViewModel : ViewModel(), com.droid.common.Logger {
fun incrementCount(): MutableStateFlow<Int> {
val countState = MutableStateFlow(0)
viewModelScope.launch {
repeat(5) {
countState.value = (it)
delay(1000)
}
}
return countState
}
fun getAllTeams(): MutableStateFlow<State> {
val state = MutableStateFlow<State>(State.None)
state.value = State.Loading
viewModelScope.launch {
try {
val allTeams = FootballAPIClient.apiService.getAllTeams()
state.value = State.Success(allTeams)
} catch (exception: Exception) {
error { "Error getting all teams: ${exception.message}" }
state.value = State.Error(exception.message.toString())
}
}
return state
}
However, calling them with separate lifecycleScope works
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
}
lifecycleScope.launchWhenStarted {
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
I can't seem to understand this behavior, anybody knows why?
You will need different coroutines, since collect() is a suspending function that suspends until your Flow terminates.
The problem with launchWhenStarted is that while your newly emitted items will not be processed your producer will still run in the background.
For collecting multiple flows the currently recommended way is:
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.incrementCount().collect { ... }
}
launch {
viewModel.getAllTeams().collect { ... }
}
}
}

Android : Testing viewmodel with coroutines

I have this testing method
private var testDispatcher = TestCoroutineDispatcher()
#Test
fun test__success() {
viewModel = MyActivateViewModel(
"LG_NAME",
"EXH_NAME",
"_CODE",
"C_CODE", controller)
every {
controller.activate(any(), any())
} returns true
testDispatcher
.runBlockingTest {
viewModel.onOkButtonClicked()
Truth.assertThat(viewModel.activated.value).isEqualTo(true)
}
}
fun onOkButtonClicked() {
viewModelScope.launch {
val status = activateTask()
if (status == 0) {
activated.value = true
} else {
activationFailed.value = status
}
}
}
private suspend fun activateTask(): Int {
return withContext(Dispatchers.IO) {
var status = 0
try {
controller.activate(code, code)
} catch (e: LoginException) {
status = e.reason
}
status
}
}
Since I am calling runBlockingTest {
viewModel.onOkButtonClicked()
I expect this to be completed. But the assert below these lines are failing. What i see is the coroutine is not finished before the assert line is executed. If i add a delay before assert, it works as expected.
What is wrong in my implemetation?
what is the right way

Categories

Resources