mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Display specific, user-friendly error message when upload categories search API call returns an error (#6540)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Make OkHttpConnectionFactory raise MwIOException when a non-suppressed API call returns an error * Add AlertDialog displaying specific error message when categories search API call returns an error * Add test for error alert dialog to UploadCategoriesFragment unit tests * Add error handling when API call fails to CategoriesPresenter.onAttachViewWithMedia
This commit is contained in:
parent
aae9d4a387
commit
28fa7b1a20
8 changed files with 76 additions and 5 deletions
|
|
@ -1,7 +1,11 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import fr.free.nrw.commons.wikidata.GsonUtil
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwErrorResponse
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwIOException
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwLegacyServiceError
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
@ -86,16 +90,25 @@ private class UnsuccessfulResponseInterceptor : Interceptor {
|
|||
rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody ->
|
||||
if (ERRORS_PREFIX == responseBody.string()) {
|
||||
rsp.body.use { body ->
|
||||
throw IOException(body!!.string())
|
||||
val bodyString = body!!.string()
|
||||
|
||||
throw MwIOException(
|
||||
"MediaWiki API returned error: $bodyString",
|
||||
GsonUtil.defaultGson.fromJson(
|
||||
bodyString,
|
||||
MwErrorResponse::class.java
|
||||
).error!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
} catch (e: MwIOException) {
|
||||
// Log the error as debug (and therefore, "expected") or at error level
|
||||
if (suppressErrors) {
|
||||
Timber.d(e, "Suppressed (known / expected) error")
|
||||
} else {
|
||||
Timber.e(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return rsp
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ interface CategoriesContract {
|
|||
|
||||
fun showError(stringResourceId: Int)
|
||||
|
||||
/**
|
||||
* Show a cancelable AlertDialog with a given message.
|
||||
*/
|
||||
fun showErrorDialog(message: String)
|
||||
|
||||
fun setCategories(categories: List<CategoryItem>?)
|
||||
|
||||
fun goToNextScreen()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD
|
|||
import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import fr.free.nrw.commons.upload.depicts.proxy
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwIOException
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Scheduler
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
|
@ -75,7 +76,12 @@ class CategoriesPresenter
|
|||
},
|
||||
{ t: Throwable? ->
|
||||
view.showProgress(false)
|
||||
view.showError(R.string.no_categories_found)
|
||||
view.showError(R.string.error_loading_categories)
|
||||
val mwException = t as? MwIOException
|
||||
view.showErrorDialog(
|
||||
if (mwException == null) ""
|
||||
else "\n${mwException.error.title} / ${mwException.error.details}"
|
||||
)
|
||||
Timber.e(t)
|
||||
},
|
||||
),
|
||||
|
|
@ -194,7 +200,12 @@ class CategoriesPresenter
|
|||
},
|
||||
{ t: Throwable? ->
|
||||
view.showProgress(false)
|
||||
view.showError(R.string.no_categories_found)
|
||||
view.showError(R.string.error_loading_categories)
|
||||
val mwException = t as? MwIOException
|
||||
view.showErrorDialog(
|
||||
if (mwException == null) ""
|
||||
else "\n${mwException.error.title} / ${mwException.error.details}"
|
||||
)
|
||||
Timber.e(t)
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.jakewharton.rxbinding2.view.RxView
|
||||
|
|
@ -32,7 +33,6 @@ import io.reactivex.Notification
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import timber.log.Timber
|
||||
import java.util.Objects
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -199,6 +199,15 @@ class UploadCategoriesFragment : UploadBaseFragment(), CategoriesContract.View {
|
|||
binding?.tilContainerSearch?.error = getString(stringResourceId)
|
||||
}
|
||||
|
||||
override fun showErrorDialog(message: String) {
|
||||
AlertDialog
|
||||
.Builder(requireContext())
|
||||
.setMessage(getString(R.string.error_loading_categories) + "\n" + message)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.ok){_,_ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun setCategories(categories: List<CategoryItem>?) {
|
||||
if (adapter == null) {
|
||||
Timber.e("Adapter is null in setCategories")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package fr.free.nrw.commons.wikidata.mwapi
|
||||
|
||||
import fr.free.nrw.commons.wikidata.model.BaseModel
|
||||
|
||||
class MwErrorResponse : BaseModel() {
|
||||
val error: MwLegacyServiceError? = null
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.wikidata.mwapi
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
class MwIOException(string: String, val error: MwLegacyServiceError) : IOException(string)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package fr.free.nrw.commons.wikidata.mwapi
|
||||
|
||||
import fr.free.nrw.commons.wikidata.model.BaseModel
|
||||
|
||||
class MwLegacyServiceError : BaseModel() {
|
||||
val code: String? = null
|
||||
private val info: String? = null
|
||||
|
||||
val title: String
|
||||
get() = code ?: ""
|
||||
|
||||
val details: String
|
||||
get() = info ?: ""
|
||||
}
|
||||
|
|
@ -153,6 +153,13 @@ class UploadCategoriesFragmentUnitTests {
|
|||
fragment.showError(R.string.no_categories_found)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testShowErrorDialog() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
fragment.showErrorDialog("")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testSetCategoriesCaseNull() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue