Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Kotlin Coroutines #270

Merged
merged 9 commits into from
Mar 24, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,14 +25,19 @@ import androidx.lifecycle.ViewModelProvider
import com.chuckerteam.chucker.R
import com.chuckerteam.chucker.databinding.ChuckerFragmentTransactionPayloadBinding
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
import kotlinx.android.synthetic.main.chucker_fragment_transaction_payload.*
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException

private const val GET_FILE_FOR_SAVING_REQUEST_CODE: Int = 43

internal class TransactionPayloadFragment :
Fragment(), SearchView.OnQueryTextListener {
Fragment(), SearchView.OnQueryTextListener {
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved

private lateinit var payloadBinding: ChuckerFragmentTransactionPayloadBinding

Expand All @@ -43,8 +47,6 @@ internal class TransactionPayloadFragment :
private var type: Int = 0

private lateinit var viewModel: TransactionViewModel
private var payloadLoaderTask: PayloadLoaderTask? = null
private var fileSaverTask: FileSaverTask? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -54,9 +56,9 @@ internal class TransactionPayloadFragment :
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
): View? {
payloadBinding = ChuckerFragmentTransactionPayloadBinding.inflate(inflater, container, false)
return payloadBinding.root
Expand All @@ -65,20 +67,19 @@ internal class TransactionPayloadFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.transaction.observe(
viewLifecycleOwner,
Observer { transaction ->
if (transaction == null) return@Observer
PayloadLoaderTask(this).execute(Pair(type, transaction))
}
viewLifecycleOwner,
Observer { transaction ->
if (transaction == null) return@Observer
CoroutineScope(Dispatchers.Main).launch {
showProgress()
val result = processPayload(type, transaction)
responseRecyclerView.adapter = TransactionBodyAdapter(result)
hideProgress()
}
}
)
}

override fun onDestroyView() {
super.onDestroyView()
payloadLoaderTask?.cancel(true)
fileSaverTask?.cancel(true)
}

CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
@SuppressLint("NewApi")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val transaction = viewModel.transaction.value
Expand Down Expand Up @@ -143,8 +144,8 @@ internal class TransactionPayloadFragment :
startActivityForResult(intent, GET_FILE_FOR_SAVING_REQUEST_CODE)
} else {
Toast.makeText(
requireContext(), R.string.chucker_save_failed_to_open_document,
Toast.LENGTH_SHORT
requireContext(), R.string.chucker_save_failed_to_open_document,
Toast.LENGTH_SHORT
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
).show()
}
}
Expand All @@ -155,8 +156,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))
CoroutineScope(Dispatchers.Main).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()
}
}
}
Expand All @@ -174,22 +181,24 @@ 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<Pair<Int, HttpTransaction>, Unit, List<TransactionPayloadItem>>() {

override fun onPreExecute() {
fragment.payloadBinding.apply {
loadingProgress.visibility = View.VISIBLE
responseRecyclerView.visibility = View.INVISIBLE
}
private fun showProgress() {
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<Int, HttpTransaction>): List<TransactionPayloadItem> {
val (type, transaction) = params[0]
private suspend fun processPayload(type: Int, transaction: HttpTransaction): MutableList<TransactionPayloadItem> {
return withContext(Dispatchers.Default) {
val result = mutableListOf<TransactionPayloadItem>()

val headersString: String
Expand All @@ -208,9 +217,9 @@ internal class TransactionPayloadFragment :

if (headersString.isNotBlank()) {
result.add(
TransactionPayloadItem.HeaderItem(
HtmlCompat.fromHtml(headersString, HtmlCompat.FROM_HTML_MODE_LEGACY)
)
TransactionPayloadItem.HeaderItem(
HtmlCompat.fromHtml(headersString, HtmlCompat.FROM_HTML_MODE_LEGACY)
)
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
)
}

Expand All @@ -219,46 +228,33 @@ 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 {
context?.getString(R.string.chucker_body_omitted)?.let {
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(it)))
}
} else {
bodyString.lines().forEach {
result.add(TransactionPayloadItem.BodyLineItem(SpannableStringBuilder.valueOf(it)))
}
}

return result
}

override fun onPostExecute(result: List<TransactionPayloadItem>) {
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<Triple<Int, Uri, HttpTransaction>, Unit, Boolean>() {

@Suppress("NestedBlockDepth")
override fun doInBackground(vararg params: Triple<Int, Uri, HttpTransaction>): Boolean {
val (type, uri, transaction) = params[0]
private suspend fun saveToFile(type: Int, uri: Uri, transaction: HttpTransaction): Boolean {
return withContext(Dispatchers.IO) {
try {
val context = fragment.context ?: return false
val context = context ?: return@withContext false
CuriousNikhil marked this conversation as resolved.
Show resolved Hide resolved
context.contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { fos ->
when (type) {
TYPE_REQUEST -> {
transaction.requestBody?.byteInputStream()?.copyTo(fos)
?: throw IOException(TRANSACTION_EXCEPTION)
?: throw IOException(TRANSACTION_EXCEPTION)
}
TYPE_RESPONSE -> {
transaction.responseBody?.byteInputStream()?.copyTo(fos)
?: throw IOException(TRANSACTION_EXCEPTION)
?: throw IOException(TRANSACTION_EXCEPTION)
}
else -> {
if (transaction.responseImageData != null) {
Expand All @@ -272,22 +268,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
}
}

Expand All @@ -303,10 +289,10 @@ internal class TransactionPayloadFragment :
const val DEFAULT_FILE_PREFIX = "chucker-export-"

fun newInstance(type: Int): TransactionPayloadFragment =
TransactionPayloadFragment().apply {
arguments = Bundle().apply {
putInt(ARG_TYPE, type)
TransactionPayloadFragment().apply {
arguments = Bundle().apply {
putInt(ARG_TYPE, type)
}
}
}
}
}