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

Firebase com internet lenta

Caro professores, preciso de sua ajuda de vocês pois sou inexperiente e aqui é um lughar que tenho para solicitações. Estou construindo um app de compromisso entre profissional e usuario. Tudo corria bem com o filtro de conexão de internet. Quando esta caia não mudava na interface (profissional ativo ou não para ser chamado). Porém, após meus créditos na Claro acabarem a interface continua ativa, pois tem internet mas não comunica com o banco de dados. Dai tive que reformular o app para internet lentar ou sem internet e com conexão. Pasei a fazer um teste de internet no banco de dados para processar a ação de mudança no banco. Passei a determinar um tempo máximo de resposta (handler.postDelay) para cancelar a mudança. Corria tudo bem, mas quando a internet caia e voltava o chamado ao banco (via Firebase) era reativado após 15 a 20 s de internet normal (efeito retardado), porém já havia cancelado. Tentei várias maneiras de cancelar este retorno de chamada. Tentei este comando - scoresRef.keepSynced(false) - https://firebase.google.com/docs/database/android/offline-capabilities?hl=pt-br - e sem sucesso. Até que coloquei dentro de uma classe AsynkTask e cancelo a tarefa no tempo determinado. Mesmo assim a chamada retorna e preciso trabalhar o retorno dentro da Asynk para não completar e ocorrer a mudança no banco de forma retardada. Ou seja, a interface mudava após 15-20s de ter cancelado a chamada após a internet normalizar. Se me fiz entender, existe uma maneira mas fácil e eficiente de cancelar este retorno?

Obs.: passei 05hs para fazer isto. Por falta de experiência preciso de sua ajuda.

21 respostas

Dá algum erro quando você tenta usar o scoresRef.keepSynced(false) ? Ou nem vale a pena tentarmos ir por este caminho?

Bom dia. Grato pela atenção. Na verdade, acredito que. o uso de scoresRef.keepSynced(false) não interfere no resultado. O que percebi que após cancelar já tem passado pelo método que solicita algo no banco do Firebase. E que, após a internet cair e voltar ele devolve o GET (assim entendo) de maneira retardada. Mas utilizo o método de cancelar a AsynTask antes da conexão cair. Parece um pouco complicado, mas tenho que trabalhar este retorno para ele não mudar o banco de dados de forma retardada. Terias alguma ideia de como deixar este processo ok?

Estou sem ideias, preciso estudar mais sobre esse assunto. Algum instrutor poderia nos ajudar a chegar nessa solução?

Oi André. Situação inesperada encontrei no app. Gostaria de dar segurança ao usuário quando o profissional registra que esta disponível para ser chamado. Porém quando o profissional registra no app que esta disponível não posso retornar ativo até ter segurança que houve mudança no banco de dados refletido no usuário. Com a internet funcionando bem, não há problemas, mas quando esta muito lenta ou ausente preciso cancelar manualmente em um determinado tempo. Imagine voce usando 3G e andando de carro, aciona o botão para ativar a internet fica lenta e cai. Voce continua inativo, mas quando a internet volta o método é acionado de forma retardada e você fica ativo. Gostaria que o pessoal experiente me desse uma dica nesta situação. Não sei se acontece somente no firebase que estou usando ou em outros webServices também. No aguardo e grato pela atenção.

Até consegui solucionar isto no app, mas escolhi um caminho bem complicado com 2-3 booleans para impedir o retorno e colocar o método numa Asynk. Porém firebase já trabalha em segundo plano

A questão é cancelar qualquer retorno quando a internet fica off e torna a ficar on. Acredito que é como fazer um GET ou POST a internet cai e volta e após 15-20 s o GET ou POST aparece. Mas minha decisão é cancelar com antecedência pois o profissional não pode ser surpreendido que esta ativo. Outra opção é deixar uma mensagem de aguardando até conexão voltar, mas não é o ideal, pois o mesmo deve ficar atento que, a partir dali pode ser chamado pelo usuário.

