hey how can i wait for a result in `coroutine` . I...
# coroutines
h
hey how can i wait for a result in
coroutine
. I have a method to store image to firebase and return its
URL
, it s
suspend
function and i am calling that
ImageUpload()
method inside my view model
Copy code
var imageUri = mutableStateOf<String?>(null)
Copy code
fun uploadImage(uri: Uri) {
        viewModelScope.launch {
            imageUri.value = saveUserRepositoryImp.saveImageToFirebase(uri)
        }
        _userDetail = _userDetail?.copy(
            profileUri = imageUri.value
        )
    }
this is function inside my
viewmodel
and this function is called inside
viewModelScope.launch{ uploadImage(uri) }
i want it to wait for the result because i want show loading and when result is return the loading should be stop
s
You should not 'wait' in your UI for loading to finish.
Copy code
fun uploadImage(uri: URI) {
  _uiState.value = UiState.Loading // will show Loading... on your UI
  viewModelScope.launch { 
      _uiState.value.userDetail = _uiState.value.userDetail?.copy(profileUri = saveUserRepositoryImp.saveImageToFirebase(uri));
  }
}
Your UI code will collect/subscribe-to the
viewModel.uiState
, When it sees a UiState.Loading, then it shows a Loading.... screen/spinner/view When later the UiState.UserDetail is observed, it shows you the upload-result/user-details.
If you don't use streams/flows in your UI, then you can make
uploadImage
suspending: In your UI:
Copy code
...
showLoadingView()
result = uploadImage(imageUri)
showResult(result)
...
In your view model:
Copy code
suspend fun uploadImage(uri: URI) {
  return withContext(Dispatchers.IO) { 
    saveUserRepositoryImp.saveImageToFirebase(uri));
  }
}
(but I suggest using an MVVM pattern where your UI observes Flow(s) from your ViewModel)
(the code-snippets may not compile correctly.... consider them as pseudo-code 🙂 )
h
im using mvvm the actul code is in remote then to repository then to viewmodel
👍 1
because calling mehod inside viewmodelscop.launc it suddely executs the next line and assign it default value , i want it to wait
s
Ah... I see... you'll have to put the assignment to
_userDetail
inside the
launch
...
h
actual imp
Copy code
override suspend fun saveImageToFirebase(uri : Uri)  : String {

    var imageUri : String = ""

    Log.d("debug", "uploadImage1")

        storageReference.child(FirebaseAuth.getInstance().uid!!).putFile(uri)
            .addOnSuccessListener {
                storageReference.child(FirebaseAuth.getInstance().uid!!).downloadUrl.addOnSuccessListener { uri ->
                    imageUri = uri.toString()
                    Log.d("debug", "uploadImage2:  ${uri}")
                }
            }
            .addOnFailureListener{
                Log.d("debug", "uploadImage3:  ${it.message}")
                it.printStackTrace()
                Log.d("debug", "uploadImage4:  ${it.message}")
            }

    return imageUri
}
the repo
Copy code
override suspend fun saveImageToFirebase(uri: Uri) : String {
   return userDetailService.saveImageToFirebase(uri)
}
viewmodel
Copy code
fun uploadImage(uri: Uri) {
    viewModelScope.launch {
           Log.d("debug", "uploadImage: actual 1")
           val profileUri = saveUserRepositoryImp.saveImageToFirebase(uri)
           Log.d("debug", "uploadImage: actual 2-> ${profileUri}")
    }
}
s
Looks good, but you'll need an assignment to the
flow.value
inside your viewModelScope.launch with the profileUri result
h
here in viewmodel im getting
Log.d("debug", "uploadImage: actual 2-> ${profileUri}")
im getting empty string because imreturn empty sting in my actual implemt var imageUri : String = ""
s
Copy code
fun uploadImage(uri: Uri) {
    uiState.value = UiState.Loading
    viewModelScope.launch {
           Log.d("debug", "uploadImage: actual 1")
           val profileUri = saveUserRepositoryImp.saveImageToFirebase(uri)
           uiState.value = UserDetails(profileUri)
           Log.d("debug", "uploadImage: actual 2-> ${profileUri}")
    }
}
^^^ or something similar... the uiState is a Flow<UiState> here, where UiState a sealed class/interface that can have a Loading value or a UserDetails instance.
The empty string is an issue with your repo/service code...
It doesn't wait for the callback to return a result
h
yes , it dosent wait and return empty , previousey i assign a default value to null but then it return null
can i add withcontext n my actul implemt so that it can wait for the result
s
Copy code
override suspend fun saveImageToFirebase(uri : Uri)  : String {
    return suspendCancellableCoroutine { cont->
        storageReference.child(FirebaseAuth.getInstance().uid!!).putFile(uri)
            .addOnSuccessListener {
                storageReference.child(FirebaseAuth.getInstance().uid!!).downloadUrl.addOnSuccessListener { uri ->
                    imageUri = uri.toString()
                    cont.resume(imageUri)
                }
            }
            .addOnFailureListener{
                cont.resumeWithException(it)
            }
    
        cont.invokeOnCancellation { /* deregister your onSuccess and onFailure listeners here */ }    
    }
}
👍 1
h
thanks man ! the actual problem is only thing i know about coroutine is If you want to perform a task asynchronously , you can mark the function as
suspend
and call it inside a coroutine scope. i need to dive more into it , can u suggest some topics or resources , thanks