diff --git a/build.gradle b/build.gradle index 631eb0bb4..5d55ee41d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,13 @@ buildscript { ext { kotlinVersion = '1.3.70' androidGradleVersion = '3.6.0' + coroutineVersion = '1.3.4' // Google libraries appCompatVersion = '1.1.0' constraintLayoutVersion = '1.1.3' materialComponentsVersion = '1.1.0' - roomVersion = '2.2.3' + roomVersion = '2.2.5' lifecycleVersion = '2.2.0' androidXCoreVersion = '2.1.0' diff --git a/library/build.gradle b/library/build.gradle index 4515e5f8c..2911f7875 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -68,9 +68,13 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" + implementation "androidx.room:room-ktx:$roomVersion" implementation "androidx.room:room-runtime:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + implementation "com.google.code.gson:gson:$gsonVersion" implementation "com.squareup.okhttp3:okhttp:$okhttp3Version" diff --git a/library/src/main/java/com/chuckerteam/chucker/api/ChuckerCollector.kt b/library/src/main/java/com/chuckerteam/chucker/api/ChuckerCollector.kt index 0aa990212..1c304d63b 100644 --- a/library/src/main/java/com/chuckerteam/chucker/api/ChuckerCollector.kt +++ b/library/src/main/java/com/chuckerteam/chucker/api/ChuckerCollector.kt @@ -5,6 +5,9 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransaction import com.chuckerteam.chucker.internal.data.entity.RecordedThrowable import com.chuckerteam.chucker.internal.data.repository.RepositoryProvider import com.chuckerteam.chucker.internal.support.NotificationHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** * The collector responsible of collecting data from a [ChuckerInterceptor] and @@ -36,7 +39,9 @@ class ChuckerCollector @JvmOverloads constructor( */ fun onError(tag: String, throwable: Throwable) { val recordedThrowable = RecordedThrowable(tag, throwable) - RepositoryProvider.throwable().saveThrowable(recordedThrowable) + CoroutineScope(Dispatchers.IO).launch { + RepositoryProvider.throwable().saveThrowable(recordedThrowable) + } if (showNotification) { notificationHelper.show(recordedThrowable) } @@ -48,7 +53,9 @@ class ChuckerCollector @JvmOverloads constructor( * @param transaction The HTTP transaction sent */ internal fun onRequestSent(transaction: HttpTransaction) { - RepositoryProvider.transaction().insertTransaction(transaction) + CoroutineScope(Dispatchers.IO).launch { + RepositoryProvider.transaction().insertTransaction(transaction) + } if (showNotification) { notificationHelper.show(transaction) } diff --git a/library/src/main/java/com/chuckerteam/chucker/api/RetentionManager.kt b/library/src/main/java/com/chuckerteam/chucker/api/RetentionManager.kt index ef0436de8..c3e26503e 100644 --- a/library/src/main/java/com/chuckerteam/chucker/api/RetentionManager.kt +++ b/library/src/main/java/com/chuckerteam/chucker/api/RetentionManager.kt @@ -6,6 +6,9 @@ import android.util.Log import com.chuckerteam.chucker.api.Chucker.LOG_TAG import com.chuckerteam.chucker.internal.data.repository.RepositoryProvider import java.util.concurrent.TimeUnit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** * Class responsible of holding the logic for the retention of your HTTP transactions @@ -62,8 +65,10 @@ class RetentionManager @JvmOverloads constructor( } private fun deleteSince(threshold: Long) { - RepositoryProvider.transaction().deleteOldTransactions(threshold) - RepositoryProvider.throwable().deleteOldThrowables(threshold) + CoroutineScope(Dispatchers.IO).launch { + RepositoryProvider.transaction().deleteOldTransactions(threshold) + RepositoryProvider.throwable().deleteOldThrowables(threshold) + } } private fun isCleanupDue(now: Long) = now - getLastCleanup(now) > cleanupFrequency diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionDatabaseRepository.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionDatabaseRepository.kt index b9ab71d75..a6388bda6 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionDatabaseRepository.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionDatabaseRepository.kt @@ -5,13 +5,9 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransaction import com.chuckerteam.chucker.internal.data.entity.HttpTransactionTuple import com.chuckerteam.chucker.internal.data.room.ChuckerDatabase import com.chuckerteam.chucker.internal.support.distinctUntilChanged -import java.util.concurrent.Executor -import java.util.concurrent.Executors internal class HttpTransactionDatabaseRepository(private val database: ChuckerDatabase) : HttpTransactionRepository { - private val executor: Executor = Executors.newSingleThreadExecutor() - private val transactionDao get() = database.transactionDao() override fun getFilteredTransactionTuples(code: String, path: String): LiveData> { @@ -28,22 +24,20 @@ internal class HttpTransactionDatabaseRepository(private val database: ChuckerDa return transactionDao.getSortedTuples() } - override fun deleteAllTransactions() { - executor.execute { transactionDao.deleteAll() } + override suspend fun deleteAllTransactions() { + transactionDao.deleteAll() } - override fun insertTransaction(transaction: HttpTransaction) { - executor.execute { - val id = transactionDao.insert(transaction) - transaction.id = id ?: 0 - } + override suspend fun insertTransaction(transaction: HttpTransaction) { + val id = transactionDao.insert(transaction) + transaction.id = id ?: 0 } override fun updateTransaction(transaction: HttpTransaction): Int { return transactionDao.update(transaction) } - override fun deleteOldTransactions(threshold: Long) { - executor.execute { transactionDao.deleteBefore(threshold) } + override suspend fun deleteOldTransactions(threshold: Long) { + transactionDao.deleteBefore(threshold) } } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionRepository.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionRepository.kt index 86c73e539..69bd90fe1 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionRepository.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/HttpTransactionRepository.kt @@ -11,13 +11,13 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransactionTuple */ internal interface HttpTransactionRepository { - fun insertTransaction(transaction: HttpTransaction) + suspend fun insertTransaction(transaction: HttpTransaction) fun updateTransaction(transaction: HttpTransaction): Int - fun deleteOldTransactions(threshold: Long) + suspend fun deleteOldTransactions(threshold: Long) - fun deleteAllTransactions() + suspend fun deleteAllTransactions() fun getSortedTransactionTuples(): LiveData> diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableDatabaseRepository.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableDatabaseRepository.kt index bd628594d..7f8b5817a 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableDatabaseRepository.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableDatabaseRepository.kt @@ -5,32 +5,28 @@ import com.chuckerteam.chucker.internal.data.entity.RecordedThrowable import com.chuckerteam.chucker.internal.data.entity.RecordedThrowableTuple import com.chuckerteam.chucker.internal.data.room.ChuckerDatabase import com.chuckerteam.chucker.internal.support.distinctUntilChanged -import java.util.concurrent.Executor -import java.util.concurrent.Executors internal class RecordedThrowableDatabaseRepository( private val database: ChuckerDatabase ) : RecordedThrowableRepository { - private val executor: Executor = Executors.newSingleThreadExecutor() - override fun getRecordedThrowable(id: Long): LiveData { return database.throwableDao().getById(id).distinctUntilChanged() } - override fun deleteAllThrowables() { - executor.execute { database.throwableDao().deleteAll() } + override suspend fun deleteAllThrowables() { + database.throwableDao().deleteAll() } override fun getSortedThrowablesTuples(): LiveData> { return database.throwableDao().getTuples() } - override fun saveThrowable(throwable: RecordedThrowable) { - executor.execute { database.throwableDao().insert(throwable) } + override suspend fun saveThrowable(throwable: RecordedThrowable) { + database.throwableDao().insert(throwable) } - override fun deleteOldThrowables(threshold: Long) { - executor.execute { database.throwableDao().deleteBefore(threshold) } + override suspend fun deleteOldThrowables(threshold: Long) { + database.throwableDao().deleteBefore(threshold) } } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableRepository.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableRepository.kt index 3c71122bc..925c74586 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableRepository.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/repository/RecordedThrowableRepository.kt @@ -11,11 +11,11 @@ import com.chuckerteam.chucker.internal.data.entity.RecordedThrowableTuple */ internal interface RecordedThrowableRepository { - fun saveThrowable(throwable: RecordedThrowable) + suspend fun saveThrowable(throwable: RecordedThrowable) - fun deleteOldThrowables(threshold: Long) + suspend fun deleteOldThrowables(threshold: Long) - fun deleteAllThrowables() + suspend fun deleteAllThrowables() fun getSortedThrowablesTuples(): LiveData> diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/room/HttpTransactionDao.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/room/HttpTransactionDao.kt index eabb5bcab..87f739f99 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/room/HttpTransactionDao.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/room/HttpTransactionDao.kt @@ -28,17 +28,17 @@ internal interface HttpTransactionDao { fun getFilteredTuples(codeQuery: String, pathQuery: String): LiveData> @Insert - fun insert(transaction: HttpTransaction): Long? + suspend fun insert(transaction: HttpTransaction): Long? @Update(onConflict = OnConflictStrategy.REPLACE) fun update(transaction: HttpTransaction): Int @Query("DELETE FROM transactions") - fun deleteAll() + suspend fun deleteAll() @Query("SELECT * FROM transactions WHERE id = :id") fun getById(id: Long): LiveData @Query("DELETE FROM transactions WHERE requestDate <= :threshold") - fun deleteBefore(threshold: Long) + suspend fun deleteBefore(threshold: Long) } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/data/room/RecordedThrowableDao.kt b/library/src/main/java/com/chuckerteam/chucker/internal/data/room/RecordedThrowableDao.kt index f78f541fc..78fcac664 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/data/room/RecordedThrowableDao.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/data/room/RecordedThrowableDao.kt @@ -13,15 +13,15 @@ internal interface RecordedThrowableDao { @Query("SELECT id,tag,date,clazz,message FROM throwables ORDER BY date DESC") fun getTuples(): LiveData> - @Insert() - fun insert(throwable: RecordedThrowable): Long? + @Insert + suspend fun insert(throwable: RecordedThrowable): Long? @Query("DELETE FROM throwables") - fun deleteAll() + suspend fun deleteAll() @Query("SELECT * FROM throwables WHERE id = :id") fun getById(id: Long): LiveData @Query("DELETE FROM throwables WHERE date <= :threshold") - fun deleteBefore(threshold: Long) + suspend fun deleteBefore(threshold: Long) } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/support/ClearDatabaseService.kt b/library/src/main/java/com/chuckerteam/chucker/internal/support/ClearDatabaseService.kt index 9da1a71d5..117628f25 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/support/ClearDatabaseService.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/support/ClearDatabaseService.kt @@ -4,6 +4,9 @@ import android.app.IntentService import android.content.Intent import com.chuckerteam.chucker.internal.data.repository.RepositoryProvider import java.io.Serializable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch internal class ClearDatabaseService : IntentService(CLEAN_DATABASE_SERVICE_NAME) { @@ -11,13 +14,17 @@ internal class ClearDatabaseService : IntentService(CLEAN_DATABASE_SERVICE_NAME) when (intent?.getSerializableExtra(EXTRA_ITEM_TO_CLEAR)) { is ClearAction.Transaction -> { RepositoryProvider.initialize(applicationContext) - RepositoryProvider.transaction().deleteAllTransactions() + CoroutineScope(Dispatchers.IO).launch { + RepositoryProvider.transaction().deleteAllTransactions() + } NotificationHelper.clearBuffer() NotificationHelper(this).dismissTransactionsNotification() } is ClearAction.Error -> { RepositoryProvider.initialize(applicationContext) - RepositoryProvider.throwable().deleteAllThrowables() + CoroutineScope(Dispatchers.IO).launch { + RepositoryProvider.throwable().deleteAllThrowables() + } NotificationHelper(this).dismissErrorsNotification() } } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/ui/MainViewModel.kt b/library/src/main/java/com/chuckerteam/chucker/internal/ui/MainViewModel.kt index dfba55287..d58d7e4b2 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/ui/MainViewModel.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/ui/MainViewModel.kt @@ -5,10 +5,12 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.switchMap +import androidx.lifecycle.viewModelScope import com.chuckerteam.chucker.internal.data.entity.HttpTransactionTuple import com.chuckerteam.chucker.internal.data.entity.RecordedThrowableTuple import com.chuckerteam.chucker.internal.data.repository.RepositoryProvider import com.chuckerteam.chucker.internal.support.NotificationHelper +import kotlinx.coroutines.launch internal class MainViewModel : ViewModel() { @@ -38,11 +40,15 @@ internal class MainViewModel : ViewModel() { } fun clearTransactions() { - RepositoryProvider.transaction().deleteAllTransactions() + viewModelScope.launch { + RepositoryProvider.transaction().deleteAllTransactions() + } NotificationHelper.clearBuffer() } fun clearThrowables() { - RepositoryProvider.throwable().deleteAllThrowables() + viewModelScope.launch { + RepositoryProvider.throwable().deleteAllThrowables() + } } } diff --git a/library/src/main/java/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt b/library/src/main/java/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt index 7598caa75..fd714c1b2 100644 --- a/library/src/main/java/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt +++ b/library/src/main/java/com/chuckerteam/chucker/internal/ui/transaction/TransactionPayloadFragment.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.graphics.Color import android.net.Uri -import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.text.SpannableStringBuilder @@ -29,6 +28,11 @@ import com.chuckerteam.chucker.internal.data.entity.HttpTransaction import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext private const val GET_FILE_FOR_SAVING_REQUEST_CODE: Int = 43 @@ -43,8 +47,8 @@ internal class TransactionPayloadFragment : private var type: Int = 0 private lateinit var viewModel: TransactionViewModel - private var payloadLoaderTask: PayloadLoaderTask? = null - private var fileSaverTask: FileSaverTask? = null + + private val uiScope = MainScope() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -58,7 +62,10 @@ internal class TransactionPayloadFragment : container: ViewGroup?, savedInstanceState: Bundle? ): View? { - payloadBinding = ChuckerFragmentTransactionPayloadBinding.inflate(inflater, container, false) + payloadBinding = ChuckerFragmentTransactionPayloadBinding.inflate( + inflater, container, + false + ) return payloadBinding.root } @@ -68,15 +75,19 @@ internal class TransactionPayloadFragment : viewLifecycleOwner, Observer { transaction -> if (transaction == null) return@Observer - PayloadLoaderTask(this).execute(Pair(type, transaction)) + uiScope.launch { + showProgress() + val result = processPayload(type, transaction) + payloadBinding.responseRecyclerView.adapter = TransactionBodyAdapter(result) + hideProgress() + } } ) } - override fun onDestroyView() { - super.onDestroyView() - payloadLoaderTask?.cancel(true) - fileSaverTask?.cancel(true) + override fun onDestroy() { + super.onDestroy() + uiScope.cancel() } @SuppressLint("NewApi") @@ -155,8 +166,14 @@ internal class TransactionPayloadFragment : val uri = resultData?.data val transaction = viewModel.transaction.value if (uri != null && transaction != null) { - fileSaverTask = FileSaverTask(this).apply { - execute(Triple(type, uri, transaction)) + uiScope.launch { + val result = saveToFile(type, uri, transaction) + val toastMessageId = if (result) { + R.string.chucker_file_saved + } else { + R.string.chucker_file_not_saved + } + Toast.makeText(context, toastMessageId, Toast.LENGTH_SHORT).show() } } } @@ -174,22 +191,23 @@ internal class TransactionPayloadFragment : return true } - /** - * Async task responsible of loading in the background the content of the HTTP request/response. - */ - class PayloadLoaderTask(private val fragment: TransactionPayloadFragment) : - AsyncTask, Unit, List>() { + private fun showProgress() { + payloadBinding.apply { + loadingProgress.visibility = View.VISIBLE + responseRecyclerView.visibility = View.INVISIBLE + } + } - override fun onPreExecute() { - fragment.payloadBinding.apply { - loadingProgress.visibility = View.VISIBLE - responseRecyclerView.visibility = View.INVISIBLE - } + private fun hideProgress() { + payloadBinding.apply { + loadingProgress.visibility = View.INVISIBLE + responseRecyclerView.visibility = View.VISIBLE + requireActivity().invalidateOptionsMenu() } + } - @Suppress("ComplexMethod") - override fun doInBackground(vararg params: Pair): List { - val (type, transaction) = params[0] + private suspend fun processPayload(type: Int, transaction: HttpTransaction): MutableList { + return withContext(Dispatchers.Default) { val result = mutableListOf() val headersString: String @@ -209,7 +227,9 @@ internal class TransactionPayloadFragment : if (headersString.isNotBlank()) { result.add( TransactionPayloadItem.HeaderItem( - HtmlCompat.fromHtml(headersString, HtmlCompat.FROM_HTML_MODE_LEGACY) + HtmlCompat.fromHtml( + headersString, HtmlCompat.FROM_HTML_MODE_LEGACY + ) ) ) } @@ -219,7 +239,7 @@ internal class TransactionPayloadFragment : if (type == TYPE_RESPONSE && responseBitmap != null) { result.add(TransactionPayloadItem.ImageItem(responseBitmap)) } else if (!isBodyPlainText) { - fragment.context?.getString(R.string.chucker_body_omitted)?.let { + requireContext().getString(R.string.chucker_body_omitted)?.let { result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(it))) } } else { @@ -227,29 +247,15 @@ internal class TransactionPayloadFragment : result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(it))) } } - - return result - } - - override fun onPostExecute(result: List) { - fragment.payloadBinding.apply { - loadingProgress.visibility = View.INVISIBLE - responseRecyclerView.visibility = View.VISIBLE - responseRecyclerView.adapter = TransactionBodyAdapter(result) - } - fragment.requireActivity().invalidateOptionsMenu() + return@withContext result } } - class FileSaverTask(private val fragment: TransactionPayloadFragment) : - AsyncTask, Unit, Boolean>() { - - @Suppress("NestedBlockDepth") - override fun doInBackground(vararg params: Triple): Boolean { - val (type, uri, transaction) = params[0] + @Suppress("ThrowsCount") + private suspend fun saveToFile(type: Int, uri: Uri, transaction: HttpTransaction): Boolean { + return withContext(Dispatchers.IO) { try { - val context = fragment.context ?: return false - context.contentResolver.openFileDescriptor(uri, "w")?.use { + requireContext().contentResolver.openFileDescriptor(uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { fos -> when (type) { TYPE_REQUEST -> { @@ -272,22 +278,12 @@ internal class TransactionPayloadFragment : } } catch (e: FileNotFoundException) { e.printStackTrace() - return false + return@withContext false } catch (e: IOException) { e.printStackTrace() - return false - } - return true - } - - override fun onPostExecute(isSuccessful: Boolean) { - fragment.fileSaverTask = null - val toastMessageId = if (isSuccessful) { - R.string.chucker_file_saved - } else { - R.string.chucker_file_not_saved + return@withContext false } - Toast.makeText(fragment.context, toastMessageId, Toast.LENGTH_SHORT).show() + return@withContext true } }