I wrote code which works fine without kotlin flow, but i want to try kotlin flow and when i used it inside repository but somehow it is not even entering inside i could not find any solution since it is not throwing any error, it is just not entering inside function and i think it is because of flow collector. It is coming till repository but for repo it is not entering.
class NewsRepositoryImpl(private val newsService: NewsService) : NewsRepository {
override suspend fun getNews(search: String): Flow<ResultWrapper<List<Article>>> = flow {
emit(ResultWrapper.Loading)
try {
val news = newsService.getNews(search, BuildConfig.API_KEY)
emit(ResultWrapper.Success(news.articles.map { it.toArticle() }))
} catch (e: Exception) {
emit(ResultWrapper.Error(e.message))
}
}
}
Using flow builder you create a cold stream of data. The code inside it is executed only when the flow is being collected, i.e. terminal operators invoked, for example collect, collectLatest, first... .
coroutineScope.launch {
newsRepository.getNews("news").collectLatest { result ->
// use result
}
}
Related
I'm trying to implement One Tap, so I have created this function:
override fun oneTapSgnInWithGoogle() = flow {
try {
emit(Result.Loading)
val result = oneTapClient.beginSignIn(signInRequest).await()
emit(Result.Success(result))
} catch (e: Exception) {
emit(Result.Error(e.message))
}
}
//.flowOn(Dispatchers.IO)
And some programmer told me that I need to add .flowOn(Dispatchers.IO) to the above function, so it can be correct. My code work correct without it. Here is how I call this function in the ViewModel:
fun oneTapSignIn() = viewModelScope.launch {
repo.oneTapSignInWithGoogle().collect { response ->
oneTapSignInResponse = response
}
}
Is it really necessary to do that? I'm really confused.
You're calling beginSignIn which returns a Task, so it does its own stuff in the background. Now Task.await is suspending, not blocking, so it won't block the current thread while waiting for the task.
Therefore, the body of your flow doesn't contain any blocking stuff, so there is no reason to use flowOn(Dispatchers.IO) here.
Basically I want to make a network request when initiated by the user, collect the Flow returned by the repository and run some code depending on the result. My current setup looks like this:
Viewmodel
private val _requestResult = MutableSharedFlow<Result<Data>>()
val requestResult = _requestResult.filterNotNull().shareIn(
scope = viewModelScope,
started = SharingStarted.WhileViewSubscribed,
replay = 0
)
fun makeRequest() {
viewModelScope.launch {
repository.makeRequest().collect { _requestResult.emit(it) }
}
}
Fragment
buttonLayout.listener = object : BottomButtonLayout.Listener {
override fun onButtonClick() {
viewModel.makeRequest()
}
}
lifecycleScope.launchWhenCreated {
viewModel.requestResult.collect { result ->
when (result) {
Result.Loading -> {
doStuff()
}
is Result.Success -> {
doDifferentStuff(result.data)
}
is Result.Failure -> {
handleError()
}
}
}
}
The first time the request is made everything seems to work. But starting with the second time the collect block in the fragment does not run anymore. The request is still made, the repository returns the flow as expected, the collect block in the viewmodel runs and emit() also seems to be executed successfully.
So what could be the problem here? Something about the coroutine scopes? Admittedly I lack any sort of deeper understanding of the matter at hand.
Also is there a more efficient way of accomplishing what I'm attempting using Kotlin Flows in general? Collecting a flow and then emitting the same flow again seems a bit counterintuitive.
Thanks in advance:)
According to the documentation there are two recommended alternatives:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
//your thing
}
}
I rather the other alternative:
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeReques().flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
I like the flowWithLifecycle shorter syntax and less boiler plate. Be carefull thar is bloking so you cant have anything after that.
The oficial docs
https://developer.android.com/topic/libraries/architecture/coroutines
Please be aware you need the lifecycle aware library.
I'm trying to use Kotlin co-routines to trigger a network request and handle Exceptions. I've looked at a lot of tutorials on co-routines but I'm really struggling to relate what i know to the problem i have.
The problem
Trying to get an exception to be caught in the View, but no Exception is thrown from the ViewModel so the application crashes.
The Code
My Android app has three layers that relate to this issue. I have the View, ViewModel and a Service layer.
In the service layer, request.execute() can throw a UserAuthException.
MyView (View)
private val mViewModel: MyViewModel by viewModels()
private fun getFileId() {
try {
mViewModel.requestFileId()
} catch (e: UserAuthException) {
Timber.i(e)
}
}
MyViewModel (ViewModel)
private val apiService = MyApiService()
fun requestFileId() {
viewModelScope.launch {
ApiService.requestFileId()
}
}
MyApiService (Service Layer)
suspend fun requestFileId(): FileId = withContext(Dispatchers.IO) {
request.execute()
}
Things that i have looked at
I have played around with CoroutineExceptionHandlers, supervisorJobs with no luck, but without the fundamental knowledge of how these things work I'm not really making any progress.
Any help would be appreciated, thanks.
fun requestFileId() {
viewModelScope.launch {
ApiService.requestFileId()
}
}
This is not a suspendable function. It launches a concurrent coroutine and returns right away. Clearly, calling requestFileId() will never throw an exception.
Launching a coroutine is just like starting another thread, it introduces concurrency to your code. If your current code hopes to stay non-concurrent while observing the results of suspendable functions, you may be looking at significant architectural changes to the application to make it behave correctly under concurrency.
In your model, change it to something like this:
fun requestFileId() {
viewModelScope.launch {
try {
ApiService.requestFileId()
} catch (e: Exception) {
// inform your view
}
}
}
Given the following components
data class Account(val name: String)
data class GetAccountRequest(val name: String)
#Dao
interface AccountDao {
#Query("SELECT * FROM accounts ORDER BY name ASC")
fun all(): LiveData<List<Account>>
}
interface AccountOperations {
#GET("/foo/account")
suspend fun getAccount(#Body request: GetAccountRequest): Account
}
class AccountRepository(private val dao: AccountDao, private val api: AccountOperations) {
val accounts: LiveData<List<Account>> = dao.all()
suspend fun refresh(name: String) {
val account = api.getAccount(GetAccountRequest(name))
dao.insert(account)
}
}
I am working on an Android application that is using these components (powered by Room for the database and Retrofit for API access).
In my ViewModel I maintain a RecyclerView that lists all accounts. I enable users to refresh that list manually. The respective (part of the) ViewModel looks like this:
fun refresh() {
viewModelScope.launch {
repository.accounts.value?.forEach {
launch { repository.refresh(it.name) }
}
}
Timber.i("Done refreshing!")
}
I do want the refresh to update all accounts in parallel, this is why I am using launch. I have also decided to do this in the ViewModel, rather than in the repository, since that would have required to launch a new coroutine in the repository. Which per this post is discouraged since repositories don't have a natural lifecycle.
The above function, refresh, is invoked from the UI and shows a refresh-indicator while the RecyclerView is updated. So I want to stop this indicator once all accounts have been updated.
My code as shown above doesn't do this, since it will launch all the updates and then print the log statement before all updates have been finished. As a result the refresh-indicator disappears although there are still updates.
So my question (finally) is: how can I refactor the code so that it runs all updates in parallel, but makes sure refresh doesn't return before all of them have finished?
EDIT #1
Going back to what I want to achieve: showing the refresh-indicator while the view is updating, I came up with the following (changed the refresh function in the ViewModel):
fun refresh() {
viewModelScope.launch {
try {
coroutineScope {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}
}
} catch (cause: CancellationException) {
throw cause
} catch (cause: Exception) {
Timber.e(cause)
} finally {
_refreshing.value = false
}
}
}
The ViewModel exposes a LiveData for when it is refreshing and the fragment can observe it to show or hide the spinner. This seems to do the trick. However, it still doesn't feel right and I appreciate any improved solutions.
In order to await for all of your parallel refresh() operations, simply use awaitAll():
coroutineScope.launch {
_refreshing.value = true
repository.accounts.value?.map { account ->
async {
repository.refresh(account.name)
}
}.awaitAll()
_refreshing.value = false
}
Furthermore, It's not advised to wrap coroutines with try/catch.
You can read more on this here.
I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.