Solucionado (ver solução)
Solucionado
(ver solução)
7
respostas

Coroutines memory leaks

Olá, professor!

Gostaria de tirar uma dúvida, vi seu video recentemente e estou seguindo como base. Criei um repository e fiz dessa forma:

class HomeRepository(private val client: ApiService) {

    private val scope = CoroutineScope(Dispatchers.IO)

    private suspend fun getList(page: Int) = client.getListHome(page)

    fun getListHot(page: Int): MutableLiveData<Resource<HomeResponse>> {
        val data: MutableLiveData<Resource<HomeResponse>> = MutableLiveData()

        scope.launch {
            val response: HomeResponse? = getList(page)
            try {
                withContext(Dispatchers.Main) {
                    response?.let {
                        data.value = Resource.Success(it)
                    }
                }
            } catch (e: HttpException) {
            } catch (e: Throwable) {
            }
        }
        return data
    }
}

Estou trabalhando de forma correta com o coroutines? E a dúvida é o seguinte, eu vi na internet que é preciso dar um cancel para evitar o memory leaks. Mas não sei como implementar do jeito que fiz.

7 respostas

Oi Charles, tudo bem?

Tem um detalhe importante na sua implementação inicial, observe que você não adiciona a chamada do client no try catch.

Esse tratamento também é importante, pois pode acontecer uma Exception que vai apresentar uma falha no App e isso precisa ser notificado caso ocorra.

Sobre a parte de cancelamento você pode usar a referência de Job(), basicamente você vincula essa referência ao escopo da Coroutine e tem total controle para cancelar quando desejar.

Seguindo o seu exemplo ficaria da seguinte maneira:

class HomeRepository(private val client: ApiService) {

    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    private suspend fun getList(page: Int) = client.getListHome(page)

    fun getListHot(page: Int): MutableLiveData<Resource<HomeResponse>> {
        val data: MutableLiveData<Resource<HomeResponse>> = MutableLiveData()

        scope.launch {
            val response: HomeResponse? = getList(page)
            try {
                withContext(Dispatchers.Main) {
                    response?.let {
                        data.value = Resource.Success(it)
                    }
                }
            } catch (e: HttpException) {
            } catch (e: Throwable) {
            }
        }

        job.cancel()
        return data
    }
}

No momento que o job chama o cancel() a Coroutine e todos os escopos internos dela são cancelados. Eu fiz mais um Alura+ sobre isso e mais alguns detalhes que vai ficar disponível logo mais :)

[]s

Obrigado, professor! Eu tinha lido sobre esse Job(), mas não entendi pra que serve. Poderia me explicar?

Obrigado, professor! Eu tinha lido sobre esse Job(), mas não entendi pra que serve. Poderia me explicar?

solução!

Oi Charles, tudo bem?

Basicamente a referência de Job permite monitorar e controlar os estados de execução de coroutines, como por exemplo, quando executamos a launch ela devolve uma referência de job, que permite iniciar um coroutine, verificar se está ativa, se foi completada, realizar tarefas como join que espera outras coroutines para prosseguir com outras instruções, cancelar a coroutine ou até buscar outras coroutines que foram executadas dentro de uma coroutine.

Ao atribuir o Job no escopo da Coroutine como demonstrei, somos capazes de realizar as rotinas que comentei acima tanto na primeira coroutine que for executada, como também em suas filhas.

[]s

Olá, Alex! Desculpe mais uma vez incomodar. Mas estou com um problema que não estou sabendo solucionar.

Meu ViewModel está se comunicando perfeitamente com minha view, mas estou tendo um problema com meu recyclerview, pesquisei e não encontrei nada relacionado ao problema, e pode ser que esteja errando em alguma coisa.

O problema é que quando eu giro a tela do meu recycler, ele soma a lista, duplicando os itens. E depois que eu adicionei uma paginação, após eu chegar na página 2 e viro a tela ele substitui os itens e sobe. Vc poderia me dar uma luz sobre isso?

Eu até tinha resolvido isso adicionado um: android:configChanges="orientation|keyboardHidden" no manifest. Mas isso não é uma solução boa.

interface ApiService {

    @GET("search/repositories?q=language:Java&sort=stars")
    suspend fun getListHome(@Query("page") page: Int): HomeResponse
}
class HomeRepository(private val client: ApiService) {

    suspend fun getList(page: Int) = client.getListHome(page)
}
class NewsViewModel(private val repository: HomeRepository): ViewModel(){

    val getList = MutableLiveData<Resource<HomeResponse>>()

    fun fetchList(page: Int = 1) {
        viewModelScope.launch {
            try {
                val response: HomeResponse? = repository.getList(page)
                response?.let {
                    getList.value = Resource.Success(it)
                }
            } catch (e: HttpException) {
            } catch (e: Throwable) {
            }
        }
    }

}
private val viewModel by viewModel<NewsViewModel>()

private val itemAdapter by lazy {
        ItemAdapter(itemList, this)
 }

private fun initViewModel() {
        viewModel.fetchList()
        viewModel.getList.observe(this, Observer<Resource<HomeResponse>> { response ->
            when (response) {
                is Resource.Start -> {
                }
                is Resource.Success -> {
                    populateList(response.data.items)
                    showSuccess()
                }
                is Resource.Error -> {
                }
            }
        })
    }
private fun populateList(itemList: List<HomeResponse.Item>) {
        this.itemList.addAll(itemList)
        itemAdapter.notifyItemChanged(itemList.size - 29, itemList.size)
    }

private fun iniUi() {
        with(recycler_home) {
            adapter = itemAdapter
            val linearLayoutManager = LinearLayoutManager(this@MainActivity)
            layoutManager = linearLayoutManager
            addOnScrollListener(object : PaginationScroll(linearLayoutManager) {
                override fun loadMoreItems() {
                    viewModel.fetchList()
                    progress_home.visibility = View.VISIBLE
                    releasedLoad = false
                }

                override fun isLoading(): Boolean {
                    return releasedLoad
                }

                override fun hideMoreItems() {
                }
            })
        }
    }

Oi Charles, tudo bem?

muito provavelmente o problema está no momento em que popula os dados do recyclerview, atualmente você está chamando esse código de popular componente do Android e em qual estado?

Essa busca é feita apenas via web api? Se sim, isso pode ser também um grande problema, para soluções como essas, ter as informações salvas no banco interno e usá-lo como single source of truth, costuma ser mais interessante, pois qualquer update o próprio banco te notifica e você consegue atualizar, dá até pra tentar usar o Paging como solução.

Se possível tenta me mandar esses detalhes e abra um tópico novo para que continuemos com essa discussão :)

Boa noite, professor! É por uma api sim. No caso eu estou chamando ele no onCreate. Pensei em colocar no banco, porém estou fazendo um caching direto com okHttp. Sobre usar o paging... Também pensei nisso, porém não estudei esse sample direito e o pouco que vi, achei complicado pra montar.