From 11f508e346b658730a84d18dea3a7287d522aed5 Mon Sep 17 00:00:00 2001 From: Andrei Toader Date: Mon, 19 Oct 2020 20:07:48 +0200 Subject: [PATCH 1/5] bugfix/error_signing_redirect: Added error handling * Added a retrofit exception that transforms all the retrofit rx calls to the retrofit exception. Retrofit exception is of 3 types, http, network and unexpected. * Added WebViewException, RetrofitException and EmptyResultsException. WebViewException is called when the webview fails to load an url. EmptyResultsException is thrown in case the data retrieved from both database and network is null or empty. * Added a base fragment, namely, BaseViewModelFragment where we handle the error received from the viewmodel by overriding onError method. * Added error handling when login fails. * Replaced all this from methods like viewModel.obj.observer(this, ...) with viewLifecycleOwner. * Created a few utility methods in Utils. * Improvd Result class so now it supports success and error checks. * Added error codes for not authorized, unknown and not found. * Added some error messages to the strings.xml. * Added logs for unknown actions. * Added todos for not fixed errors. --- .../monitorizarevot/data/dao/FormsDao.kt | 2 +- .../exceptions/EmptyResultsException.kt | 5 + .../monitorizarevot/exceptions/ErrorCodes.kt | 7 ++ .../exceptions/RetrofitException.kt | 106 ++++++++++++++++ .../exceptions/WebViewException.kt | 8 ++ .../extensions/RetrofitExtensions.kt | 9 ++ .../RxErrorHandlingCallAdapterFactory.kt | 114 ++++++++++++++++++ .../monitorizarevot/helper/APIError400.kt | 3 - .../ro/code4/monitorizarevot/helper/Result.kt | 42 ++++++- .../ro/code4/monitorizarevot/helper/Utils.kt | 36 ++++++ .../code4/monitorizarevot/modules/Modules.kt | 3 +- .../repositories/Repository.kt | 52 +++++--- .../monitorizarevot/services/ApiInterface.kt | 3 +- .../monitorizarevot/ui/base/BaseActivity.kt | 57 +-------- .../ui/base/BaseViewModelFragment.kt | 62 ++++++++++ .../ui/base/ViewModelFragment.kt | 3 + .../monitorizarevot/ui/forms/FormsFragment.kt | 24 ++-- .../ui/forms/FormsListFragment.kt | 15 ++- .../ui/forms/FormsViewModel.kt | 34 ++++-- .../forms/questions/BaseQuestionViewModel.kt | 5 + .../questions/QuestionsDetailsFragment.kt | 26 ++-- .../questions/QuestionsDetailsViewModel.kt | 20 ++- .../forms/questions/QuestionsListFragment.kt | 7 +- .../monitorizarevot/ui/guide/GuideFragment.kt | 46 ++++++- .../monitorizarevot/ui/login/LoginActivity.kt | 73 +++++++++-- .../ui/login/LoginViewModel.kt | 2 +- .../monitorizarevot/ui/notes/NoteFragment.kt | 11 +- .../ui/section/PollingStationActivity.kt | 2 - .../details/PollingStationDetailsFragment.kt | 10 +- .../PollingStationSelectionFragment.kt | 13 +- .../PollingStationSelectionViewModel.kt | 21 ++-- app/src/main/res/values-ro-rRO/strings.xml | 11 +- app/src/main/res/values/strings.xml | 10 ++ 33 files changed, 674 insertions(+), 168 deletions(-) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/extensions/RetrofitExtensions.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt delete mode 100644 app/src/main/java/ro/code4/monitorizarevot/helper/APIError400.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt b/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt index b3b1689a..d80f0ad3 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt @@ -115,7 +115,7 @@ interface FormsDao { pollingStationNumber: Int, formId: Int, synced: Boolean = true - ) + ): Completable @Query("UPDATE question SET hasNotes=:hasNotes WHERE id=:questionId") fun updateQuestionWithNotes( diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt new file mode 100644 index 00000000..58fa3db0 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt @@ -0,0 +1,5 @@ +package ro.code4.monitorizarevot.exceptions + +import java.lang.Exception + +class EmptyResultsException(message: String, thr: Throwable? = null) : Exception(message, thr) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt new file mode 100644 index 00000000..44e09326 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt @@ -0,0 +1,7 @@ +package ro.code4.monitorizarevot.exceptions + +object ErrorCodes { + const val UNAUTHORIZED = 401 + const val NOT_FOUND = 404 + const val UNKNOWN = 400 +} \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt new file mode 100644 index 00000000..0f686bfc --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt @@ -0,0 +1,106 @@ +package ro.code4.monitorizarevot.exceptions + +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Response +import retrofit2.Retrofit +import java.io.IOException + + +/** + * Exception that is retrieved from retrofit. It is of three types, http, network and unexpected. + */ +open class RetrofitException internal constructor( + message: String?, + /** + * RobResponse object containing status code, headers, body, etc. + */ + val response: Response<*>?, + /** + * The event kind which triggered this error. + */ + val kind: Kind, + val exception: Throwable?, + /** + * The Retrofit this request was executed on + */ + val retrofit: Retrofit? +) : + RuntimeException(message, exception) { + /** + * Identifies the event kind which triggered a [RetrofitException]. + */ + enum class Kind { + /** + * An [IOException] occurred while communicating to the server. + */ + NETWORK, + + /** + * A non-200 HTTP status code was received from the server. + */ + HTTP, + + /** + * An internal error occurred while attempting to execute a request. It is best practice to + * re-throw this exception so your application crashes. + */ + UNEXPECTED + } + + /** + * HTTP response body converted to specified `type`. `null` if there is no + * response. + * + * @param type + * @throws IOException if unable to convert the body to the specified `type`. + */ + @Throws(IOException::class) + fun getErrorBodyAs(type: Class<*>?): T? { + if (response?.errorBody() == null) { + return null + } + val converter: Converter = + retrofit!!.responseBodyConverter(type, arrayOfNulls(0)) + return converter.convert(response.errorBody()) + } + + companion object { + fun httpError( + response: Response<*>, + retrofit: Retrofit? + ): RetrofitException { + val message = response.code().toString() + " " + response.message() + return RetrofitException( + message, + response, + Kind.HTTP, + null, + retrofit + ) + } + + fun networkError(exception: IOException): RetrofitException { + return RetrofitException( + exception.message, + null, + Kind.NETWORK, + exception, + null + ) + } + + fun unexpectedError(exception: Throwable): RetrofitException { + return RetrofitException( + exception.message, + null, + Kind.UNEXPECTED, + exception, + null + ) + } + } + +} + + diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt new file mode 100644 index 00000000..5f6ba551 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt @@ -0,0 +1,8 @@ +package ro.code4.monitorizarevot.exceptions + +import java.lang.Exception + +/** + * Exception thrown when there is an issue with webview, loading paage, etc. + */ +class WebViewException(override val message: String, val code: Int = -1) : Exception(message) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/extensions/RetrofitExtensions.kt b/app/src/main/java/ro/code4/monitorizarevot/extensions/RetrofitExtensions.kt new file mode 100644 index 00000000..3eaf02ad --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/extensions/RetrofitExtensions.kt @@ -0,0 +1,9 @@ +package ro.code4.monitorizarevot.extensions + +import retrofit2.HttpException +import retrofit2.Response + +fun Response.successOrThrow(): Boolean { + if (!isSuccessful) throw HttpException(this) + return true +} \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt b/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt new file mode 100644 index 00000000..41af0a28 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt @@ -0,0 +1,114 @@ +package ro.code4.monitorizarevot.extensions + +import io.reactivex.* +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.HttpException +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import ro.code4.monitorizarevot.exceptions.RetrofitException +import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.httpError +import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.networkError +import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.unexpectedError +import java.io.IOException +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +/** + * Rxjava error handling CallAdapter factory. This class ensures the mapping of errors to + * one of the following exceptions: http, network or unexpected exceptions. + */ +class RxErrorHandlingCallAdapterFactory private constructor() : CallAdapter.Factory() { + private val original: RxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create() + + override fun get( + returnType: Type, annotations: Array, + retrofit: Retrofit + ): CallAdapter<*, *> { + return RxCallAdapterWrapper( + returnType, + retrofit, + (original.get(returnType, annotations, retrofit) as CallAdapter) + ) + } + + internal inner class RxCallAdapterWrapper( + private val returnType: Type, + private val retrofit: Retrofit, + private val wrapped: CallAdapter + ) : + CallAdapter { + override fun responseType(): Type { + return wrapped.responseType() + } + + override fun adapt(call: Call): Any? { + val rawType = getRawType(returnType) + + val isFlowable = rawType == Flowable::class.java + val isSingle = rawType == Single::class.java + val isMaybe = rawType == Maybe::class.java + val isCompletable = rawType == Completable::class.java + if (rawType != Observable::class.java && !isFlowable && !isSingle && !isMaybe) { + return null + } + if (returnType !is ParameterizedType) { + val name = if (isFlowable) + "Flowable" + else if (isSingle) "Single" else if (isMaybe) "Maybe" else "Observable" + throw IllegalStateException( + name + + " return type must be parameterized" + + " as " + + name + + " or " + + name + + "" + ) + } + + if (isFlowable) { + return (wrapped.adapt(call) as Flowable<*>).onErrorResumeNext { throwable: Throwable -> + Flowable.error(asRetrofitException(throwable)) + } + } + if (isSingle) { + return (wrapped.adapt(call) as Single<*>).onErrorResumeNext { throwable -> + Single.error(asRetrofitException(throwable)) + } + } + if (isMaybe) { + return (wrapped.adapt(call) as Maybe<*>).onErrorResumeNext { throwable: Throwable -> + Maybe.error(asRetrofitException(throwable)) + } + } + if (isCompletable) { + return (wrapped.adapt(call) as Completable).onErrorResumeNext { throwable -> + Completable.error(asRetrofitException(throwable)) + } + } + return (wrapped.adapt(call) as Observable<*>).onErrorResumeNext { throwable: Throwable -> + Observable.error(asRetrofitException(throwable)) + } + } + + private fun asRetrofitException(throwable: Throwable): RetrofitException { + if (throwable is HttpException) { + val response = throwable.response() + return httpError(response!!, retrofit) + } else if (throwable is IOException) { + return networkError(throwable) + } + return unexpectedError(throwable) + } + } + + companion object { + const val TAG = "RxErrorHandlingCallAdapterFactory" + + fun create(): CallAdapter.Factory { + return RxErrorHandlingCallAdapterFactory() + } + } +} + diff --git a/app/src/main/java/ro/code4/monitorizarevot/helper/APIError400.kt b/app/src/main/java/ro/code4/monitorizarevot/helper/APIError400.kt deleted file mode 100644 index ffa88f7d..00000000 --- a/app/src/main/java/ro/code4/monitorizarevot/helper/APIError400.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ro.code4.monitorizarevot.helper - -class APIError400 (var error: String) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/helper/Result.kt b/app/src/main/java/ro/code4/monitorizarevot/helper/Result.kt index e5e5583d..ef6898c0 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/helper/Result.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/helper/Result.kt @@ -1,13 +1,21 @@ package ro.code4.monitorizarevot.helper + /** - * .:.:.:. Created by @henrikhorbovyi on 13/10/19 .:.:.:. + + * Class that encapsulates successful result with a value of type [T] or + * a failure with a [Throwable] exception. */ sealed class Result { - class Failure(val error: Throwable, val message: String = "") : Result() - class Success(val data: T? = null) : Result() + data class Error(val exception: Throwable) : Result() + data class Success(val data: T? = null) : Result() object Loading : Result() + fun exceptionOrNull(): Throwable? = + when (this) { + is Error -> exception + else -> null + } fun handle( onSuccess: (T?) -> Unit = {}, @@ -16,8 +24,32 @@ sealed class Result { ) { when (this) { is Success -> onSuccess(data) - is Failure -> onFailure(error) + is Error -> onFailure(exception) is Loading -> onLoading() } } -} \ No newline at end of file + + override fun toString(): String { + return when (this) { + is Success<*> -> "Success[data=$data]" + is Error -> "Error[exception=$exception]" + Loading -> "Loading" + } + } +} + +val Result<*>.succeeded + get() = this is Result.Success && data != null + +val Result<*>.error + get() = this is Result.Error + +inline fun Result.getOrThrow(onFailure: (exception: Throwable) -> R): R { + return when (val exception = exceptionOrNull()) { + null -> data as T + else -> onFailure(exception) + } +} + +val Result.data: T? + get() = (this as? Result.Success)?.data diff --git a/app/src/main/java/ro/code4/monitorizarevot/helper/Utils.kt b/app/src/main/java/ro/code4/monitorizarevot/helper/Utils.kt index 24895fae..51ffeb24 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/helper/Utils.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/helper/Utils.kt @@ -3,6 +3,7 @@ package ro.code4.monitorizarevot.helper import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.graphics.Rect import android.graphics.Typeface @@ -14,13 +15,17 @@ import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.text.* +import android.text.method.LinkMovementMethod import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan +import android.text.util.Linkify import android.view.MotionEvent import android.view.View import android.view.inputmethod.InputMethodManager +import android.widget.TextView import android.widget.EditText import androidx.annotation.IdRes +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.content.FileProvider @@ -45,6 +50,7 @@ import ro.code4.monitorizarevot.ui.section.PollingStationActivity import java.io.File import java.text.SimpleDateFormat import java.util.* +import java.util.concurrent.TimeUnit fun String.createMultipart(name: String): MultipartBody.Part { @@ -447,6 +453,36 @@ fun Context.browse(url: String, newTask: Boolean = false): Boolean { } } +fun Activity.createAndShowDialog( + message: String, + callback:() -> Unit, + title: String = getString(R.string.error_generic) +): AlertDialog? { + + val s = SpannableString(message) + + //added a TextView + val tx1 = TextView(this) + tx1.text = s + tx1.autoLinkMask = Activity.RESULT_OK + tx1.movementMethod = LinkMovementMethod.getInstance() + val valueInPixels = resources.getDimension(R.dimen.big_margin).toInt() + tx1.setPadding(valueInPixels, valueInPixels, valueInPixels, valueInPixels) + + Linkify.addLinks(s, Linkify.PHONE_NUMBERS) + val builder = AlertDialog.Builder(this) + return builder.setTitle(title) + .setCancelable(false) + .setPositiveButton(R.string.push_notification_ok) + { p0, _ -> p0.dismiss() } + .setCancelable(false) + .setOnDismissListener { + callback() + } + .setView(tx1) + .show() +} + @Suppress("NOTHING_TO_INLINE") internal inline fun FirebaseRemoteConfig?.getStringOrDefault(key: String, defaultValue: String) = this?.getString(key).takeUnless { diff --git a/app/src/main/java/ro/code4/monitorizarevot/modules/Modules.kt b/app/src/main/java/ro/code4/monitorizarevot/modules/Modules.kt index 8cf3f6f0..d5904236 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/modules/Modules.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/modules/Modules.kt @@ -19,6 +19,7 @@ import ro.code4.monitorizarevot.App import ro.code4.monitorizarevot.BuildConfig.API_URL import ro.code4.monitorizarevot.BuildConfig.DEBUG import ro.code4.monitorizarevot.data.AppDatabase +import ro.code4.monitorizarevot.extensions.RxErrorHandlingCallAdapterFactory import ro.code4.monitorizarevot.helper.getToken import ro.code4.monitorizarevot.repositories.Repository import ro.code4.monitorizarevot.ui.forms.FormsViewModel @@ -84,9 +85,9 @@ val apiModule = module { single { Retrofit.Builder() .baseUrl(API_URL) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create()) .client(get()) .build() } diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index 05cff46e..e8571186 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -7,6 +7,7 @@ import io.reactivex.Observable import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction +import io.reactivex.functions.Consumer import io.reactivex.schedulers.Schedulers import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody @@ -14,6 +15,7 @@ import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.ResponseBody import org.koin.core.KoinComponent import org.koin.core.inject +import retrofit2.Response import retrofit2.Retrofit import ro.code4.monitorizarevot.data.AppDatabase import ro.code4.monitorizarevot.data.model.* @@ -25,6 +27,8 @@ import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO import ro.code4.monitorizarevot.data.pojo.FormWithSections import ro.code4.monitorizarevot.data.pojo.PollingStationInfo import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions +import ro.code4.monitorizarevot.exceptions.EmptyResultsException +import ro.code4.monitorizarevot.extensions.successOrThrow import ro.code4.monitorizarevot.helper.createMultipart import ro.code4.monitorizarevot.services.ApiInterface import ro.code4.monitorizarevot.services.LoginInterface @@ -69,10 +73,15 @@ class Repository : KoinComponent { apiCounties } apiCounties.isNotEmpty() && areAllApiCountiesInDb -> apiCounties - else -> dbCounties + dbCounties.isNotEmpty() -> + dbCounties + else -> + throw EmptyResultsException("empty results.") } } - ) + ).doOnError(Consumer { + Log.e(TAG, "exception received when fetching the counties:$it") + }) } fun getPollingStationDetails( @@ -122,8 +131,12 @@ class Repository : KoinComponent { db.formDetailsDao().getSectionsWithQuestions(formId) fun getForms(): Observable { + val observableDb = db.formDetailsDao().getFormsWithSections() val observableApi = apiInterface.getForms() + + // todo fix this as it does not work as expected. nulls are not treated as return values. + // instead it throws a NullPointerException. return Observable.zip( observableDb.onErrorReturn { null }, observableApi.onErrorReturn { null }, @@ -236,25 +249,30 @@ class Repository : KoinComponent { }) } - @SuppressLint("CheckResult") - fun syncAnswers(countyCode: String, pollingStationNumber: Int, formId: Int) { - db.formDetailsDao().getNotSyncedQuestionsForForm(countyCode, pollingStationNumber, formId) + fun syncAnswers( + countyCode: String, + pollingStationNumber: Int, + formId: Int + ): Observable { + return db.formDetailsDao() + .getNotSyncedQuestionsForForm(countyCode, pollingStationNumber, formId) .toObservable() - .subscribeOn(Schedulers.io()).flatMap { - syncAnswers(it) - }.observeOn(AndroidSchedulers.mainThread()).subscribe({ - Observable.create { - db.formDetailsDao() - .updateAnsweredQuestions(countyCode, pollingStationNumber, formId) - }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribe() - }, { - Log.i(TAG, it.message ?: "Error on synchronizing data") - }) + .subscribeOn(Schedulers.io()) + .flatMap { + return@flatMap syncAnswers(it) + } + .map { + return@map it.successOrThrow() + } + .flatMap { + return@flatMap db.formDetailsDao() + .updateAnsweredQuestions(countyCode, pollingStationNumber, formId) + .andThen(Observable.just(it)) + } } - private fun syncAnswers(list: List): Observable { + private fun syncAnswers(list: List): Observable> { val responseAnswerContainer = ResponseAnswerContainer() responseAnswerContainer.answers = list.map { it.answeredQuestion.options = it.selectedAnswers diff --git a/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt b/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt index 21d36782..2c28252a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt @@ -4,6 +4,7 @@ import io.reactivex.Observable import io.reactivex.Single import okhttp3.MultipartBody import okhttp3.ResponseBody +import retrofit2.Response import retrofit2.http.* import ro.code4.monitorizarevot.data.model.County import ro.code4.monitorizarevot.data.model.PollingStation @@ -25,7 +26,7 @@ interface ApiInterface { fun postPollingStationDetails(@Body pollingStation: PollingStation): Observable @POST("/api/v1/answers") - fun postQuestionAnswer(@Body responseAnswer: ResponseAnswerContainer): Observable + fun postQuestionAnswer(@Body responseAnswer: ResponseAnswerContainer): Observable> @Multipart @POST("/api/v2/note/upload") diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt index 1ff0b752..9941c571 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt @@ -1,35 +1,25 @@ package ro.code4.monitorizarevot.ui.base -import android.app.Activity import android.content.Context import android.os.Bundle -import android.text.SpannableString -import android.text.method.LinkMovementMethod -import android.text.util.Linkify import android.view.MotionEvent import android.view.View -import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import com.google.android.material.snackbar.Snackbar -import com.google.gson.Gson -import retrofit2.HttpException import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.helper.APIError400 import ro.code4.monitorizarevot.helper.LocaleManager import ro.code4.monitorizarevot.helper.collapseKeyboardIfFocusOutsideEditText -import ro.code4.monitorizarevot.helper.fromJson import ro.code4.monitorizarevot.helper.lifecycle.ActivityCallbacks import ro.code4.monitorizarevot.interfaces.Layout import ro.code4.monitorizarevot.interfaces.ViewModelSetter - abstract class BaseActivity : AppCompatActivity(), Layout, ViewModelSetter { private val mCallbacks = ActivityCallbacks() - private var dialog: AlertDialog? = null + internal var dialog: AlertDialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) LocaleManager.wrapContext(this) @@ -74,51 +64,6 @@ abstract class BaseActivity : AppCompatActivity(), Layout } } - fun handleThrowable(exception: Throwable, otherError: () -> Unit) { - when (exception) { - is HttpException -> { - if (exception.code() == 400) { - val apiError400 = exception.response()?.errorBody()?.string()?.fromJson(Gson(), APIError400::class.java) - - apiError400?.let { - if (it.error == null) { - otherError() - - return - } - - val s = SpannableString(it.error) - - //added a TextView - val tx1 = TextView(this) - tx1.text = s - tx1.autoLinkMask = Activity.RESULT_OK - tx1.movementMethod = LinkMovementMethod.getInstance() - val valueInPixels = resources.getDimension(R.dimen.big_margin).toInt() - tx1.setPadding(valueInPixels, valueInPixels, valueInPixels, valueInPixels) - - Linkify.addLinks(s, Linkify.PHONE_NUMBERS) - val builder = AlertDialog.Builder(this) - builder.setTitle(getString(R.string.error_generic)) - .setCancelable(false) - .setPositiveButton(R.string.push_notification_ok) - { p0, _ -> p0.dismiss() } - .setCancelable(false) - .setOnDismissListener { dialog = null } - .setView(tx1) - .show() - - } - } else { - otherError() - } - } - else -> { - otherError() - } - } - } - fun showDefaultErrorSnackBar(view: View) { showErrorSnackBar(view, getString(R.string.error_generic)) } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt new file mode 100644 index 00000000..a7cfc4ce --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt @@ -0,0 +1,62 @@ +package ro.code4.monitorizarevot.ui.base + +import ro.code4.monitorizarevot.R +import ro.code4.monitorizarevot.exceptions.EmptyResultsException +import ro.code4.monitorizarevot.exceptions.ErrorCodes +import ro.code4.monitorizarevot.exceptions.RetrofitException +import ro.code4.monitorizarevot.helper.createAndShowDialog +import ro.code4.monitorizarevot.helper.logE +import ro.code4.monitorizarevot.helper.startActivityWithoutTrace +import ro.code4.monitorizarevot.ui.login.LoginActivity + +abstract class BaseViewModelFragment : ViewModelFragment() { + + override fun onError(thr: Throwable) { + when (thr) { + is RetrofitException -> { + processRetrofitException(thr) + } + is EmptyResultsException -> { + val messageId: String = getString(R.string.no_counties_found) + activity?.createAndShowDialog(messageId, { + logE(TAG, "action needed.") + }) + } + } + } + + protected open fun processRetrofitException(thr: RetrofitException) { + when (thr.kind) { + RetrofitException.Kind.HTTP -> { + when (thr.response?.code()) { + ErrorCodes.UNAUTHORIZED -> { + startLoginActivity() + } + ErrorCodes.UNKNOWN -> { + logE(TAG, "unknown error.") + } + else -> { + logE(TAG, "unexpected exception.") + } + } + } + + RetrofitException.Kind.NETWORK -> { + logE(TAG, "network error.") + //todo do something about it. + } + + RetrofitException.Kind.UNEXPECTED -> { + logE(TAG, "unexpected error.") + //todo do something about it. + } + } + } + + private fun startLoginActivity() = + activity?.startActivityWithoutTrace(activity = LoginActivity::class.java) + + companion object { + const val TAG = "BaseViewModelFragment" + } +} \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt index c93d8310..2ee27dea 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt @@ -16,6 +16,7 @@ abstract class ViewModelFragment : BaseAnalyticsFragment( super.onAttach(context) mContext = context } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -23,4 +24,6 @@ abstract class ViewModelFragment : BaseAnalyticsFragment( ): View? { return inflater.inflate(layout, container, false) } + + open fun onError(thr: Throwable) = Unit } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt index 26d92b1f..8e90af13 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt @@ -12,7 +12,9 @@ import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.helper.Constants.FORM import ro.code4.monitorizarevot.helper.Constants.QUESTION import ro.code4.monitorizarevot.helper.changePollingStation +import ro.code4.monitorizarevot.helper.data import ro.code4.monitorizarevot.helper.replaceFragment +import ro.code4.monitorizarevot.helper.succeeded import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.questions.QuestionsDetailsFragment import ro.code4.monitorizarevot.ui.forms.questions.QuestionsListFragment @@ -37,16 +39,22 @@ class FormsFragment : ViewModelFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.pollingStation().observe(this, Observer { - pollingStationBarText.text = - getString(R.string.polling_station, it.pollingStationNumber, it.countyName) + viewModel.pollingStation().observe(viewLifecycleOwner, Observer { + if (it.succeeded) { + pollingStationBarText.text = + getString( + R.string.polling_station, + it.data?.pollingStationNumber, + it.data?.countyName + ) + } }) - viewModel.title().observe(this, Observer { + viewModel.title().observe(viewLifecycleOwner, Observer { (activity as MainActivity).setTitle(it) }) - viewModel.selectedForm().observe(this, Observer { + viewModel.selectedForm().observe(viewLifecycleOwner, Observer { childFragmentManager.replaceFragment( R.id.content, QuestionsListFragment(), @@ -54,7 +62,7 @@ class FormsFragment : ViewModelFragment() { QuestionsListFragment.TAG ) }) - viewModel.selectedQuestion().observe(this, Observer { + viewModel.selectedQuestion().observe(viewLifecycleOwner, Observer { childFragmentManager.replaceFragment( R.id.content, QuestionsDetailsFragment(), @@ -65,7 +73,7 @@ class FormsFragment : ViewModelFragment() { QuestionsDetailsFragment.TAG ) }) - viewModel.navigateToNotes().observe(this, Observer { + viewModel.navigateToNotes().observe(viewLifecycleOwner, Observer { childFragmentManager.replaceFragment( R.id.content, NoteFragment(), @@ -86,6 +94,4 @@ class FormsFragment : ViewModelFragment() { ) } - - } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt index c4a0efb9..b5371ddf 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt @@ -15,7 +15,7 @@ import ro.code4.monitorizarevot.adapters.FormDelegationAdapter import ro.code4.monitorizarevot.analytics.Event import ro.code4.monitorizarevot.analytics.Param import ro.code4.monitorizarevot.analytics.ParamKey -import ro.code4.monitorizarevot.helper.isOnline +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.ui.base.ViewModelFragment @@ -41,15 +41,20 @@ class FormsListFragment : ViewModelFragment() { override fun onAttach(context: Context) { super.onAttach(context) - viewModel = getSharedViewModel(from = { parentFragment!! }) + viewModel = getSharedViewModel(from = { requireParentFragment() }) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.forms().observe(this, Observer { - formAdapter.items = it + viewModel.forms().observe(viewLifecycleOwner, Observer { + if (it.succeeded) { + formAdapter.items = it.data + } else { + logE(TAG, "forms retrieval failed.") + //todo add a dialog. + } }) - viewModel.syncVisibility().observe(this, Observer { + viewModel.syncVisibility().observe(viewLifecycleOwner, Observer { syncGroup.visibility = it }) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt index 736e1651..de07d2f7 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt @@ -18,20 +18,22 @@ import ro.code4.monitorizarevot.data.model.Question import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO import ro.code4.monitorizarevot.data.pojo.FormWithSections import ro.code4.monitorizarevot.data.pojo.PollingStationInfo +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.helper.Constants.REMOTE_CONFIG_FILTER_DIASPORA_FORMS import ro.code4.monitorizarevot.helper.completedPollingStationConfig import ro.code4.monitorizarevot.ui.base.BaseFormViewModel class FormsViewModel : BaseFormViewModel() { - private val formsLiveData = MutableLiveData>() + private val formsLiveData = MutableLiveData>>() private val selectedFormLiveData = MutableLiveData() private val selectedQuestionLiveData = MutableLiveData>() private val syncVisibilityLiveData = MediatorLiveData() private val navigateToNotesLiveData = MutableLiveData() - private val pollingStationLiveData = MutableLiveData() + private val pollingStationLiveData = MutableLiveData>() init { getForms() + //todo this does not work as expected. getPollingStationBarText() } @@ -67,12 +69,12 @@ class FormsViewModel : BaseFormViewModel() { }) } - fun forms(): LiveData> = formsLiveData + fun forms(): LiveData>> = formsLiveData fun selectedForm(): LiveData = selectedFormLiveData fun selectedQuestion(): LiveData> = selectedQuestionLiveData fun navigateToNotes(): LiveData = navigateToNotesLiveData - fun pollingStation(): LiveData = pollingStationLiveData + fun pollingStation(): LiveData> = pollingStationLiveData private fun getPollingStationBarText() { disposables.add( @@ -85,7 +87,7 @@ class FormsViewModel : BaseFormViewModel() { subscribe() } .subscribe({ - pollingStationLiveData.postValue(it) + pollingStationLiveData.postValue(Result.Success(it)) }, { onError(it) }) @@ -98,9 +100,13 @@ class FormsViewModel : BaseFormViewModel() { repository.getForms() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({}, { - onError(it) - }) + .subscribe( + { + logD(TAG, "retrieving polling bar text.") + }, + { + formsLiveData.postValue(Result.Error(it)) + }) ) } @@ -116,8 +122,9 @@ class FormsViewModel : BaseFormViewModel() { } catch (e: Exception) { false } + var formsList = when { - !filterDiasporaForms || pollingStationLiveData.value?.isDiaspora == true -> forms.map { + !filterDiasporaForms || pollingStationLiveData.value?.data?.isDiaspora == true -> forms.map { FormListItem(it) } else -> forms.filter { it.form.diaspora == false }.map { FormListItem(it) } @@ -125,7 +132,7 @@ class FormsViewModel : BaseFormViewModel() { formsList = formsList.sortedBy { it.formWithSections.form.order } items.addAll(formsList) items.add(AddNoteListItem()) - formsLiveData.postValue(items) + formsLiveData.postValue(Result.Success(items)) } fun selectForm(formDetails: FormDetails) { @@ -150,4 +157,11 @@ class FormsViewModel : BaseFormViewModel() { preferences.completedPollingStationConfig(false) } + override fun onError(throwable: Throwable) { + logE(TAG, "exception." + throwable.message) + } + + companion object { + const val TAG = "FormsViewModel" + } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt index d057a686..2d931ee6 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt @@ -10,6 +10,7 @@ import ro.code4.monitorizarevot.adapters.helper.ListItem import ro.code4.monitorizarevot.data.model.FormDetails import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions +import ro.code4.monitorizarevot.helper.Result import ro.code4.monitorizarevot.ui.base.BaseFormViewModel abstract class BaseQuestionViewModel : BaseFormViewModel() { @@ -17,6 +18,10 @@ abstract class BaseQuestionViewModel : BaseFormViewModel() { var selectedFormId: Int = -1 fun questions(): LiveData> = questionsLiveData + val syncLiveData = MutableLiveData>() + + fun syncData(): LiveData> = syncLiveData + private fun getQuestions(formId: Int) { selectedFormId = formId disposables.add(Observable.combineLatest( diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt index 1c52d54f..cb077d26 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt @@ -3,6 +3,7 @@ package ro.code4.monitorizarevot.ui.forms.questions import android.content.Context import android.os.Bundle import android.os.Parcelable +import android.util.Log import android.view.View import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager @@ -18,14 +19,12 @@ import ro.code4.monitorizarevot.adapters.QuestionDetailsAdapter import ro.code4.monitorizarevot.adapters.helper.QuestionDetailsListItem import ro.code4.monitorizarevot.data.model.FormDetails import ro.code4.monitorizarevot.data.model.Question -import ro.code4.monitorizarevot.helper.Constants -import ro.code4.monitorizarevot.helper.addOnLayoutChangeListenerForGalleryEffect -import ro.code4.monitorizarevot.helper.addOnScrollListenerForGalleryEffect -import ro.code4.monitorizarevot.ui.base.ViewModelFragment +import ro.code4.monitorizarevot.helper.* +import ro.code4.monitorizarevot.ui.base.BaseViewModelFragment import ro.code4.monitorizarevot.ui.forms.FormsViewModel -class QuestionsDetailsFragment : ViewModelFragment(), +class QuestionsDetailsFragment : BaseViewModelFragment(), QuestionDetailsAdapter.OnClickListener { override fun addNoteFor(question: Question) { baseViewModel.selectedNotes(question) @@ -49,17 +48,17 @@ class QuestionsDetailsFragment : ViewModelFragment(), override fun onAttach(context: Context) { super.onAttach(context) - baseViewModel = getSharedViewModel(from = { parentFragment!! }) + baseViewModel = getSharedViewModel(from = { requireParentFragment() }) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.questions().observe(this, Observer { list -> + viewModel.questions().observe(viewLifecycleOwner, Observer { list -> setData(ArrayList(list.map { it as QuestionDetailsListItem })) }) - viewModel.title().observe(this, Observer { + viewModel.title().observe(viewLifecycleOwner, Observer { baseViewModel.setTitle(it) }) @@ -107,9 +106,15 @@ class QuestionsDetailsFragment : ViewModelFragment(), setButtons() } } - }) + viewModel.syncData().observe(viewLifecycleOwner, Observer { it -> + val result = it.getOrThrow { + logE(TAG, "exception when syncing data:" + it.message) + onError(it) + } + logD(TAG, "success when syncing the data:$result") + }) } private fun setButtons() { @@ -159,8 +164,7 @@ class QuestionsDetailsFragment : ViewModelFragment(), if (::adapter.isInitialized) { viewModel.saveAnswer(adapter.getItem(currentPosition)) } - viewModel.syncData() + viewModel.syncAnswersData() super.onPause() - } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt index a3b226c1..42a5429f 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt @@ -1,5 +1,6 @@ package ro.code4.monitorizarevot.ui.forms.questions +import io.reactivex.android.schedulers.AndroidSchedulers import ro.code4.monitorizarevot.adapters.helper.ListItem import ro.code4.monitorizarevot.adapters.helper.MultiChoiceListItem import ro.code4.monitorizarevot.adapters.helper.QuestionDetailsListItem @@ -12,6 +13,8 @@ import ro.code4.monitorizarevot.helper.Constants.TYPE_MULTI_CHOICE import ro.code4.monitorizarevot.helper.Constants.TYPE_MULTI_CHOICE_DETAILS import ro.code4.monitorizarevot.helper.Constants.TYPE_SINGLE_CHOICE import ro.code4.monitorizarevot.helper.Constants.TYPE_SINGLE_CHOICE_DETAILS +import ro.code4.monitorizarevot.helper.Result +import ro.code4.monitorizarevot.helper.logD class QuestionsDetailsViewModel : BaseQuestionViewModel() { @@ -82,8 +85,21 @@ class QuestionsDetailsViewModel : BaseQuestionViewModel() { } } - fun syncData() { - repository.syncAnswers(countyCode, pollingStationNumber, selectedFormId) + fun syncAnswersData() { + val disposable = repository.syncAnswers(countyCode, pollingStationNumber, selectedFormId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + logD(TAG, "response:$it") + syncLiveData.postValue(Result.Success(it)) + }, { + syncLiveData.postValue(Result.Error(it)) + } + ) + disposables.add(disposable) } + companion object { + const val TAG = "QuestionsDetailsViewModel" + } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt index 2d0ca824..77d64c2d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt @@ -37,16 +37,16 @@ class QuestionsListFragment : ViewModelFragment() { override fun onAttach(context: Context) { super.onAttach(context) - baseViewModel = getSharedViewModel(from = { parentFragment!! }) + baseViewModel = getSharedViewModel(from = { requireParentFragment() }) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.questions().observe(this, Observer { + viewModel.questions().observe(viewLifecycleOwner, Observer { questionAdapter.items = it }) - viewModel.title().observe(this, Observer { + viewModel.title().observe(viewLifecycleOwner, Observer { baseViewModel.setTitle(it) }) viewModel.setData(Parcels.unwrap(arguments?.getParcelable((FORM)))) @@ -62,5 +62,4 @@ class QuestionsListFragment : ViewModelFragment() { } } - } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt index 15ed2421..77cf3c5b 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt @@ -3,6 +3,8 @@ package ro.code4.monitorizarevot.ui.guide import android.annotation.SuppressLint import android.graphics.Bitmap import android.os.Bundle +import android.text.TextUtils +import android.util.Log import android.view.View import android.webkit.WebChromeClient import android.webkit.WebResourceRequest @@ -12,10 +14,14 @@ import androidx.lifecycle.Observer import kotlinx.android.synthetic.main.fragment_guide.* import org.koin.android.ext.android.inject import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.ui.base.ViewModelFragment +import ro.code4.monitorizarevot.helper.createAndShowDialog +import ro.code4.monitorizarevot.ui.base.BaseViewModelFragment +import ro.code4.monitorizarevot.exceptions.WebViewException +import ro.code4.monitorizarevot.helper.logE import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class GuideFragment : ViewModelFragment() { +class GuideFragment : BaseViewModelFragment() { + override val layout: Int get() = R.layout.fragment_guide override val screenName: Int @@ -58,6 +64,24 @@ class GuideFragment : ViewModelFragment() { ): Boolean { return request.url?.let { !it.path.equals(viewModel.url().value) } ?: true } + + override fun onReceivedError( + view: WebView?, + errorCode: Int, + description: String?, + failingUrl: String? + ) { + var message: String? = description + if (TextUtils.isEmpty(message)) { + message = "Unknown Error" + } + onError( + WebViewException( + message!!, + errorCode + ) + ) + } } } viewModel.url().observe(viewLifecycleOwner, Observer { @@ -65,10 +89,28 @@ class GuideFragment : ViewModelFragment() { }) } + override fun onError(thr: Throwable) { + logE(TAG, "Error loading the page:" + thr.message) + var messageId: String = getString(R.string.error_generic_message) + if (thr is WebViewException) { + if (thr.message.contains("ERR_INTERNET_DISCONNECTED")) { + messageId = getString(R.string.error_no_connection) + } + } + + progressDialog.dismiss() + logE(TAG, messageId) + //todo add action + } + override fun onDestroyView() { if (progressDialog.isResumed) { progressDialog.dismissAllowingStateLoss() } super.onDestroyView() } + + companion object { + const val TAG = "GuideFragment" + } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt index 650e56dc..0bd191d3 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt @@ -6,13 +6,16 @@ import androidx.lifecycle.Observer import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_login.* import org.koin.android.viewmodel.ext.android.viewModel +import retrofit2.HttpException +import retrofit2.http.HTTP import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.helper.TextWatcherDelegate -import ro.code4.monitorizarevot.helper.isOnline -import ro.code4.monitorizarevot.helper.startActivityWithoutTrace +import ro.code4.monitorizarevot.exceptions.ErrorCodes +import ro.code4.monitorizarevot.exceptions.RetrofitException +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.ui.base.BaseAnalyticsActivity import ro.code4.monitorizarevot.widget.ProgressDialogFragment +import java.io.IOException class LoginActivity : BaseAnalyticsActivity() { @@ -60,13 +63,6 @@ class LoginActivity : BaseAnalyticsActivity() { private fun clickListenersSetup() { loginButton.setOnClickListener { - if (!isOnline()) { - Snackbar.make(loginButton, getString(R.string.login_no_internet), Snackbar.LENGTH_SHORT) - .show() - - return@setOnClickListener - } - loginButton.isEnabled = false viewModel.login(phone.text.toString(), password.text.toString()) } @@ -79,7 +75,7 @@ class LoginActivity : BaseAnalyticsActivity() { progressDialog.dismiss() activity?.let(::startActivityWithoutTrace) }, - onFailure = {error -> + onFailure = { error -> // TODO: Handle errors to show personalized messages for each one progressDialog.dismiss() @@ -95,4 +91,59 @@ class LoginActivity : BaseAnalyticsActivity() { ) }) } + + private fun handleThrowable(exception: Throwable, fallback: (exception: Throwable) -> Unit) { + when (exception) { + is RetrofitException -> { + when (exception.kind) { + RetrofitException.Kind.HTTP -> { + processHttpException(exception, fallback) + } + RetrofitException.Kind.NETWORK -> { + var messageId: String = getString(R.string.error_generic) + if (!isOnline()) { + messageId = getString(R.string.login_no_internet) + } + + Snackbar.make( + loginButton, + messageId, + Snackbar.LENGTH_SHORT + ).show() + } + else -> { + fallback(exception) + } + } + } + } + } + + private fun processHttpException( + exception: RetrofitException, + fallback: (exception: Throwable) -> Unit + ) { + val message = exception.message ?: getString(R.string.error_generic) + when (exception.response?.code()) { + ErrorCodes.UNKNOWN -> { + createAndShowDialog( + message, { + dialog = null + }, + getString(R.string.login_no_internet) + ) + } + ErrorCodes.UNAUTHORIZED -> { + createAndShowDialog( + message, { + dialog = null + }, + getString(R.string.login_unauthorized) + ) + } + else -> { + fallback(exception) + } + } + } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt index e1868d3c..b4dc376d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt @@ -83,6 +83,6 @@ class LoginViewModel : BaseViewModel() { override fun onError(throwable: Throwable) { logE("onError ${throwable.message}", throwable) - loginLiveData.postValue(Result.Failure(throwable)) + loginLiveData.postValue(Result.Error(throwable)) } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt index 3ea9690a..b6277d0c 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt @@ -33,7 +33,6 @@ import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_GALLERY import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_RECORD_VIDEO import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_TAKE_PHOTO -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.FormsViewModel @@ -53,8 +52,8 @@ class NoteFragment : ViewModelFragment(), PermissionManager.Permi private lateinit var permissionManager: PermissionManager override fun onAttach(context: Context) { super.onAttach(context) - permissionManager = PermissionManager(activity!!, this) - baseViewModel = getSharedViewModel(from = { parentFragment!! }) + permissionManager = PermissionManager(requireActivity(), this) + baseViewModel = getSharedViewModel(from = { requireParentFragment() }) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -66,15 +65,15 @@ class NoteFragment : ViewModelFragment(), PermissionManager.Permi .color(Color.TRANSPARENT) .sizeResId(R.dimen.small_margin).build() ) - viewModel.title().observe(this, Observer { + viewModel.title().observe(viewLifecycleOwner, Observer { baseViewModel.setTitle(it) }) viewModel.setData(Parcels.unwrap(arguments?.getParcelable((Constants.QUESTION)))) - viewModel.notes().observe(this, Observer { + viewModel.notes().observe(viewLifecycleOwner, Observer { noteAdapter.items = it }) - viewModel.fileName().observe(this, Observer { + viewModel.fileName().observe(viewLifecycleOwner, Observer { filenameText.text = it filenameText.visibility = View.VISIBLE addMediaButton.visibility = View.GONE diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/PollingStationActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/PollingStationActivity.kt index 931af7a3..1fab471b 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/PollingStationActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/PollingStationActivity.kt @@ -38,6 +38,4 @@ class PollingStationActivity : BaseActivity() { }) replaceFragment(R.id.container, PollingStationSelectionFragment()) } - - } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt index 5040f525..9397c23f 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt @@ -36,7 +36,7 @@ class PollingStationDetailsFragment : ViewModelFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.pollingStation().observe(this, Observer { + viewModel.pollingStation().observe(viewLifecycleOwner, Observer { pollingStationBarText.text = it }) viewModel.setTitle(getString(R.string.title_polling_station)) @@ -45,10 +45,10 @@ class PollingStationDetailsFragment : ViewModelFragment viewModel.notifyChangeRequested() activity?.onBackPressed() } - viewModel.departureTime().observe(this, Observer { + viewModel.departureTime().observe(viewLifecycleOwner, Observer { departureTime.text = it }) - viewModel.arrivalTime().observe(this, Observer { + viewModel.arrivalTime().observe(viewLifecycleOwner, Observer { arrivalTime.text = it }) arrivalTime.setOnClickListener { @@ -83,7 +83,7 @@ class PollingStationDetailsFragment : ViewModelFragment } }) } - viewModel.selectedPollingStation().observe(this, Observer { + viewModel.selectedPollingStation().observe(viewLifecycleOwner, Observer { setSelection(it) }) setContinueButton() @@ -100,7 +100,7 @@ class PollingStationDetailsFragment : ViewModelFragment private fun showDatePicker(dateTitleId: Int, timeTitleId: Int, listener: DateTimeListener) { val now = Calendar.getInstance() val datePickerDialog = DatePickerDialog( - activity!!, + requireActivity(), DatePickerDialog.OnDateSetListener { _, year, month, day -> showTimePicker(timeTitleId, year, month, day, listener) }, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt index 9c72bb3d..c4434c04 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt @@ -11,12 +11,13 @@ import org.koin.android.viewmodel.ext.android.getSharedViewModel import org.koin.android.viewmodel.ext.android.viewModel import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.BaseViewModelFragment import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.section.PollingStationViewModel import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class PollingStationSelectionFragment : ViewModelFragment() { +class PollingStationSelectionFragment : BaseViewModelFragment() { private val progressDialog: ProgressDialogFragment by lazy { ProgressDialogFragment().also { @@ -47,22 +48,24 @@ class PollingStationSelectionFragment : ViewModelFragment + result.handle( onSuccess = { counties -> progressDialog.dismiss() counties?.run(::setCountiesDropdown) }, onFailure = { progressDialog.dismiss() - // TODO: Show some message for the user know what happened + onError(it) }, onLoading = { activity?.run { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt index bc253a84..2bca6054 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt @@ -35,17 +35,20 @@ class PollingStationSelectionViewModel : BaseViewModel() { return } - disposables += repository.getCounties().subscribeOn(Schedulers.io()) + disposables += repository.getCounties() + .subscribeOn(Schedulers.io()) .doOnSuccess { counties.clear() counties.addAll(it) } .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ - updateCounties() - }, { - onError(it) - }) + .subscribe( + { + updateCounties() + }, + { + onError(it) + }) } private fun updateCounties() { @@ -71,9 +74,7 @@ class PollingStationSelectionViewModel : BaseViewModel() { } override fun onError(throwable: Throwable) { - // TODO: Handle errors to show a specific message for each one - countiesLiveData.postValue(Result.Failure(throwable)) + logE("onError ${throwable.message}", throwable) + countiesLiveData.postValue(Result.Error(throwable)) } - - } \ No newline at end of file diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index 8a2f755f..875c9815 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -12,6 +12,10 @@ Eroare de server Eroare necunoscută Eroare permisiune %1$S + Eroare de conexiune + + + Nu a fost gasit niciun judet. Te rog conecteaza-te la retea si incearca din nou. v%1$S aplicație dezvoltată de @@ -20,6 +24,9 @@ Login Ai nevoie de o conexiune la internet pentru a te conecta! + + Nu esti autorizat! + Continuă Înapoi @@ -141,6 +148,8 @@ A apărut o problemă! + + A aparut o problema si nu putem incarca datele! Despre aplicație @@ -150,4 +159,4 @@ Politică de confidențialitate Trimiteți email prin v%1$S build %2$d aplicație dezvoltată de - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b39e68b..95583eda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,10 @@ A server error has occurred An unknown error has occurred Permission error %1$S + Connection error + + + No counties found. Please connect to the network and try again. v%1$S app developed by @@ -20,6 +24,9 @@ Login You need to be connected to the internet to be able to log in! + + You are not authorized! + Next Back @@ -147,6 +154,9 @@ Something went wrong! + + Something went wrong and we cannot retrieve data! + About this app Vote Monitor is an application created by Code for Romania.

This is the first app, developed in Romania, intended to be used as part of the vote monitoring process. It\'s also one of the few such apps in the world. The app has been used, by observers, ever since the prezidential elections in 2016. In Poland, it was used in 2018, in the first independently-monitored elections.

The app is open source. Check it out on GitHub.

Code for Romania is an NGO that develops IT applications, pro-bono, with the overall goal of solving the pain points identified in society. If you want to support our activity, head over to our website and join the community, as a volunteer, or donate.]]>
From 1855137348cec4ce66e51e3dc2e45defc2dedd1e Mon Sep 17 00:00:00 2001 From: Andrei Toader Date: Mon, 2 Nov 2020 08:19:04 +0100 Subject: [PATCH 2/5] bugfix/error_signing_redirect: Fixed code review comments * Processed code review comments. --- .../exceptions/RetrofitException.kt | 2 +- .../monitorizarevot/repositories/Repository.kt | 3 ++- .../monitorizarevot/ui/guide/GuideFragment.kt | 15 ++++----------- .../monitorizarevot/ui/login/LoginActivity.kt | 6 ++---- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt index 0f686bfc..4cf78f55 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/RetrofitException.kt @@ -10,7 +10,7 @@ import java.io.IOException /** * Exception that is retrieved from retrofit. It is of three types, http, network and unexpected. */ -open class RetrofitException internal constructor( +class RetrofitException internal constructor( message: String?, /** * RobResponse object containing status code, headers, body, etc. diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index e8571186..5ecfab43 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -260,13 +260,14 @@ class Repository : KoinComponent { .toObservable() .subscribeOn(Schedulers.io()) .flatMap { - return@flatMap syncAnswers(it) + syncAnswers(it) } .map { return@map it.successOrThrow() } .flatMap { return@flatMap db.formDetailsDao() + .updateAnsweredQuestions(countyCode, pollingStationNumber, formId) .andThen(Observable.just(it)) } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt index 77cf3c5b..18095cdf 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt @@ -71,10 +71,7 @@ class GuideFragment : BaseViewModelFragment() { description: String?, failingUrl: String? ) { - var message: String? = description - if (TextUtils.isEmpty(message)) { - message = "Unknown Error" - } + val message = description.takeUnless { it.isNullOrEmpty() } ?: "Unknown Error" onError( WebViewException( message!!, @@ -91,15 +88,11 @@ class GuideFragment : BaseViewModelFragment() { override fun onError(thr: Throwable) { logE(TAG, "Error loading the page:" + thr.message) - var messageId: String = getString(R.string.error_generic_message) - if (thr is WebViewException) { - if (thr.message.contains("ERR_INTERNET_DISCONNECTED")) { - messageId = getString(R.string.error_no_connection) - } - } + val messageId = + if (thr is WebViewException) R.string.error_no_connection else R.string.error_generic_message progressDialog.dismiss() - logE(TAG, messageId) + logE(TAG, getString(messageId)) //todo add action } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt index 0bd191d3..01587b58 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt @@ -100,10 +100,8 @@ class LoginActivity : BaseAnalyticsActivity() { processHttpException(exception, fallback) } RetrofitException.Kind.NETWORK -> { - var messageId: String = getString(R.string.error_generic) - if (!isOnline()) { - messageId = getString(R.string.login_no_internet) - } + val messageId = + if (!isOnline()) R.string.login_no_internet else R.string.error_generic Snackbar.make( loginButton, From e9727af6eb2a7e09ff168b17378defdeccec66e7 Mon Sep 17 00:00:00 2001 From: Andrei Toader Date: Sat, 7 Nov 2020 11:29:59 +0100 Subject: [PATCH 3/5] bugfix/error_signing_redirect: Fixed error log not parsed * Fixed the error message not being parsed from the request in case an error 400 is received. In case it is null, we show the generic error message. --- .../ro/code4/monitorizarevot/exceptions/ErrorCodes.kt | 2 +- .../ro/code4/monitorizarevot/helper/ErrorResponse.kt | 5 +++++ .../monitorizarevot/ui/base/BaseViewModelFragment.kt | 2 +- .../ro/code4/monitorizarevot/ui/login/LoginActivity.kt | 9 +++------ app/src/main/res/values-ro-rRO/strings.xml | 3 +++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/helper/ErrorResponse.kt diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt index 44e09326..1df5dfb1 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/exceptions/ErrorCodes.kt @@ -3,5 +3,5 @@ package ro.code4.monitorizarevot.exceptions object ErrorCodes { const val UNAUTHORIZED = 401 const val NOT_FOUND = 404 - const val UNKNOWN = 400 + const val BAD_REQUEST = 400 } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/helper/ErrorResponse.kt b/app/src/main/java/ro/code4/monitorizarevot/helper/ErrorResponse.kt new file mode 100644 index 00000000..8a1b717a --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/helper/ErrorResponse.kt @@ -0,0 +1,5 @@ +package ro.code4.monitorizarevot.helper + +import com.google.gson.annotations.Expose + +data class ErrorResponse(@Expose var error: String) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt index a7cfc4ce..c819f8cd 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt @@ -32,7 +32,7 @@ abstract class BaseViewModelFragment : ViewModelFragment< ErrorCodes.UNAUTHORIZED -> { startLoginActivity() } - ErrorCodes.UNKNOWN -> { + ErrorCodes.BAD_REQUEST -> { logE(TAG, "unknown error.") } else -> { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt index 01587b58..2d03ff2d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginActivity.kt @@ -6,8 +6,6 @@ import androidx.lifecycle.Observer import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_login.* import org.koin.android.viewmodel.ext.android.viewModel -import retrofit2.HttpException -import retrofit2.http.HTTP import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.exceptions.ErrorCodes @@ -15,7 +13,6 @@ import ro.code4.monitorizarevot.exceptions.RetrofitException import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.ui.base.BaseAnalyticsActivity import ro.code4.monitorizarevot.widget.ProgressDialogFragment -import java.io.IOException class LoginActivity : BaseAnalyticsActivity() { @@ -121,14 +118,14 @@ class LoginActivity : BaseAnalyticsActivity() { exception: RetrofitException, fallback: (exception: Throwable) -> Unit ) { - val message = exception.message ?: getString(R.string.error_generic) + val message = exception.getErrorBodyAs(ErrorResponse::class.java)?.error ?: getString(R.string.error_generic) when (exception.response?.code()) { - ErrorCodes.UNKNOWN -> { + ErrorCodes.BAD_REQUEST -> { createAndShowDialog( message, { dialog = null }, - getString(R.string.login_no_internet) + getString(R.string.login_bad_request) ) } ErrorCodes.UNAUTHORIZED -> { diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index 875c9815..7635a914 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -24,6 +24,9 @@ Login Ai nevoie de o conexiune la internet pentru a te conecta! + + Bad Request! + Nu esti autorizat! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95583eda..5f78a1d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ Code Login You need to be connected to the internet to be able to log in! + Bad Request! You are not authorized! From 86eee3747767939404b567f502df98112c552590 Mon Sep 17 00:00:00 2001 From: Andrei Toader Date: Sun, 8 Nov 2020 13:54:12 +0100 Subject: [PATCH 4/5] bugfix/error_signing_redirect: fixed bug and reverted unnecessary changes * Fixed bug in onPause that was causing the disposable not to finish. * Reverted some unnecessary changes. --- .../monitorizarevot/data/dao/FormsDao.kt | 2 +- .../RxErrorHandlingCallAdapterFactory.kt | 15 ++++--- .../repositories/Repository.kt | 26 ++++++------- .../monitorizarevot/ui/forms/FormsFragment.kt | 10 +---- .../ui/forms/FormsListFragment.kt | 9 +---- .../ui/forms/FormsViewModel.kt | 34 +++++----------- .../forms/questions/BaseQuestionViewModel.kt | 4 +- .../questions/QuestionsDetailsViewModel.kt | 9 +++-- .../monitorizarevot/ui/guide/GuideFragment.kt | 39 +------------------ .../PollingStationSelectionFragment.kt | 10 ++--- .../PollingStationSelectionViewModel.kt | 19 +++++---- 11 files changed, 59 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt b/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt index d80f0ad3..b3b1689a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt @@ -115,7 +115,7 @@ interface FormsDao { pollingStationNumber: Int, formId: Int, synced: Boolean = true - ): Completable + ) @Query("UPDATE question SET hasNotes=:hasNotes WHERE id=:questionId") fun updateQuestionWithNotes( diff --git a/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt b/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt index 41af0a28..44fa8fa1 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/extensions/RxErrorHandlingCallAdapterFactory.kt @@ -93,13 +93,16 @@ class RxErrorHandlingCallAdapterFactory private constructor() : CallAdapter.Fact } private fun asRetrofitException(throwable: Throwable): RetrofitException { - if (throwable is HttpException) { - val response = throwable.response() - return httpError(response!!, retrofit) - } else if (throwable is IOException) { - return networkError(throwable) + return when (throwable) { + is HttpException -> { + val response = throwable.response() + httpError(response!!, retrofit) + } + is IOException -> { + networkError(throwable) + } + else -> unexpectedError(throwable) } - return unexpectedError(throwable) } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index 5ecfab43..e57e43df 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -3,6 +3,7 @@ package ro.code4.monitorizarevot.repositories import android.annotation.SuppressLint import android.util.Log import androidx.lifecycle.LiveData +import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers @@ -30,6 +31,7 @@ import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions import ro.code4.monitorizarevot.exceptions.EmptyResultsException import ro.code4.monitorizarevot.extensions.successOrThrow import ro.code4.monitorizarevot.helper.createMultipart +import ro.code4.monitorizarevot.helper.logD import ro.code4.monitorizarevot.services.ApiInterface import ro.code4.monitorizarevot.services.LoginInterface import java.io.File @@ -73,15 +75,10 @@ class Repository : KoinComponent { apiCounties } apiCounties.isNotEmpty() && areAllApiCountiesInDb -> apiCounties - dbCounties.isNotEmpty() -> - dbCounties - else -> - throw EmptyResultsException("empty results.") + else -> dbCounties } } - ).doOnError(Consumer { - Log.e(TAG, "exception received when fetching the counties:$it") - }) + ) } fun getPollingStationDetails( @@ -193,7 +190,8 @@ class Repository : KoinComponent { apiFormDetails.forEach { apiForm -> val dbForm = dbFormDetails.find { it.form.id == apiForm.id } if (dbForm != null && (apiForm.formVersion != dbForm.form.formVersion || - apiForm.order != dbForm.form.order)) { + apiForm.order != dbForm.form.order) + ) { deleteFormDetails(dbForm.form) saveFormDetails(apiForm) } @@ -262,14 +260,14 @@ class Repository : KoinComponent { .flatMap { syncAnswers(it) } - .map { - return@map it.successOrThrow() - } + .map { it.successOrThrow() } .flatMap { - return@flatMap db.formDetailsDao() + Completable.fromAction { + logD("saving the forms details to db for stationNr:$pollingStationNumber", TAG) + db.formDetailsDao() + .updateAnsweredQuestions(countyCode, pollingStationNumber, formId) - .updateAnsweredQuestions(countyCode, pollingStationNumber, formId) - .andThen(Observable.just(it)) + }.andThen(Observable.just(it)) } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt index 8e90af13..8d15f735 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt @@ -40,14 +40,8 @@ class FormsFragment : ViewModelFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.pollingStation().observe(viewLifecycleOwner, Observer { - if (it.succeeded) { - pollingStationBarText.text = - getString( - R.string.polling_station, - it.data?.pollingStationNumber, - it.data?.countyName - ) - } + pollingStationBarText.text = + getString(R.string.polling_station, it.pollingStationNumber, it.countyName) }) viewModel.title().observe(viewLifecycleOwner, Observer { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt index b5371ddf..a281487c 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt @@ -15,7 +15,7 @@ import ro.code4.monitorizarevot.adapters.FormDelegationAdapter import ro.code4.monitorizarevot.analytics.Event import ro.code4.monitorizarevot.analytics.Param import ro.code4.monitorizarevot.analytics.ParamKey -import ro.code4.monitorizarevot.helper.* +import ro.code4.monitorizarevot.helper.isOnline import ro.code4.monitorizarevot.ui.base.ViewModelFragment @@ -47,12 +47,7 @@ class FormsListFragment : ViewModelFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.forms().observe(viewLifecycleOwner, Observer { - if (it.succeeded) { - formAdapter.items = it.data - } else { - logE(TAG, "forms retrieval failed.") - //todo add a dialog. - } + formAdapter.items = it }) viewModel.syncVisibility().observe(viewLifecycleOwner, Observer { syncGroup.visibility = it diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt index de07d2f7..736e1651 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt @@ -18,22 +18,20 @@ import ro.code4.monitorizarevot.data.model.Question import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO import ro.code4.monitorizarevot.data.pojo.FormWithSections import ro.code4.monitorizarevot.data.pojo.PollingStationInfo -import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.helper.Constants.REMOTE_CONFIG_FILTER_DIASPORA_FORMS import ro.code4.monitorizarevot.helper.completedPollingStationConfig import ro.code4.monitorizarevot.ui.base.BaseFormViewModel class FormsViewModel : BaseFormViewModel() { - private val formsLiveData = MutableLiveData>>() + private val formsLiveData = MutableLiveData>() private val selectedFormLiveData = MutableLiveData() private val selectedQuestionLiveData = MutableLiveData>() private val syncVisibilityLiveData = MediatorLiveData() private val navigateToNotesLiveData = MutableLiveData() - private val pollingStationLiveData = MutableLiveData>() + private val pollingStationLiveData = MutableLiveData() init { getForms() - //todo this does not work as expected. getPollingStationBarText() } @@ -69,12 +67,12 @@ class FormsViewModel : BaseFormViewModel() { }) } - fun forms(): LiveData>> = formsLiveData + fun forms(): LiveData> = formsLiveData fun selectedForm(): LiveData = selectedFormLiveData fun selectedQuestion(): LiveData> = selectedQuestionLiveData fun navigateToNotes(): LiveData = navigateToNotesLiveData - fun pollingStation(): LiveData> = pollingStationLiveData + fun pollingStation(): LiveData = pollingStationLiveData private fun getPollingStationBarText() { disposables.add( @@ -87,7 +85,7 @@ class FormsViewModel : BaseFormViewModel() { subscribe() } .subscribe({ - pollingStationLiveData.postValue(Result.Success(it)) + pollingStationLiveData.postValue(it) }, { onError(it) }) @@ -100,13 +98,9 @@ class FormsViewModel : BaseFormViewModel() { repository.getForms() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - logD(TAG, "retrieving polling bar text.") - }, - { - formsLiveData.postValue(Result.Error(it)) - }) + .subscribe({}, { + onError(it) + }) ) } @@ -122,9 +116,8 @@ class FormsViewModel : BaseFormViewModel() { } catch (e: Exception) { false } - var formsList = when { - !filterDiasporaForms || pollingStationLiveData.value?.data?.isDiaspora == true -> forms.map { + !filterDiasporaForms || pollingStationLiveData.value?.isDiaspora == true -> forms.map { FormListItem(it) } else -> forms.filter { it.form.diaspora == false }.map { FormListItem(it) } @@ -132,7 +125,7 @@ class FormsViewModel : BaseFormViewModel() { formsList = formsList.sortedBy { it.formWithSections.form.order } items.addAll(formsList) items.add(AddNoteListItem()) - formsLiveData.postValue(Result.Success(items)) + formsLiveData.postValue(items) } fun selectForm(formDetails: FormDetails) { @@ -157,11 +150,4 @@ class FormsViewModel : BaseFormViewModel() { preferences.completedPollingStationConfig(false) } - override fun onError(throwable: Throwable) { - logE(TAG, "exception." + throwable.message) - } - - companion object { - const val TAG = "FormsViewModel" - } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt index 2d931ee6..21ea2dfd 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt @@ -16,12 +16,12 @@ import ro.code4.monitorizarevot.ui.base.BaseFormViewModel abstract class BaseQuestionViewModel : BaseFormViewModel() { val questionsLiveData = MutableLiveData>() var selectedFormId: Int = -1 - fun questions(): LiveData> = questionsLiveData val syncLiveData = MutableLiveData>() - + fun questions(): LiveData> = questionsLiveData fun syncData(): LiveData> = syncLiveData + private fun getQuestions(formId: Int) { selectedFormId = formId disposables.add(Observable.combineLatest( diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt index 42a5429f..5671256d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt @@ -15,6 +15,7 @@ import ro.code4.monitorizarevot.helper.Constants.TYPE_SINGLE_CHOICE import ro.code4.monitorizarevot.helper.Constants.TYPE_SINGLE_CHOICE_DETAILS import ro.code4.monitorizarevot.helper.Result import ro.code4.monitorizarevot.helper.logD +import ro.code4.monitorizarevot.helper.logE class QuestionsDetailsViewModel : BaseQuestionViewModel() { @@ -86,17 +87,19 @@ class QuestionsDetailsViewModel : BaseQuestionViewModel() { } fun syncAnswersData() { - val disposable = repository.syncAnswers(countyCode, pollingStationNumber, selectedFormId) + // todo a better solution is required for disposing this. + // if added to the CompositeDisposable it will be disposed before finishing. + repository.syncAnswers(countyCode, pollingStationNumber, selectedFormId) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { - logD(TAG, "response:$it") + logD("response success:$it", TAG) syncLiveData.postValue(Result.Success(it)) }, { + logE("response error:$it", TAG) syncLiveData.postValue(Result.Error(it)) } ) - disposables.add(disposable) } companion object { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt index 18095cdf..15ed2421 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt @@ -3,8 +3,6 @@ package ro.code4.monitorizarevot.ui.guide import android.annotation.SuppressLint import android.graphics.Bitmap import android.os.Bundle -import android.text.TextUtils -import android.util.Log import android.view.View import android.webkit.WebChromeClient import android.webkit.WebResourceRequest @@ -14,14 +12,10 @@ import androidx.lifecycle.Observer import kotlinx.android.synthetic.main.fragment_guide.* import org.koin.android.ext.android.inject import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.helper.createAndShowDialog -import ro.code4.monitorizarevot.ui.base.BaseViewModelFragment -import ro.code4.monitorizarevot.exceptions.WebViewException -import ro.code4.monitorizarevot.helper.logE +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class GuideFragment : BaseViewModelFragment() { - +class GuideFragment : ViewModelFragment() { override val layout: Int get() = R.layout.fragment_guide override val screenName: Int @@ -64,21 +58,6 @@ class GuideFragment : BaseViewModelFragment() { ): Boolean { return request.url?.let { !it.path.equals(viewModel.url().value) } ?: true } - - override fun onReceivedError( - view: WebView?, - errorCode: Int, - description: String?, - failingUrl: String? - ) { - val message = description.takeUnless { it.isNullOrEmpty() } ?: "Unknown Error" - onError( - WebViewException( - message!!, - errorCode - ) - ) - } } } viewModel.url().observe(viewLifecycleOwner, Observer { @@ -86,24 +65,10 @@ class GuideFragment : BaseViewModelFragment() { }) } - override fun onError(thr: Throwable) { - logE(TAG, "Error loading the page:" + thr.message) - val messageId = - if (thr is WebViewException) R.string.error_no_connection else R.string.error_generic_message - - progressDialog.dismiss() - logE(TAG, getString(messageId)) - //todo add action - } - override fun onDestroyView() { if (progressDialog.isResumed) { progressDialog.dismissAllowingStateLoss() } super.onDestroyView() } - - companion object { - const val TAG = "GuideFragment" - } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt index c4434c04..46ef187f 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt @@ -10,14 +10,12 @@ import kotlinx.android.synthetic.main.fragment_polling_station_selection.* import org.koin.android.viewmodel.ext.android.getSharedViewModel import org.koin.android.viewmodel.ext.android.viewModel import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment -import ro.code4.monitorizarevot.ui.base.BaseViewModelFragment import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.section.PollingStationViewModel import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class PollingStationSelectionFragment : BaseViewModelFragment() { +class PollingStationSelectionFragment : ViewModelFragment() { private val progressDialog: ProgressDialogFragment by lazy { ProgressDialogFragment().also { @@ -57,15 +55,15 @@ class PollingStationSelectionFragment : BaseViewModelFragment - result.handle( + viewModel.counties().observe(viewLifecycleOwner, Observer { + it.handle( onSuccess = { counties -> progressDialog.dismiss() counties?.run(::setCountiesDropdown) }, onFailure = { progressDialog.dismiss() - onError(it) + // TODO: Show some message for the user know what happened }, onLoading = { activity?.run { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt index 2bca6054..670de1e4 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt @@ -35,20 +35,17 @@ class PollingStationSelectionViewModel : BaseViewModel() { return } - disposables += repository.getCounties() - .subscribeOn(Schedulers.io()) + disposables += repository.getCounties().subscribeOn(Schedulers.io()) .doOnSuccess { counties.clear() counties.addAll(it) } .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - updateCounties() - }, - { - onError(it) - }) + .subscribe({ + updateCounties() + }, { + onError(it) + }) } private fun updateCounties() { @@ -74,7 +71,9 @@ class PollingStationSelectionViewModel : BaseViewModel() { } override fun onError(throwable: Throwable) { - logE("onError ${throwable.message}", throwable) + // TODO: Handle errors to show a specific message for each one countiesLiveData.postValue(Result.Error(throwable)) } + + } \ No newline at end of file From 39b0a96b358b34307031bde9674d341e5320061b Mon Sep 17 00:00:00 2001 From: Andrei Toader Date: Sun, 8 Nov 2020 13:58:56 +0100 Subject: [PATCH 5/5] bugfix/error_signing_redirect: Removed unused classes * Removed unused classes and dead code. --- .../exceptions/EmptyResultsException.kt | 5 ----- .../monitorizarevot/exceptions/WebViewException.kt | 8 -------- .../code4/monitorizarevot/repositories/Repository.kt | 2 -- .../monitorizarevot/ui/base/BaseViewModelFragment.kt | 11 ----------- .../ui/forms/questions/QuestionsDetailsViewModel.kt | 2 ++ 5 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt delete mode 100644 app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt deleted file mode 100644 index 58fa3db0..00000000 --- a/app/src/main/java/ro/code4/monitorizarevot/exceptions/EmptyResultsException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ro.code4.monitorizarevot.exceptions - -import java.lang.Exception - -class EmptyResultsException(message: String, thr: Throwable? = null) : Exception(message, thr) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt b/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt deleted file mode 100644 index 5f6ba551..00000000 --- a/app/src/main/java/ro/code4/monitorizarevot/exceptions/WebViewException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ro.code4.monitorizarevot.exceptions - -import java.lang.Exception - -/** - * Exception thrown when there is an issue with webview, loading paage, etc. - */ -class WebViewException(override val message: String, val code: Int = -1) : Exception(message) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index e57e43df..b463ec5d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -8,7 +8,6 @@ import io.reactivex.Observable import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction -import io.reactivex.functions.Consumer import io.reactivex.schedulers.Schedulers import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody @@ -28,7 +27,6 @@ import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO import ro.code4.monitorizarevot.data.pojo.FormWithSections import ro.code4.monitorizarevot.data.pojo.PollingStationInfo import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions -import ro.code4.monitorizarevot.exceptions.EmptyResultsException import ro.code4.monitorizarevot.extensions.successOrThrow import ro.code4.monitorizarevot.helper.createMultipart import ro.code4.monitorizarevot.helper.logD diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt index c819f8cd..494099d6 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseViewModelFragment.kt @@ -1,10 +1,7 @@ package ro.code4.monitorizarevot.ui.base -import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.exceptions.EmptyResultsException import ro.code4.monitorizarevot.exceptions.ErrorCodes import ro.code4.monitorizarevot.exceptions.RetrofitException -import ro.code4.monitorizarevot.helper.createAndShowDialog import ro.code4.monitorizarevot.helper.logE import ro.code4.monitorizarevot.helper.startActivityWithoutTrace import ro.code4.monitorizarevot.ui.login.LoginActivity @@ -16,12 +13,6 @@ abstract class BaseViewModelFragment : ViewModelFragment< is RetrofitException -> { processRetrofitException(thr) } - is EmptyResultsException -> { - val messageId: String = getString(R.string.no_counties_found) - activity?.createAndShowDialog(messageId, { - logE(TAG, "action needed.") - }) - } } } @@ -43,12 +34,10 @@ abstract class BaseViewModelFragment : ViewModelFragment< RetrofitException.Kind.NETWORK -> { logE(TAG, "network error.") - //todo do something about it. } RetrofitException.Kind.UNEXPECTED -> { logE(TAG, "unexpected error.") - //todo do something about it. } } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt index 5671256d..942df25a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsViewModel.kt @@ -1,5 +1,6 @@ package ro.code4.monitorizarevot.ui.forms.questions +import android.annotation.SuppressLint import io.reactivex.android.schedulers.AndroidSchedulers import ro.code4.monitorizarevot.adapters.helper.ListItem import ro.code4.monitorizarevot.adapters.helper.MultiChoiceListItem @@ -86,6 +87,7 @@ class QuestionsDetailsViewModel : BaseQuestionViewModel() { } } + @SuppressLint("CheckResult") fun syncAnswersData() { // todo a better solution is required for disposing this. // if added to the CompositeDisposable it will be disposed before finishing.