How to synchonize executing http requests (kotlin, android)?

139 views Asked by At

I am debugging an application that communicates with an IoT device via http. In response to commands, the device sends information in xml format. An application can also receive binary data on a GET request. In the functionality of the application, filling the RecyclerView from the list and loading images to fill the RecyclerView and executing individual commands to change modes. The problem is that the device does not have the most powerful processor, and when a large number of http commands are received, the service cannot cope and hangs for a long time until the WiFi channel fails. I can’t figure out how to organize interaction so that each next command waits for the previous one to complete. The solution is complicated by the fact that populating the RecyclerView, loading images, and executing commands are in different parts of the code, and each is executed asynchronously.

Populating RecyclerView:

private fun initViewModel(filter: String) {
    val st = Storage(requireContext())
    val cache = "${st.externalCacheDir}/$filter/"

    val viewModel = ViewModelProvider(this).get(DeviceListViewModel::class.java)
    viewModel.getRecycerListObserver().observe(requireActivity(), Observer<ResponseData> {
        if (it != null) {
            val media = it.mediaData?.filter { it.mediaData?.fPath!!.contains(filter, false) }
            mediaList = arrayListOf()
            if (media != null) {
                for (i in media.sortedByDescending { it.mediaData?.fTimeCode }) {
                    i.mediaData?.let { it1 -> mediaList.add(it1) }
                }
            }
            viewModel.recyclerListLiveData = MutableLiveData()
            ThumbDownloader(dataAdapter, mediaList, cache, swipeLayout).execute()
        } else {
            Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
        }
    })
    viewLifecycleOwner.lifecycleScope.launch {
        viewModel.makeApiCall()
    }
}

ViewModel:

class DeviceListViewModel : ViewModel() {
    var recyclerListLiveData: MutableLiveData<ResponseData>

    init {
        recyclerListLiveData = MutableLiveData()
    }

    fun getRecycerListObserver(): MutableLiveData<ResponseData> {
        return recyclerListLiveData
    }

    fun makeApiCall() {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val retroInstance =
                        RetroInstance.getRetroInstance(MainActivity.BaseUrl).create(RetroService::class.java)
                val response = retroInstance.getDataFromApi(1, Cmd.WIFIAPP_CMD_FILELIST)
                recyclerListLiveData.postValue(response)
            } catch (e: Exception) {
                var response: ResponseData? = null
                when (e) {
                    is ConnectException -> {
                        recyclerListLiveData.postValue(response)
                    }
                    is SocketTimeoutException -> {
                        recyclerListLiveData.postValue(response)
                    }
                }
            }
        }
    }
}

Service to make a command (processing results in the Handler):

class DeviceService {
    private val handler: Handler
    private var mJob: Job? = null

    constructor(handler: Handler) {
        this.handler = handler
    }

    fun sendCommand(cmd: Int) {
        val service = RetroInstance.buildService(MainActivity.BaseUrl, RetroService::class.java)
        mJob = CoroutineScope(Dispatchers.IO).launch {
            val response = when (cmd) {
                Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
                    try {
                        service.getLinkFromApi(1, cmd)
                    } catch (e: Exception) {
                        handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
                        mJob?.cancel()
                    }
                }
                else -> {
                    try {
                        service.makeCommand(1, cmd)
                    } catch (e: Exception) {
                        handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
                        mJob?.cancel()
                    }
                }
            }
            withContext(Dispatchers.Main) {
                try {
                    when (cmd) {
                        Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
                            handler.obtainMessage(Msg.MESSAGE_LINK_FORMAT, response).sendToTarget()
                        }
                        else -> {
                            handler.obtainMessage(Msg.MESSAGE_PAR_FUNCTION, response).sendToTarget()
                        }
                    }
                } catch (e: Exception) {
                    when (e) {
                        is ConnectException -> {
                            handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
                        }
                        is SocketTimeoutException -> {
                            handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
                        }
                    }
                }
                mJob?.cancelAndJoin()
            }
        }
    }
}

Downloading a images:

class ThumbDownloader(dataAdapter: DeviceAdapter, data: ArrayList<MediaData>, file_path: String, swipe: SwipeRefreshLayout) : CoroutineScope {
        private var job: Job = Job()
        private var file_path: String
        private var dataAdapter: DeviceAdapter
        private var data: ArrayList<MediaData>
        private var swipe: SwipeRefreshLayout
    
        init {
            this.data = data
            this.file_path = file_path
            this.dataAdapter = dataAdapter
            this.swipe = swipe
        }
    
        override val coroutineContext: CoroutineContext
            get() = Dispatchers.Main + job
    
        fun cancel() {
            job.cancel()
        }
    
        fun execute() = async {
            var item: File? = null
            for (i in data) {
                val task = async(Dispatchers.IO) {
                    val url = i.fPath!!
                    val real_url = "${MainActivity.BaseUrl}$url"
                    item = NetworkUtil.downloadFile(real_url, file_path, false)
                }
                task.await()
                if (item != null) {
                    dataAdapter.insertItem(i)
                }
            }
            cancel()
            swipe.isRefreshing = false
        }
    }

Any ideas how to come up with their synchronization while waiting for the previous commands to complete?

0

There are 0 answers