Can't understand why the flow block doesn't execute and control returns back to ViewModel.
ViewModel code
fun getFilesFromServer() {
viewModelScope.launch(Dispatchers.IO) {
ftpRepository.getFilesFromServer()
.onEach { state ->
when (state) {
is Resource.Loading -> {
liveData.postValue(Resource.Loading())
}
}
}
}
Repository (Here the problem, control doesn't go inside flow {} block)
override suspend fun getFilesFromServer(): Flow<Resource<Response>> = flow {
emit(Resource.Loading())
ftpClient.connect(
Constants.SERVER_IP, Constants.PORT,
Constants.USER_NAME,
Constants.PASSWORD,
object : OnEZFtpCallBack<Void?> {
override fun onSuccess(response: Void?) {
requestFtpFileList()
}
override fun onFail(code: Int, msg: String) {
}
}
)
}
Thanks for your time...
Forgot to launch the flow
launchIn(this)
Complete code
fun getFilesFromServer() {
viewModelScope.launch(Dispatchers.IO) {
ftpRepository.getFilesFromServer()
.onEach { state ->
when (state) {
is Resource.Loading -> {
liveData.postValue(Resource.Loading())
}
}
}.launchIn(this)
}
Related
I'm new to Android development and trying to understand Coroutines and LiveData from various example projects. I have currently setup a function to call my api when the user has input a username and password. However after 1 button press, the app seems to jam and I can't make another api call as if its stuck on a pending process.
This is my first android app made with a mash of ideas so please let me know where I've made mistakes!
Activity:
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
viewModel.userClicked(username, password).observe(this, Observer {
it?.let { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
})
}
ViewModel:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
viewModelScope.launch {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
}
Repository:
#WorkerThread
suspend fun login(
username: String,
password: String
): Flow<Resource<String?>> {
return flow {
emit(Resource.loading(null))
api.login(LoginRequest(username, password)).apply {
this.onSuccessSuspend {
data?.let {
prefs.apiToken = it.key
emit(Resource.success(null))
}
}
}.onErrorSuspend {
emit(Resource.error(message(), null))
}.onExceptionSuspend {
emit(Resource.error(message(), null))
}
}.flowOn(dispatcherIO)
}
API:
suspend fun login(#Body request: LoginRequest): ApiResponse<Auth>
You don't need to launch a coroutine in liveData builder, it is already suspend so you can call suspend functions there:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
If you want to use LiveDate with Flow you can convert Flow to LiveData object using asLiveData function:
fun userClicked(username: String, password: String): LiveData<Resource<String?>> {
return userRepository.login(username, password).asLiveData()
}
But I wouldn't recommend to mix up LiveData and Flow streams in the project. I suggest to use only Flow.
Using only Flow:
// In ViewModel:
fun userClicked(username: String, password: String): Flow<Resource<String?>> {
return userRepository.login(username, password)
}
// Activity
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
lifecycleScope.launch {
viewModel.userClicked(username, password).collect { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
}
}
Remove suspend keyword from the login function in Repository.
lifecycleScope docs.
Do you have any ideas how to implement repository pattern with NetworkBoundResource and Kotlin coroutines? I know we can launch a coroutine withing a GlobalScope, but it may lead to coroutine leak. I would like to pass a viewModelScope as a parameter, but it is a bit tricky, when it comes to implementation (because my repository doesn't know a CoroutineScope of any ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
#Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
Update (2020-05-27):
A way which is more idiomatic to the Kotlin language than my previous examples, uses the Flow APIs, and borrows from Juan's answer can be represented as a standalone function like the following:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow<Resource<ResultType>> {
emit(Resource.Loading(null))
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The above code can be called from a class, e.g. a Repository, like so:
fun getItems(request: MyRequest): Flow<Resource<List<MyItem>>> {
return networkBoundResource(
query = { dao.queryAll() },
fetch = { retrofitService.getItems(request) },
saveFetchResult = { items -> dao.insert(items) }
)
}
Original answer:
This is how I've been doing it using the livedata-ktx artifact; no need to pass in any CoroutineScope. The class also uses just one type instead of two (e.g. ResultType/RequestType) since I always end up using an adapter elsewhere for mapping those.
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import nihk.core.Resource
// Adapted from: https://developer.android.com/topic/libraries/architecture/coroutines
abstract class NetworkBoundResource<T> {
fun asLiveData() = liveData<Resource<T>> {
emit(Resource.Loading(null))
if (shouldFetch(query())) {
val disposable = emitSource(queryObservable().map { Resource.Loading(it) })
try {
val fetchedData = fetch()
// Stop the previous emission to avoid dispatching the saveCallResult as `Resource.Loading`.
disposable.dispose()
saveFetchResult(fetchedData)
// Re-establish the emission as `Resource.Success`.
emitSource(queryObservable().map { Resource.Success(it) })
} catch (e: Exception) {
onFetchFailed(e)
emitSource(queryObservable().map { Resource.Error(e, it) })
}
} else {
emitSource(queryObservable().map { Resource.Success(it) })
}
}
abstract suspend fun query(): T
abstract fun queryObservable(): LiveData<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(exception: Exception) = Unit
open fun shouldFetch(data: T) = true
}
Like #CommonsWare said in the comments, however, it'd be nicer to just expose a Flow<T>. Here's what I've tried coming up with to do that. Note that I haven't used this code in production, so buyer beware.
import kotlinx.coroutines.flow.*
import nihk.core.Resource
abstract class NetworkBoundResource<T> {
fun asFlow(): Flow<Resource<T>> = flow {
val flow = query()
.onStart { emit(Resource.Loading<T>(null)) }
.flatMapConcat { data ->
if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
}
emitAll(flow)
}
abstract fun query(): Flow<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(throwable: Throwable) = Unit
open fun shouldFetch(data: T) = true
}
#N1hk answer works right, this is just a different implementation that doesn't use the flatMapConcat operator (it is marked as FlowPreview at this moment)
#FlowPreview
#ExperimentalCoroutinesApi
abstract class NetworkBoundResource<ResultType, RequestType> {
fun asFlow() = flow {
emit(Resource.loading(null))
val dbValue = loadFromDb().first()
if (shouldFetch(dbValue)) {
emit(Resource.loading(dbValue))
when (val apiResponse = fetchFromNetwork()) {
is ApiSuccessResponse -> {
saveNetworkResult(processResponse(apiResponse))
emitAll(loadFromDb().map { Resource.success(it) })
}
is ApiErrorResponse -> {
onFetchFailed()
emitAll(loadFromDb().map { Resource.error(apiResponse.errorMessage, it) })
}
}
} else {
emitAll(loadFromDb().map { Resource.success(it) })
}
}
protected open fun onFetchFailed() {
// Implement in sub-classes to handle errors
}
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract suspend fun saveNetworkResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): Flow<ResultType>
#MainThread
protected abstract suspend fun fetchFromNetwork(): ApiResponse<RequestType>
}
I am new to Kotlin Coroutine. I just come across this problem this week.
I think if you go with the repository pattern as mentioned in the post above, my opinion is feeling free to pass a CoroutineScope into the NetworkBoundResource. The CoroutineScope can be one of the parameters of the function in the Repository, which returns a LiveData, like:
suspend fun getData(scope: CoroutineScope): LiveDate<T>
Pass the build-in scope viewmodelscope as the CoroutineScope when calling getData() in your ViewModel, so NetworkBoundResource will work within the viewmodelscope and be bound with the Viewmodel's lifecycle. The coroutine in the NetworkBoundResource will be cancelled when ViewModel is dead, which would be a benefit.
To use the build-in scope viewmodelscope, don't forget add below in your build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'
I have list of carousels and run over each carousel and based on the carousel query I do fetchAssets() and fetchAssets() is Kotlin suspended function but the problem is each function is called when the previous one is finished I want to achieve concurency?
uiScope.launch {
carousels.mapIndexed { index, carousel ->
when (val assetsResult = assetRepository.fetchAssets(carousel.query)) {
is Response.Success<List<Asset>> -> {
if (assetsResult.data.isNotEmpty()) {
val contentRow = ContentRow(assetsResult.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> {
}
}
}
}
override suspend fun fetchAssets(query: String): Response<List<Asset>> {
return suspendCoroutine { cont ->doHttp(assetsEndpoint, JsonHttpCall("GET"),
object : JsonReaderResponseHandler() {
override fun onSuccess(jsonReader: JsonReader) {
val apiAsset = ApiAssetList(jsonReader)
cont.resume(Response.Success(apiAsset.items))
}
override fun onError(error: Error) {
cont.resume(Response.Failure("errorMessage"))
}
})
}
}```
You have to wrap your suspend function in an async block, then wait for all async operations to complete:
uiScope.launch {
val asyncList = carousels.map { carousel ->
async { assetRepository.fetchAssets(carousel.query) }
}
val results = asyncList.awaitAll()
results.forEach { result ->
when (result) {
is Response.Success -> TODO()
is Response.Failure -> TODO()
}
}
}
suspend fun fetchAssets(query: String): Response<List<Asset>>
Edit: if you want to update the UI as each completes, you need to change it like this:
carousels.forEach { carousel ->
uiScope.launch {
val result = fetchAssets(carousel.query)
when (result) {
is Response.Success -> {
if (result.data.isNotEmpty()) {
val contentRow = ContentRow(result.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> TODO()
}
}
}
Check this for concurrency with Coroutines.
I have the following code, that does one single call, gets the result of the call, which is a boolean, then makes the second call if the result is false.
private fun linkEmailAndTextTogether(contactPhoneNumber: ContactPhoneNumbers,phoneNumber : PhoneNumber) {
val single = SingleOnSubscribe<Boolean> {
contactPhoneNumber.doesEmailContactExist(phoneNumber)
}
Single.create(single)
.subscribeOn(Schedulers.io())
.subscribeWith(object : SingleObserver<Boolean> {
override fun onSuccess(phoneNumberDoesExist: Boolean) {
if (!phoneNumberDoesExist) {
val completable = CompletableOnSubscribe {
contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
compositeDisposable.add(Completable.create(completable)
.subscribeOn(Schedulers.io())
.subscribe())
}
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onError(e: Throwable) {
Timber.e(e,e.localizedMessage)
}
})
}
It seems like there should be a more elegant way to do this in some kind of chain.
you could use the flatMap operator - the downside is that you won't know if the first or the second failed.
Single.just(phoneNumber)
.subscribeOn(Schedulers.io())
.map { it -> contactPhoneNumber.doesEmailContactExist(it) }
.flatMap { it ->
if (it) {
return#flatMap contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
Single.just(it)
}.subscribe({}, Throwable::printStackTrace);
This should help.
val single = SingleOnSubscribe<Boolean> {
getSingle()
}
Single.create(single).map({
if (it){
return#map getCompleteable()
}
return#map Completable.complete()
})
I am working on creating my MVVM template project.
The way MVVM work's is that the View observes changes to the VM and reacts to them. How would i implement that logic in the following example.
View code:
fun addPost(): Unit {
viewModel.addPost(dataSource.text.get(), dataSource.title.get(), { postID: Long ->
if (postID.equals(0)) {
Toast.makeText(MyApp.instance.getContext(), "Error", Toast.LENGTH_LONG).show()
} else {
(arguments.get(ARG_RunOnPositiveDismiss) as (DMPost) -> Unit)(DMPost(postID, MyOptions.User.GET()!!.UserID, dataSource.title.get(), dataSource.text.get()))
dismiss()
}
})
}
ViewModel code:
fun addPost(title:String,text:String,onSuccess: (Long) -> Unit): Unit {
rep.insertPost(DMPost(0, MyOptions.User.GET()!!.UserID, title, text),onSuccess)
}
Repository code:
fun insertPost(post: DMPost, onSuccess: (Long) -> Unit) {
if (MyOptions.Online.GET()!!) {
val client = retrofit.create(APIPost::class.java)
val insertPost: Call<Long> = client.insertPost(post)
insertPost.enqueue(object : Callback<Long> {
override fun onResponse(call: Call<Long>, response: Response<Long>) {
if (response.isSuccessful) {
onSuccess(response.body()!!)
} else {
onSuccess(0)
}
}
override fun onFailure(call: Call<Long>, t: Throwable) {
onSuccess(0)
}
})
} else {
AsyncTask.execute {
try {
onSuccess(daoPost.insertPost(EntityPost(post)))
} catch (e: Exception) {
onSuccess(0)
}
}
}
}
I am assuming that i need to have 2 methods in my view, one for showing the error toast and the other for success.the view should observe a value on the VM and on a change on that value, should call one of those 2 methods.
Is my assumption right?
If yes, can someone write the code for that logic?
In your viewmodel:
private MediatorLiveData<YourDataType> mDataObserved;
public LiveData<YourDataType> getDataObserved(){
return mDataObserved;
}
And in your view:
viewModel.getDataObserved().observer(this, Observer<YourDataType>{
// do update view logic in here
});
Last, in your repository callback call viewmodel to notify data changed:
mDataObserved.postValue(YourDataValue);