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

[Bug] O objeto não é removido de um aparelho caso tenha sido deletado do outro

Bom dia a todos

O bug que eu encontrei se refere ao projeto do curso Android com Kotlin: comunicação com Web API

Eu fiz um teste para ver se o SwipeRefreshLayout estava funcionado. E nesse teste eu fiz em 2 aparelhos, percebi que o refresh estava funcionando normalmente, mas nisso eu percebi um bug no projeto.

Eu inseri um objeto no aparelho 1, atualizei o aparelho 2 e apareceu o objeto. Deletei o objeto do aparelho 1 de forma offline, atualizei o aparelho 1 online e o objeto foi deletado, mas ao atualizar o aparelho 2 ele não apagava o objeto.

Eu acredito que o objeto não foi deletado no aparelho 2 pois ele conta como se objeto no aparelho 2 não tivesse desativado, por isso o servidor não apaga, e nem o aparelho 2 manda o objeto ao servidor porque neste aparelho o objeto conta como se tivesse já sincronizado, então o objeto fica no limbo do aparelho 2, sem ser excluído, e sem ser mandado para o servidor.

Eu até posso colocar meu código aqui, mas acredito que não vai fazer muita diferença, porque em todos os testes ele estava funcionando normalmente:

class ListaNotasActivity : AppCompatActivity() {

    private val binding by lazy {
        ActivityListaNotasBinding.inflate(layoutInflater)
    }
    private val adapter by lazy {
        ListaNotasAdapter(this)
    }
    private val repository by lazy {
        NotaRepository(
            AppDatabase.instancia(this).notaDao(),
            NotaWebClient()
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        configuraFab()
        configuraRecyclerView()
        carregaEAtualizaNotas()
        configuraSwipeRefresh()
    }

    private fun configuraSwipeRefresh() {
        binding.activityListaNotasSwipe.apply {
            setOnRefreshListener {
                carregaEAtualizaNotas()
                lifecycleScope.launch {
                    repository.sincroniza()
                    isRefreshing = false
                }
            }
        }
    }

    private fun configuraFab() {
        binding.activityListaNotasFab.setOnClickListener {
            Intent(this, FormNotaActivity::class.java).apply {
                startActivity(this)
            }
        }
    }

    private fun configuraRecyclerView() {
        binding.activityListaNotasRecyclerview.adapter = adapter
        adapter.quandoClicaNoItem = { nota ->
            vaiPara(FormNotaActivity::class.java) {
                putExtra(NOTA_ID, nota.id)
            }
        }
    }

    private fun carregaEAtualizaNotas() {
        lifecycleScope.launch {
            launch {
                repository.sincroniza()
            }
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                buscaNotas()
            }
        }
    }

    private suspend fun buscaNotas() {
        repository.buscaTodes()
            .collect { notasEncontradas ->
                binding.activityListaNotasMensagemSemNotas.visibility =
                    if (notasEncontradas.isEmpty()) {
                        binding.activityListaNotasRecyclerview.visibility = GONE
                        VISIBLE
                    } else {
                        binding.activityListaNotasRecyclerview.visibility = VISIBLE
                        adapter.atualiza(notasEncontradas)
                        GONE
                    }
            }
    }
}
class NotaRepository(
    private val dao: NotaDao,
    private val webClient: NotaWebClient
) {
    fun buscaTodes(): Flow<List<Nota>>{
        return dao.buscaTodos()
    }
    fun buscaPorId(id: String): Flow<Nota> {
        return dao.buscaPorId(id)
    }

    suspend fun desativa(id: String) {
        dao.desativa(id)
        remove(id)
    }

    private suspend fun remove(id: String) {
        if (webClient.remove(id)) {
            dao.remove(id)
        }
    }

    suspend fun salva(nota: Nota) {
        if(webClient.salva(nota)){
            val notaSincronizada = nota.copy(sincronizada = true)
            dao.salva(notaSincronizada)
        } else{
            dao.salva(nota)
        }
    }

    suspend fun sincroniza(){
        val notasDesativadas = dao.buscaNotasDesativadas().first()
        notasDesativadas.forEach {
            remove(it.id)
        }
        val notasNaoSincronizadas = dao.buscaNaoSincronizadas().first()
        notasNaoSincronizadas.forEach {
            salva(it)
        }
        atualizaTodos()
    }
    private suspend fun atualizaTodos(){
        webClient.buscaTodos()?.let { notas ->
            val notasSincronizadas = notas.map {
                it.copy(sincronizada = true)
            }
            dao.salvaTodos(notasSincronizadas)
        }
    }
}
2 respostas
interface NotaDao {