Oi Antonio, tudo bem?

Realmente o problema que você está encarando está relacionado à sincronização. No geral, sincronização se faz com parâmetros boolean que servem como sinalizadores para poder permitir ou não uma ação, como por exemplo, um parâmetro que indica que a ativação foi feita, logo, ninguém mais pode pedir a ativação e coisas do gênero.

Não existe uma solução que serve para todos os casos, pois cada caso é uma regra de negócio diferente, ou seja, primeiro precisa identificar todos os passos realizados e suas possibilidades para depois entender quando sinalizadores são necessários.

Como você comentou, pode ser que a quantidade que você usa pareça bastante, mas dependendo de todo o fluxo, seja necessário. Então comece com esse exercício de mapeamento e identificação dos pontos de sincronia, acredito que esse seja o primeiro passo para identificar o que faz ou não sentido ser mantido.

Se preferir, pode compartilhar a sua divisão junto com o código de implementação, dessa forma eu entendo o seu raciocínio e vejo o que foi refletido via código. Pode ser no modo pseudo código se preferir :)

[]s

Oi Alex. Adorando suas aulas no geral. Grato pela atenção. Mas tarde em casa vou postar algo do código para você olhar e vê como pode me ajudar. Na verdade é mais para aprendizagem, pois consegui implementar mas foi dispendioso. Como não sou da área de TI mas da saúde utilizo da alura para aprender e montar boa programação.

Fico muito contente com o seu feedback, Antonio!

É muito gratificante para nós, instrutores, quando sabemos a diferença que o conteúdo faz para os alunos ;)

Entendo perfeitamente o seu ponto de vista, o que você quer é aplicar uma refatoração para melhorar a qualidade do código. Sendo assim, me manda o código com os detalhes que assim que eu analisar eu te mando um feedback.

[]s

Boa noite Alex. estou com dificuldade de compartilhar o código. Resumindo. Tem um Switch que o profissional aciona para fica disponível para o chamado pelo usuário. Este Switch também é modificado pelos dados banco no onCreate. Acho bem complicado mandar pedaços do código. Posso te mandar a Classe inteira por e-mail? O ponto crítico é que não posso deixar o profissional esperando uma internet lenta pra saber que esta ativo para receber chamada. Preciso da uma reposta momentânea. Caso o profissional acione o switch para fica ativo e a internet cai (desligo wi-fi) antes da resposta do servidor firebase acontece um problema. Tento simular uma internet ruim 3G que perde sinal. Após religá-la, com atraso de 15-20s o método de tornar o profissional ativo é acionado, mudando a interface do profissional de repente. Dai ficou um código bem enrolado para impedir este retorno do servidor firebase. Se puder da uma olhada. De qualquer forma já fico grato. Consigo prosseguir no app que estou criando, mas achei meio compicado para meu nível, então ter um olhar de alguem experiente seria bom para minha aprendizagem.

Consegue me mandar o código via GitHub ou Gist? É bem mais fácil de olhar e passar um feedback também.

Oi Alex. Ainda não sei usar GitHub para postar, mas vou estudar e conseguindo te mando uma resposta.

Já consegui colocar a classe no GitHub. Da minha maneira. Desculpe a desorganização no código.

https://github.com/MarceloRab/saudeLab/blob/master/ProfissionalActivity

Antes do profissional ficar ativo deve digitar código de segurança. Decidi por fazer um teste de internet antes de mudar o banco.

Minha primeira vez que exponho meus códigos, pois este é meu primeiro app que estou desenvolvendo. Então já fico grato.

Oi Antonio, tudo bem?

Eu vi o seu código e num primeiro momento é bem difícil de compreender o que ele faz exatamente, pois está com muito código de implementação em uma classe só.

Sendo assim, o primeiro passo que eu faria seria aplicar a extração de classes e métodos, dessa forma, é possível identificar com mais clareza o que cada bloco de código faz e se faz sentido mantê-lo dentro da activity ou em outra classe.

