How to wait the end of a firebase call in Kotlin (Android) - android

In an android application, i'm trying to do a firebase call that fills an ArrayAdapter in order to show a list of ships.
When i'm using a local ArrayList, it works, but my firebase call doesn't work properly.
Because that firebase call is asynchronous, android shows me the application before ending the firebase call, so my ArrayAdapter is empty and my layout is empty too.
I tryed to use a Coroutine method i've seen online but i doesn't seem to work.
Can someone help me ?
Here is my source code :
MainActivity :
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var containerShips : List<Containership> = listOf()
val db = Database()
runBlocking {
containerShips = db.getAllContainerships()
}
val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, containerShips)
val listShipDetails = findViewById<ListView>(R.id.listShipDetails)
listShipDetails.adapter = arrayAdapter
}
Database:
suspend fun getAllContainerships() : List<Containership> {
val list : MutableList<Containership> = mutableListOf()
val job = GlobalScope.launch {
db.collection("Containership").get().addOnSuccessListener { result ->
for (containership in result) {
list.add(containership.toObject(Containership::class.java))
println(containership.toObject(Containership::class.java))
}
}
}
job.join()
return list
}
Thanks for your help !

You can use Tasks.await to get this done.
private fun getAllContainerships(): List<Containership> {
return try {
val taskResult = Tasks.await(db.collection("Containership").get(), 2, TimeUnit.SECONDS)
taskResult.mapTo(ArrayList()) { containership.toObject(Containership::class.java) }, null)
} catch (e: ExecutionException) {
//TODO handle exception
} catch (e: InterruptedException) {
//TODO handle exception
} catch (e: TimeoutException) {
//TODO handle exception
}
}
Make sure this is done in a background thread. Also, try to get rid of your GlobalScope.async as it is a code smell (to do this, you can use the MVVM pattern, put your data access code in a viewmodel and use viewModelScope from AndroidX lifecycle).

Related

Best way to refresh data from server after adding new Data using retrofit2

In my case scenario as I use Compose, I add data to the server and read them from it, but after adding the new data and storing it in MutableListState when I go back to the main screen where the list of data is represented, the new data isn't shown as the API call was already done and If I close the app and reopen the new data read normally, so I want the best way to refresh the data from the server after adding. I am thinking about recalling the API again on my screen by using a launchedEffect, but I believe there are better ways than this. something like Firebase real-time database feature.
My Code for example is like this:
Class HomeRepository #Inject constructor(
val service : ApiServieces
) : IHomeRepository {
override suspend fun getHomeData() = service.getHomeData()
override suspend fun getProfileData() = service.getProfileData()
.................
Then I use it in my UseCase
class CheckPhoneUseCase #Inject constructor(private val repository: IRegistrationRepository) {
fun checkPhone(phoneBodyModel: PhoneBodyModel) : Flow<Resource<BaseResponse>> = flow{
emit(Resource.Loading())
try {
val response = repository.checkPhoneNumber(phoneBodyModel).toDomain()
emit(Resource.Success(response))
} catch (e: Exception) {
emit(Resource.Error(e.message ?: "Error Occurred!"))
} catch (e : HttpException) {
if (e.code() == HttpURLConnection.HTTP_NOT_FOUND) {
emit(Resource.Error("Phone Number Not Found!"))
} else {
emit(Resource.Error("Error Occurred!"))
}
} catch (e : IOException){
emit(Resource.Error("Failed to connect to server! check you internet connection!"))
}
}
}
and Use it in the ViewModel and Call the API method in the Composable function
private fun getCampaigns() {
getCampaignsUseCase().onEach{ response ->
when(response){
is Resource.Loading -> {
_isLoadingProgressBar.emit(true)
}
is Resource.Success -> {
_state.value = ResponseState(isSuccess = response.data?.status ?: false)
_campaignsData.value = ResponseCampaigns(isSuccess = response.data?.data)
Log.v("TAG", "getCampaigns: ${_campaignsData.value.isSuccess?.current?.items?.data?.size}")
for (item in response.data?.data?.current?.items?.data!!){
_campaignListCurrent.add(item)
}
_campaignListCurrent.removeAt(0)
for (item in response.data.data.completed.items.data!!){
_campaignListCompleted.add(item)
}
_campaignListCompleted.removeAt(0)
_isLoadingProgressBar.emit(false)
Log.v("TAG", "_campaignListCompleted: ${_campaignListCompleted}")
}
is Resource.Error -> {
_isLoadingProgressBar.emit(false)
Log.v("TAG", "_campaignListCompletedErorr: ${_campaignsData.value.isError}")
Log.v("TAG", "_campaignListCompletedErorrMessage: ${response.message}")
}
}
}.launchIn(viewModelScope)
}
This way I get the data, bat cant refresh it when I add new data from the server unless I close the app and recall the Api method again.

can we use runblocking with coroutine for room queries in production?