    @Insert(onConflict = REPLACE)
    suspend fun salva(note: Nota)

    @Insert(onConflict = REPLACE)
    suspend fun salvaTodos(notas: List<Nota>)

    @Query("SELECT * FROM Nota WHERE removida = 0")
    fun buscaTodos() : Flow<List<Nota>>

    @Query("SELECT * FROM Nota WHERE id = :id AND removida = 0")
    fun buscaPorId(id: String): Flow<Nota>

    @Query("DELETE FROM Nota WHERE id = :id")
    suspend fun remove(id: String)

    @Query("SELECT * FROM Nota WHERE sincronizada = 0 AND removida = 0")
    fun buscaNaoSincronizadas(): Flow<List<Nota>>

    @Query("UPDATE Nota SET removida = 1 WHERE id = :id")
    suspend fun desativa(id: String)

    @Query("SELECT * FROM Nota WHERE removida = 1")
    fun buscaNotasDesativadas(): Flow<List<Nota>>
const val TAG = "NotaWebClient"
class NotaWebClient {
    private val notaService:NotaService = RetrofitInicializador().notaService
    suspend fun buscaTodes():List<Nota>? {
        return try {
            val notasResposta = notaService.buscaTodos()
            notasResposta.map { notaResposta ->
                notaResposta.nota
            }
        } catch (e: Exception){
            Log.e(TAG,"buscaTodos:",e)
            null
        }
    }

    suspend fun salva(nota: Nota):Boolean{
        try {
            val resposta = notaService.salva(
                nota.id, NotaView(
                    titulo = nota.titulo,
                    descricao = nota.descricao,
                    imagem = nota.imagem
                )
            )
            return resposta.isSuccessful
        } catch (e: Exception) {
        Log.e(TAG,"salva: falha ao tentar salvar",e)
        }
        return false
    }

    suspend fun remove(id: String): Boolean {
        try {
            val resposta = notaService.remove(id)
            return resposta.isSuccessful
        } catch (e: Exception) {
            Log.e(TAG,"remove: falha ao tentar remover",e)
        }
        return false
    }
}
interface NotaService {

    @GET("notas")
    suspend fun buscaTodos():List<NotaMapper>

    @PUT("notas/{id}")
    suspend fun salva(@Path("id") id:String,
              @Body nota: NotaView): Response<NotaMapper>

    @DELETE("notas/{id}")
    suspend fun remove(@Path("id") id: String): Response<Void>
}
solução!

Olá Murilo, tudo bem?

Pelo seu relato, parece que o objeto não está sendo deletado no aparelho 2 porque ele ainda está contando como ativo. Isso pode estar acontecendo porque o servidor não apaga o objeto, já que ele ainda está contando como ativo no aparelho 2, e o aparelho 2 não envia o objeto para o servidor, já que ele já conta como sincronizado.

Uma possível solução para esse problema seria verificar se o objeto está contando como ativo no aparelho 2 e, caso esteja, desativá-lo antes de tentar deletá-lo. Dessa forma, o objeto seria deletado tanto no servidor quanto no aparelho 2.

Talvez a lógica do servidor precise ser atualizada também, acredito que o problema seja mais com o servidor do que com app. A ideia é a gente sempre confiar nos dados que estamos recebendo.

Espero ter ajudado e bons estudos!