Assim que fizer isso, me manda que vejo o resultado.

[]s

Também achei isso. Como sou iniciante no prática de programação fui fazendo as activities sem refatorar para ver minha ideia em ação e depois ir refatorando e organizando. Assisti sua aula do curso de formulário (Curso Android Brasil: Validações e formatações) e achei muito legal como refatora para usar a interface no final para cadastrar. Vou tentar refatora melhor e volto a postar então. Obrigado pela atenção.

Exato, essa vai ser a etapa na qual você vai conseguir identificar o que faz sentido ou não no código. Em toda feature que implemento faço o código que chamo de 'quebra galho' que é similar ao que você fez, o que funciona. Então começo a aplicar a refatoração.

De maneira geral é bem próximo do que faço em curso mesmo, focando primeiro em entregar a feature e depois entender pontos de melhoria :)

Esse é o comportamento mais comum para qualquer programador hehe

Oi Andre. Apenas dando um retorno. Consegui implementar de uma forma mais simples usando um tempo máximo de resposta (sugestão de um professor de outro curso). Faço um System.currentTimeMillis() antes de entrar no método e quando recebe a resposta. Assim limito as resposta retardatárias.

São várias as possibilidades hehe

Mas acho que deve ter alguma forma mais elegante de limitar tempo do que o System.currentTimeMillis(), talvez até mesmo o postAtTime() do Handler pode ajudar e dá mais significado. Dá uma olhada depois.

Pronto professor. Assim ficou minha classe de teste de conexão.

public class TempoInternetLimita {

    private DatabaseReference internetRef;
    private Runnable runnable;
    private long tempoCallInicial;
    private Handler handler;
    private String sucesso = "";
    private final long tempoQuinze = 15000;
    private final long tempoDez = 10000;
    //private MySink mySinkQuize;

    private RespostaTempoInternet respostaTempoInternet;

    public interface RespostaTempoInternet {

        void respostaSucessoInternet();
        void respostaSemSucessoInternet();
    }



    public TempoInternetLimita( RespostaTempoInternet respostaTempoInternet) {
        internetRef = ConfiguracaoFirebase.getFirebaseDatabase().child("testeInternet");
        handler = new Handler();
        //mySinkQuize = new MySink();
        this.respostaTempoInternet = respostaTempoInternet;


    }

    private void limitaTempoPostQuinze(final Long tempoCall) {

        runnable = new Runnable() {
            @Override
            public void run() {
                if (sucesso.equals("")) {
                    respostaTempoInternet.respostaSemSucessoInternet();
                   // mySinkQuize.cancel(true);
                }
            }
        };

        handler.postDelayed(runnable, tempoCall);
    }

    public void iniciaTempoConexaoQuinze(){

        limitaTempoPostQuinze(tempoQuinze);
        limitaTempoInternetFire(tempoQuinze);
        //mySinkQuize.execute(tempoQuinze);

    }

    public void iniciaTempoConexaoDez(){

        limitaTempoPostQuinze(tempoDez);
        limitaTempoInternetFire(tempoDez);
        //mySinkQuize.execute(tempoDez);

    }

    public void limitaTempoInternetFire(final long tempoEspera){

        tempoCallInicial = System.currentTimeMillis();

        internetRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                String testeChildre = dataSnapshot.getKey();

                if (!testeChildre.isEmpty()) {
                    long tempoRespostaCall = System.currentTimeMillis() - tempoCallInicial;

                    if (tempoRespostaCall < tempoEspera) {
                        setSucesso("ok");
                        handler.removeCallbacks(runnable);
                        respostaTempoInternet.respostaSucessoInternet();

                    }

                }

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });


    }

    private String getSucesso() {
        return sucesso;
    }

    private void setSucesso(String sucesso) {
        this.sucesso = sucesso;
    }


}

