Android Room RxJava UndeliverableException - android

I'm trying to fix some error reported by crashlytics in my app which I released to play store
I checked the log, but I don't know how to fix it...
io.reactivex.exceptions.UndeliverableException:
at io.reactivex.plugins.RxJavaPlugins.onError (RxJavaPlugins.java:367)
at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual (SingleFromCallable.java:50)
at io.reactivex.Single.subscribe (Single.java:3603)
at io.reactivex.internal.operators.maybe.MaybeFilterSingle.subscribeActual (MaybeFilterSingle.java:40)
at io.reactivex.Maybe.subscribe (Maybe.java:4290)
at io.reactivex.internal.operators.maybe.MaybeSubscribeOn$SubscribeTask.run (MaybeSubscribeOn.java:54)
at io.reactivex.Scheduler$DisposeTask.run (Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run (ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call (ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run (FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run (ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:607)
at java.lang.Thread.run (Thread.java:762)
Caused by: android.arch.b.b.b:
at com.eastriver.workingtimer.data.source.WorkDao_Impl$4.call (WorkDao_Impl.java:172)
at com.eastriver.workingtimer.data.source.WorkDao_Impl$4.call (WorkDao_Impl.java:129)
at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual (SingleFromCallable.java:44)
at io.reactivex.Single.subscribe (Single.java:3603)
Actually, all my rx code is handling the error like this:
class MyIntentService(
private val disposable: CompositeDisposable = CompositeDisposable()
) : IntentService("MyIntentService") {
...
override fun onHandleIntent(intent: Intent?) {
disposable.add(
workDao.getWorkById(getToday())
.subscribeOn(Schedulers.io())
.subscribe({
// my logic
}, { t ->
Log.e(TAG, "error: ${t.message}", t)
})
)
}
override fun onDestroy() {
super.onDestroy()
disposable.clear()
}
}
And the WorkDao is:
#Dao
interface WorkDao {
#Query("SELECT * FROM work WHERE id = :id")
fun getWorkById(id: Long): Single<Work>
}
But I cannot find my error log in the crash report.
What should I do?

Ok, it looks to me that onDestroy is called before workDao.getWorkById(getToday()) is done. So it throws error while your subscriber is already disposed. What you can try now is handling errors from RxJava itself by following this link https://stackoverflow.com/a/49387916/2164363
Cheers

Related

Kotlin executes only one of two suspend functions

In my suspend function getContent I am calling two other suspend functions:
override suspend fun getContent(token: String) {
...
getUserPosts(token)
getRecentPosts(token)
...
}
But only the first one gets executed.
If I put getRecentPosts first then only that one gets executed.
How to solve this?
I call the getContent method from ViewModel with viewModelScope.
EDIT
Here is my getUserPosts() method, the getRecentPosts() method look pretty much the same, the "Finished 1" and "Finsihed 2" Log is getting logged but not "Finished 3:
override suspend fun getUserPosts(token: String) {
try {
myInterceptor.setAccessToken(token)
graphqlAPI.query(GetUserPostsQuery()).fetchPolicy(FetchPolicy.NetworkFirst).watch()
.collect { response ->
val newList = arrayListOf<Post>()
response.data?.getUserPosts?.let {
for (post in it) {
newList.add(
Post(
id = post.postFragment.id,
...
)
)
}
userState.editUserState(userState.userStateFlow.value?.copy(posts = newList))
Log.i("#+#", "Finished 1")
}
Log.i("#+#", "Finished 2")
}
Log.i("#+#", "Finished 3")
} catch (e: ApolloException) {
e.printStackTrace()
}
}
collect never stops, so the first function will never end.
What you have to do is launch another coroutine inside.
override suspend fun getContent(token: String) {
CoroutineContex.launch { getUserPosts(token) }
CoroutineContex.launch { getRecentPosts(token) }
}
However that is considered dirty because you can't test it. That launch there won't have the same scope than the coroutine scope from testing.
Also, it won't stop, so it is better to provide the coroutine scope.
override suspend fun getContent(token: String, coroutineScope: CoroutineScope) {
coroutineScope.launch { getUserPosts(token) }
coroutineScope.launch { getRecentPosts(token) }
}
This way in your view model or fragment you can call it "safely" by knowing the scope will cancel the collection when the owner life cycle ends.
getContent(token, viewModelScope)
getContent(token, viewLifeCycleOwner.lifecycleScope)
This is not an uncommon doubt, and there is a fairly brief mention on the documentation: https://developer.android.com/topic/libraries/architecture/coroutines#lifecycle-aware

Kotlin coroutines crash with no helpful stacktrace

My Android app crashes and I see this stack trace in Logcat. It doesn't tell me which line of code is causing the problem.
2021-05-05 09:13:33.143 1069-1069/com.mycompany.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mycompany.app, PID: 1069
retrofit2.HttpException: HTTP 403
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Is there a way to map this back to my code, to see which call to retrofit is causing it? I have a repository with code like this:
suspend fun getSomeData(): Stuff {
return withContext(Dispatchers.IO) {
val body = myRetroApi.getStuff()
...
Do I need to wrap every withContext body to make sure no Throwables escape? I thought that if something threw an exception there, it would log an error, not crash the entire app.
Edit
I messed up when asking this question and put the emphasis on wrong things. So I'm removing the "retrofit" tag. It turns out the withContext(Dispatchers.IO) call does re-throw the Exception as expected, but when the exception gets back up to viewModelScope.launch, if that block does not catch it, the app crashes.
If the exception is not handled the app will crash of course.
You can add a try catch to avoid this:
suspend fun getSomeData() {
withContext(Dispatchers.IO) {
try{
val body = myRetroApi.getStuff()
...
} catch (e : Exception){
//your code
}
...
Retrofit is giving you a 403 Unauthorized HTTP exception. It may be that the server isn't passing any additional error message or that you need to catch HttpException and check for the message. In either case, this isn't a Retrofit issue hence it's just passing the error it's getting from the server you're calling.
It's best to create a network result wrapper and a wrapper function for API calls to handle exceptions.
You can do something like this. Keep in mind, the actual implementation is completely up to you. I would however suggest using runCatching when it comes to couroutines as it handles cancellation exceptions.
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Throwable, val message: String?) : NetworkResult<Nothing>()
}
suspend fun networkCall(): String = ""
suspend fun <T> safeApiCall(block: suspend () -> T): NetworkResult<T> {
return runCatching {
withContext(Dispatchers.IO) {
block()
}
}.fold({
NetworkResult.Success(it)
}, {
when (it) {
is HttpException -> NetworkResult.Error(it, "Network error")
else -> NetworkResult.Error(it, "Some other message...")
// else -> throw it
}
})
}
suspend fun getData() {
val result: NetworkResult<String> = safeApiCall {
networkCall()
}
when (result) {
is NetworkResult.Success -> {
//Handle success
}
is NetworkResult.Error -> { //Handle error
}
}
}
runCatching uses Kotlin's built-in Result class and there are several ways of handling the result. These are just a few.
runCatching {
//.....
}.getOrElse { throwable ->
//handle exception
}
runCatching {
//.....
}.getOrThrow()
runCatching {
}.onSuccess {
}.onFailure {
}

Handling exception thrown within a withContext() in Android coroutine

I have an android app that I have built up an architecture similar to the Google IO App. I use the CoroutineUseCase from that app (but wrap results in a kotlin.Result<T> instead).
The main code looks like this:
suspend operator fun invoke(parameters: P): Result<R> {
return try {
withContext(Dispatchers.Default) {
work(parameters).let {
Result.success(it)
}
}
} catch (e: Throwable) {
Timber.e(e, "CoroutineUseCase Exception on ${Thread.currentThread().name}")
Result.failure<R>(e)
}
}
#Throws(RuntimeException::class)
protected abstract suspend fun work(parameters: P): R
Then in my view model I am invoking the use case like this:
viewModelScope.launch {
try {
createAccountsUseCase(CreateAccountParams(newUser, Constants.DEFAULT_SERVICE_DIRECTORY))
.onSuccess {
// Update UI for success
}
.onFailure {
_errorMessage.value = Event(it.message ?: "Error")
}
} catch (t: Throwable) {
Timber.e("Caught exception (${t.javaClass.simpleName}) in ViewModel: ${t.message}")
}
My problem is even though the withContext call in the use case is wrapped with a try/catch and returned as a Result, the exception is still thrown (hence why I have the catch in my view model code - which i don't want). I want to propagate the error as a Result.failure.
I have done a bit of reading. And my (obviously flawed) understanding is the withContext should create a new scope so any thrown exceptions inside that scope shouldn't cancel the parent scope (read here). And the parent scope doesn't appear to be cancelled as the exception caught in my view model is the same exception type thrown in work, not a CancellationException or is something unwrapping that?. Is that a correct understanding? If it isn't what would be the correct way to wrap the call to work so I can safely catch any exceptions and return them as a Result.failure to the view model.
Update:
The implementation of the use case that is failing. In my testing it is the UserPasswordInvalidException exception that is throwing.
override suspend fun work(parameters: CreateAccountParams): Account {
val tokenClient = with(parameters.serviceDirectory) {
TokenClient(tokenAuthorityUrl, clientId, clientSecret, moshi)
}
val response = tokenClient.requestResourceOwnerPassword(
parameters.newUser.emailAddress!!,
parameters.newUser.password!!,
"some scopes offline_access"
)
if (!response.isSuccess || response.token == null) {
response.statusCode?.let {
if (it == 400) {
throw UserPasswordInvalidException("Login failed. Username/password incorrect")
}
}
response.exception?.let {
throw it
}
throw ResourceOwnerPasswordException("requestResourceOwnerPassword() failed: (${response.message} (${response.statusCode})")
}
// logic to create account
return acc
}
}
class UserPasswordInvalidException(message: String) : Throwable(message)
class ResourceOwnerPasswordException(message: String) : Throwable(message)
data class CreateAccountParams(
val newUser: User,
val serviceDirectory: ServiceDirectory
)
Update #2:
I have logging in the full version here is the relevant details:
2020-09-24 18:12:28.596 25842-25842/com.ipfx.identity E/CoroutineUseCase: CoroutineUseCase Exception on main
com.ipfx.identity.domain.accounts.UserPasswordInvalidException: Login failed. Username/password incorrect
at com.ipfx.identity.domain.accounts.CreateAccountsUseCase.work(CreateAccountsUseCase.kt:34)
at com.ipfx.identity.domain.accounts.CreateAccountsUseCase.work(CreateAccountsUseCase.kt:14)
at com.ipfx.identity.domain.CoroutineUseCase$invoke$2.invokeSuspend(CoroutineUseCase.kt:21)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
2020-09-24 18:12:28.598 25842-25842/com.ipfx.identity E/LoginViewModel$createAccount: Caught exception (UserPasswordInvalidException) in ViewModel: Login failed. Username/password incorrect
The full exception is logged inside the catching in CoroutineUseCase.invoke. And then again the details logged inside the catch in the view model.
Update #3
#RKS was correct. His comment caused me to look deeper. My understanding was correct on the exception handling. The problem was in using the kotlin.Result<T> return type. I am not sure why yet but I was somehow in my usage of the result trigger the throw. I switched the to the Result type from the Google IO App source and it works now. I guess enabling its use as a return type wasn't the smartest.
try/catch inside viewModelScope.launch {} is not required.
The following code is working fine,
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class TestCoroutines {
private suspend fun work(): String {
delay(1000)
throw Throwable("Exception From Work")
}
suspend fun invoke(): String {
return try {
withContext(Dispatchers.Default) {
work().let { "Success" }
}
} catch (e: Throwable) {
"Catch Inside:: invoke"
}
}
fun action() {
runBlocking {
val result = invoke()
println(result)
}
}
}
fun main() {
TestCoroutines().action()
}
Please check the entire flow if same exception is being thrown from other places.

Firebase: How to check if document write was successful

I want to check if my database write was successful in order to show the user an error message.
My current approach doesn't work as it says "Type mismatch, required Unit found EmailStatus"
Current approach
class EmailRepositoryImpl : EmailRepository {
private val db = Firebase.firestore
override fun sendEmail(email: Email): EmailStatus<Nothing> {
db.collection("emails").document().set(email).addOnCompleteListener {
if (it.isSuccessful) return#addOnCompleteListener EmailStatus.Success<Nothing>
if (it.isCanceled) return#addOnCompleteListener EmailStatus.Error(it.exception!!)
}
}
}
Status Sealed Class
sealed class EmailStatus<out T> {
data class Success<out T>(val data: T) : EmailStatus<T>()
data class Error(val exception: Exception) : EmailStatus<Nothing>()
}
Is it even possible to write something like this? As far as I know there is a generic firebase error type but I didn't found anything related to kotlin or android...
I appreciate every help, thank you
Edit
I've tried getting my document, but I am just getting null: (When I use the listener approach, everything works fine)
Interface
interface EmailRepository {
suspend fun getEmail(): Flow<EmailEntity?>
}
Interface Implementation
override suspend fun getEmail(): Flow<EmailEntity?> = flow {
val result = db.collection("emailprice").document("Email").get().await()
emit(result.toObject<EmailEntity>())
}
ViewModel
private val emailEntity = liveData<EmailEntity?>(Dispatchers.IO) {
emailRepository.getCalibratePrice()
}
The problem is that addOnCompleteListener callback does not return anything (Unit) and you are trying to return an EmailStatus from that scope.
You have three approaches:
Create an interface that will populate the value and return that EmailStatus down to your caller layer
Use Coroutines to suspend this function when the async call to firebase is done and then return that value
Use Flow to offer the data when it's ready to process
I think the easiest way to do this one shot operation is to use Coroutines; I have written an article about that.
Okay, this is the final solution, thanks to #Gastón Saillén and #Doug Stevenson :
EmailRepository
interface EmailRepository {
fun sendEmail(email: Email): Flow<EmailStatus<Unit>>
}
EmailRepository Implementation
class EmailRepositoryImpl #Inject constructor(
private val db: FirebaseFirestore
) : EmailRepository {
override fun sendEmail(email: Email)= flow<EmailStatus<Unit>> {
db.collection("emails").add(email).await()
emit(EmailStatus.success(Unit))
}.catch {
emit(EmailStatus.failed(it.message.toString()))
}.flowOn(Dispatchers.Main)
}
ViewModel
fun sendEmail(): LiveData<EmailStatus<Unit>> {
val newEmail = createEmail()
return emailRepository.sendEmail(newEmail).asLiveData()
}
Fragment
btn.setOnClickListener {
viewModel.sendEmail().observe(viewLifecycleOwner) {
when(it) {
is EmailStatus.Success -> {
valid = true
navigateTo(next, bundleNext)
Toast.makeText(requireContext(), "Success", Toast.LENGTH_SHORT).show()
}
is EmailStatus.Failure -> {
valid = false
Toast.makeText(requireContext(), "Failed ${it.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
The only problem I currently have is that my "faield state" does not work like it should.
It should fail, if the user has no internet access. Currently, the write to the db never fails and Firebase just waits till the user has internet access. The problem here is that when I click multiple times, the write is executed multiple times. But I think I have to implement a bit more logic here and the above written code is fine like it currently is.

Single.defer() not executed

I have an issue wherein my Single.defer block is not being executed.
documentRepository.getDocuments() is called to get documents from DB:
class GetReports
#Inject constructor(
private val reportRepository: ReportRepository,
private val documentRepository: DocumentRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
) : SingleUseCase<List<Report>, Void?>(
threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Void?): Single<List<Report>> {
return reportRepository
.getReports()
.flatMap { reports ->
val finalReports = ArrayList<Report>()
reports.forEach { report ->
documentRepository.getDocuments(report.id!!)
.map { documents ->
finalReports.add(Report(report.id, report.name, report.status,
report.dateCreated, documents))
}
}
Single.just(finalReports.toList())
}
}
}
The first Timber.d() logging code gets executed so I know it enters this method but the whole Single.defer code after that is not executed -- debug is not entering inside the block and the second Timber.d() logging code is also not executed.
override fun getDocuments(reportId: Long): Single<List<Document>> {
Timber.d("Getting documents for report ID $reportId")
return Single.defer<List<Document>> {
try {
Timber.d("Getting document cursor for report ID $reportId")
val documentCursor = database.query(
Db.DocumentTable.TABLE_NAME,
null,
Db.DocumentTable.REPORT_ID + "= $reportId",
null,
null,
null,
null
)
Timber.d("Got document cursor for report ID $reportId. Row count: ${documentCursor.count}")
getDocuments(documentCursor)
} catch (e: Exception) {
Timber.e(e, "Error getting documents for report ID $reportId")
Single.error(e)
}
}
}
Am I doing it incorrectly? Any help would be appreciated. Thanks!
As #azizbekian said, there was nothing that subscribed to the getDocuments. However, calling subscribe() inside that flatMap is discouraged as it breaks the flow. Instead, you should create an inner flow and return that in the flatMap of reports:
override fun buildUseCaseObservable(params: Void?): Single<List<Report>> {
return reportRepository
.getReports()
.flatMap { reports ->
Observable.fromIterable(reports)
.concatMap { report ->
documentRepository.getDocuments(report.id!!)
.map { documents ->
Report(report.id, report.name, report.status,
report.dateCreated, documents))
}.toObservable()
}
.toList()
}
}
Here's what you are essentialy doing:
documentRepository.getDocuments(...)
.map { ...}
Nobody is subscribing to this stream. Stream won't be executed unless there exists a subscriber to it.
Have you had added one line to this code, you'll see defer() being executed:
documentRepository.getDocuments(...)
.map { ...}
.subscribe()

Categories

Resources