In our product we are using MVP pattern and in Room examples and after long searching I am getting all example with MVVM but now I found this way to run my Room queries using runblocking everything working smoothly I want to know that is this good way to use coroutines, I have heard that runblocking is not recommended for production
#Query("""SELECT V.* FROM VISITS AS V LEFT JOIN ORDERS AS O ON O.visitId = V.visitId
WHERE V.visitComplete = 1 AND V.visitId = :visitId AND (V.shopClosed = 1 OR O.orderTotal > 0)""")
suspend fun getCompletedVisitByVisitId(visitId: Int): Visits
And in my table Helper I am getting result like this
fun getCompletedVisitByVisitId(visitId: Int): Visits? = runBlocking {
var data: Visits? = null
try {
data = async {
visitsDao.getCompletedVisitByVisitId(visitId)
}.await()
} catch (e: SQLException) {
CustomMethods.errorLog(e, "getCompletedVisitByVisitId", ErrorLog.TYPE_ERROR, ErrorLog.APPLICATION_ERROR, context)
e.printStackTrace()
}
data
}
override fun getCompletedVisitByVisitId(visitId: Int): Visits? {
return visitsTableHelper!!.getCompletedVisitByVisitId(visitId)
}
androidx.lifecycle package provides extension function for lifecycle owners (activity,fragment,viewmodel etc). So, it would be convenient to to use the lifecycleScope.launch for MVP pattern. By this way your coroutine jobs will automatically get canceled when the lifecycle owner is not in its active state.
So, you code can be life below:
override fun onCreate(savedInstanceState: Bundle?) {
...
.....
lifecycleScope.launch {
try {
val visits = getCompletedVisitByVisitId(someNumber)
// do something with your data
} catch (e: Exception) {
//handle exception
}
}
}
suspend fun getCompletedVisitByVisitId(visitId: Int): Visits? {
var data: Visits? = null
try {
data = visitsDao.getCompletedVisitByVisitId(visitId)
} catch (e: SQLException) {
CustomMethods.errorLog(e, "getCompletedVisitByVisitId", ErrorLog.TYPE_ERROR, ErrorLog.APPLICATION_ERROR, context)
e.printStackTrace()
}
data
}
Also import the dependencies:
def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

android - kotlin - mvvm - posting data to webservice

I want to post some data to webservice and get the result . this is my code :
fab.setOnClickListener {
viewModel.newBimeGozar(name)
.observe(this#BimeGozarAct, Observer {
dialogbimegozarNew?.hidePg()
})
}
this is my viewmodel :
class BimeNewViewModel:ViewModel() {
private val repository=BimeNewRepository()
fun newBimeGozar(name: String): MutableLiveData<StatModel> {
return repository.newBimeGozar(name)
}
this is my repository :
fun newBimeShode(
name: String
): MutableLiveData<StatModel> {
scope.launch {
val request = api.newBimeShode(name)
withContext(Dispatchers.Main) {
try {
val response = request.await()
regBimeshodeLiveData.value = response
} catch (e: HttpException) {
Log.v("this", e.message);
} catch (e: Throwable) {
Log.v("this", e.message);
}
}
}
return regBimeshodeLiveData;
}
it works fine but there is a problem . I think the observer keeps running and if the result's answer is an error and user press fab button again , it creates a new observer and after this , it returns two value , the first value is the first run and the second value is the second run
how can I fix this ? what is the correct way for submitting forms ?
If your problem is because of LiveData, you should use SingleLiveEvent like as follow
// For first article
val _liveData = MutableLiveData<Event<StatModel>>()
// For second article
val _liveData = SingleLiveEvent<StatModel>()
If you do not know SingleLiveEvent, you can find it here and here.
If your problem is because of your ui element, I think the best solution is to disable the submit button after submitting for the first time.

Cancel Retrofit request started from ViewModel coroutine job

I would like my app users to be able to cancel file upload.
My coroutine upload job in ViewModel looks like this
private var uploadImageJob: Job? = null
private val _uploadResult = MutableLiveData<Result<Image>>()
val uploadResult: LiveData<Result<Image>>
get() = _uploadResult
fun uploadImage(filePath: String, listener: ProgressRequestBody.UploadCallbacks) {
//...
uploadImageJob = viewModelScope.launch {
_uploadResult.value = withContext(Dispatchers.IO) {
repository.uploadImage(filePart)
}
}
}
fun cancelImageUpload() {
uploadImageJob?.cancel()
}
Then in the repository the Retrofit 2 request is handled like this
suspend fun uploadImage(file: MultipartBody.Part): Result<Image> {
return try {
val response = webservice.uploadImage(file).awaitResponse()
if (response.isSuccessful) {
Result.Success(response.body()!!)
} else {
Result.Error(response.message(), null)
}
} catch (e: Exception) {
Result.Error(e.message.orEmpty(), e)
}
}
When cancelImageUpload() it called the job gets cancelled and the exception gets caught in the repository but the result won't get assigned to uploadResult.value.
Any ideas please how to make this work?
PS: There is a similar question Cancel file upload (retrofit) started from coroutine kotlin android but it suggests using coroutines call adapter which is depricated now.
Have finally managed to make it work by moving withContext one level up like this
uploadImageJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
_uploadResult.postValue(repository.uploadImage(filePart))
}
}

Making synchronous calls to Cloud Firestore when running off the main thread

I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0

Categories

Resources