Oi Antonio, já ficou bem melhor, ein! Eu fiz uma revisão rápida apenas no código mesmo e não na regra de negócio e deixei as observações em comentários do código, confira:

// considerando que é uma classe controlada pelo Android, é possivel tentar refatorar de tal forma que mantém os atributos final, dessa forma evita a falta de inicialização e também modificações não esperadas, ou melhor, o uso de variável locais.
public class TempoInternetLimita {

    private DatabaseReference internetRef;
    // não precisa ser um atributo, dado que só usa apenas em um método. Evite ao máximo uso de atributos caso apenas um membro da classe faz uso da variável.
    private Runnable runnable;
    private long tempoCallInicial;
    private Handler handler;
    private String sucesso = "";
    // tente manter o padrão de constantes, colocando tudo em letra maiúscula.
    private final long tempoQuinze = 15000;
    private final long tempoDez = 10000;
    // evite também comentários em código, caso tiver algo que não precisa simplesmente remova :)
    //private MySink mySinkQuize;

    private RespostaTempoInternet respostaTempoInternet;

    public interface RespostaTempoInternet {

        // dado que o nome da interface leva o nome RespostaTempoInternet, acredito que apenas sucesso() e falha()/semSucesso() seria o suficiente :)
        void respostaSucessoInternet();
        void respostaSemSucessoInternet();
    }



    public TempoInternetLimita( RespostaTempoInternet respostaTempoInternet) {
        internetRef = ConfiguracaoFirebase.getFirebaseDatabase().child("testeInternet");
        handler = new Handler();
        //mySinkQuize = new MySink();
        this.respostaTempoInternet = respostaTempoInternet;


    }

    private void limitaTempoPostQuinze(final Long tempoCall) {

        runnable = new Runnable() {
            @Override
            public void run() {
                if (sucesso.equals("")) {
                    respostaTempoInternet.respostaSemSucessoInternet();
                   // mySinkQuize.cancel(true);
                }
            }
        };

        handler.postDelayed(runnable, tempoCall);
    }

    // o nome ficou muito atrelado a 15, dado que recebe uma variável como tempo, tem duas alternativas, use um nome genérico para que não fique vinculado ao número 15, ou use apenas valores internos referenciando o 15 que não seja possível usar diferente, pois dessa forma, ele executa o que o nome propõe executar
    public void iniciaTempoConexaoQuinze(){

        limitaTempoPostQuinze(tempoQuinze);
        limitaTempoInternetFire(tempoQuinze);
        //mySinkQuize.execute(tempoQuinze);

    }

    public void iniciaTempoConexaoDez(){

        limitaTempoPostQuinze(tempoDez);
        limitaTempoInternetFire(tempoDez);
        //mySinkQuize.execute(tempoDez);

    }

    public void limitaTempoInternetFire(final long tempoEspera){

        // dá pra extrair pra mais métodos aqui também, veja que tem uma certa complexidade em todo o bloco de código que precisa interpretar pra entender.
        tempoCallInicial = System.currentTimeMillis();

        internetRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                String testeChildre = dataSnapshot.getKey();

                if (!testeChildre.isEmpty()) {
                    long tempoRespostaCall = System.currentTimeMillis() - tempoCallInicial;

                    if (tempoRespostaCall < tempoEspera) {
                        // vale a pena deixar a string como constante
                        setSucesso("ok");
                        handler.removeCallbacks(runnable);
                        respostaTempoInternet.respostaSucessoInternet();

                    }

                }

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });


    }

    private String getSucesso() {
        return sucesso;
    }

    private void setSucesso(String sucesso) {
        this.sucesso = sucesso;
    }


}

Muito obrigado Alex pelo comentários. Acrescentou mesmo! Ainda com erros simples. Considere o cansaço das tentativas. Caso queira finalizar... Desde já agradeço.

solução!

Opa Antonio, fico contente que tenha o ajudado! :)

Se não tiver mais dúvidas em relação ao tópico, pode marcar como solucionado por favor?