Volley, Gson e RetryPolicy em Material Design Android Série APP

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Volley, Gson e RetryPolicy em Material Design Android Série APP

Volley, Gson e RetryPolicy em Material Design Android Série APP

Vinícius Thiengo
(5219) (109) (1)
Go-ahead
"É uma estrada áspera que leva ao topo da excelência."
Lucius Annaeus Seneca
Kotlin Android
Capa do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia
TítuloDesenvolvedor Kotlin Android - Bibliotecas para o dia a dia
CategoriasAndroid, Kotlin
AutorVinícius Thiengo
Edição
Capítulos19
Páginas1035
Acessar Livro
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas936
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Nesse vídeo dou continuidade a APP da série Material Design no Android, do Blog, dessa vez implementando comunicação remota com um servidor Web no qual tem instalado a linguagem de backend PHP e o SGBD MySQL. Comunicação realizada com o suporte da lib Volley para transmissão e recebimento de ddos e da lib Gson para o parser de Json para Objeto Java e vice-versa.

Note que o objetivo do vídeo foi integrar o Volley a APP que está sendo construída na série, tendo em mente que já tem no Blog dois vídeos sobre a lib Volley, alguns outros vídeos utilizando a lib Volley, além de a documentação (apesar de estar em inglês) ser bem explicativa incluindo códigos de exemplos. Porém a integração apresentada no vídeo inclui conteúdos importantes como o RetryPolicy e o porquê de conteúdos duplicados na base de dados remota quando há um delay muito alto na resposta do servidor, além de utilizar o Volley com o padrão Singleton. Há também a utilização do JsonArrayRequest para receber e enviar dados para o Servidor Web (nesse caso é implementado um CustomRequest).

Na parte do RetryPolicy recomendo a alteração apenas do parametro do TimeOut, porém essa é apenas uma opinião minha baseada nas necessidades de projeto que já tive, você deverá alterar os parâmetros necessários de acordo com suas especificações de projetos. Note no vídeo que há uma problemática com a persistência dos fragments, até mesmo recomendo a utilização do SQLite para a possível solução. Com vídeos futuros vamos ver como corrigir isso. Ressalto que no vídeo abordo como carregar tanto dados novos como dados mais antigos no RecyclerView. No vídeo também é apresentada uma forma de realizar conexão "localhost" para utilizar uma base de dados em sua máquina ao invés de um servidor Web. Sem mais delongas vou deixar você assistir ao vídeo.

Segue link para download / fork do projeto no GitHub: https://github.com/viniciusthiengo/tc-material-design

Segue link da série Material Design no Android:

Série Material Design, YouTube channel

Links dos vídeos / post sobre o Volley Android recomendados pelo Blog:

Transmitting Network Data Using Volley

Volley no Android, Entendendo e Utilizando

Android working with Volley Library - AndroidHive

GitHub Volley-Demo (bom RetryPolicy content)

Stackoverflow com implementação de uma CustomRequest para utilizar JsonArrayRequest / JsonObjectRequest com envio de dados no Volley Android

Segue links das libs via Maven, Volley e Gson:

Android Volley GitHub lib

Maven Gson

Caso queira se aventurar no SQLite para já adiantar uma possível solução para o problema de persistência dos conteúdos dos fragments, veja o vídeo SQLite no Android, Entendendo e Utilizando disponível no Blog.

Vlw

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

AppWidget. Material Design Android - Parte 14AppWidget. Material Design Android - Parte 14Android
APP Invites Para Compartilhamento. Material Design Android - Parte 15APP Invites Para Compartilhamento. Material Design Android - Parte 15Android
Date e Time PickerDialog. Material Design Android - Parte 16Date e Time PickerDialog. Material Design Android - Parte 16Android
ContextMenu no RecyclerView. Material Design Android - Parte 17ContextMenu no RecyclerView. Material Design Android - Parte 17Android

Compartilhar

Comentários Facebook (9)

Comentários Blog (100)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
Robson (1) (0)
29/12/2017
Olá Thiengo! meu caro vim recorrer a vc porque posso estar diante da solução para o caso mais não estou percebendo. Então eu tenho uma atividade que acessa uma API , um dos recursos dela é obter os dados para a sincronização ( Servidor -> Dispositivo ) e estou usando o Volley para tal, a API me retorna um json que eu itero e atualizo o banco, tem por volta uns 12.000 registros e o processo todo dura cerca de 3 minutos. Tudo se inicia por ação do usuário que acessa a atividade e chama a API através de um botão, ai vem um progressDialog para que o usuário perceba que algo esta ocorrendo. O problema é que aparece a caixa de dialogo do Android informando que o app não esta respondendo e ai ele pode Esperar ou Sair como eu posso resolver isto, ou seja esta caixa de dialogo ta dando a possibilidade do usuário sair. Eu também tentei atualizar o progressDialog , qdo recebo o json eu fecho ele e ao iniciar a leitura do mesmo chamo a progress com outra mensagem para o usuário perceber que agora estou "Atualizando a base de dados..." mais nesta segunda vez ela não aparece. Na activity tenho estes métodos
   @Override
    public void showProgress(String mensagem) {
        dialog = new ProgressDialog(this);
        dialog.setMessage(mensagem);
        dialog.setCancelable(false);
        dialog.show();
    }

    @Override
    public void hideProgress() {
        if (dialog.isShowing())
            dialog.dismiss();
    }
a parte de network esta aqui
    @Override
    public void sincronizarFromRemoteServer() {

        inicializaServicos();

        serverRequest = new CustomRequest(
                Helper.PATH_TO_SINCRONIZAR ,
                this.params,
                createRequestSuccessListener(),
                createRequestErrorListener());

        serverRequest.setRetryPolicy(new DefaultRetryPolicy(
                Helper.MY_SOCKET_TIMEOUT_MS * 2,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        presenter.showProgress("Sincronizando, aguarde...");

        VolleySingleton.getInstance(presenter.getContext()).addToRequestQueue(serverRequest);
    }

  private Response.Listener<JSONObject> createRequestSuccessListener() {
        return new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                //presenter.hideProgress();
                try {
                    Log.d(TAG, "Json Response " + response);
                    if (response.getJSONArray("errors").length() == 0){
                        JSONObject json = response.getJSONObject("results");
                        presenter.atualizaDados(json);
                    } else {
                        presenter.fimSincronismo(false);
                    }
                } catch (Exception e){
                    //Log.d(TAG, "Ocorreu um erro " );
                    e.printStackTrace();
                }
            }
        };
    }
Responder
Vinícius Thiengo (1) (0)
03/01/2018
Robson, tudo bem?

12.000 registros com o Volley API! É importante ressaltar que o Volley foi feito para a requisição / envio de poucos dados, na casa dos KBs.

Trabalhar com 12.000 registros junto ao Volley pode estar colocando seu aplicativo no "caminho da sorte", ou seja, funciona de maneira consistente em ambiente de desenvolvimento, com emuladores, mas não permanece consistente quando rodando em alguns devices reais.

De qualquer forma, manter o usuário com o app ativo enquanto são baixados dados grandes o bastante para o fazerem aguardar mais de 5 segundos é algo crítico. Estude a possibilidade de manter a requisição dos dados em segundo plano, utilizando APIs como:

-> GcmNetworkManager: https://www.thiengo.com.br/gcmnetworkmanager-para-execucao-de-tarefas-no-background-android

-> Service: https://www.thiengo.com.br/service-android-entendendo-e-utilizando

Junto a alguma outra API de requisição remota como:

-> Retrofit (sem sombra de dúvidas a melhor opção): https://www.thiengo.com.br/library-retrofit-2-no-android

-> AsyncHttp: http://loopj.com/android-async-http/

Agora o Dialog informando que o aplicativo não está respondendo, provavelmente é devido a todo o código em onResponse() de Response.Listener<JSONObject>.

Se me lembro bem o conteúdo dentro de onResponse() já está sendo executado na Thread de invocação de requisição remota, ou seja, em sua lógica de negócio, aparentemente, é a Thread Principal (Thread UI).

Neste caso, processar o parser JSON de 12.000 registros trava o aplicativo tempo suficiente (5 segundos) para o Android então apresentar este Dialog de informe sobre a paralisação do app.

A solução então é realizar esse parser em uma Thread de background, pode até mesmo ser realizada em um IntentService: https://www.thiengo.com.br/intentservice-no-android-entendendo-e-utilizando

Robson, tente primeiro a solução da Thread de background, pois a princípio é o que realmente pararia com o Dialog do Android.

Depois estude a possibilidade de mudar a API de requisição remota de dados e também a lógica de negócio utilizada atualmente, passando o algoritmo de solicitação de dados para alguma outra API: Service ou GcmNetworkManager, por exemplo.

Abraço.
Responder
Robson (1) (0)
03/01/2018
Thiengo meu caro, obrigado pelo retorno, e sim to sabendo que o Volley não é para esta quantidade de registros, na verdade o json da API que estou consumindo evoluiu e eu nem esperava esta quantidade de registros, conheço o loopj.com/android-async-http e já estou usando ele neste mesmo app para o Envio de dados.

Sobre executar em Background eu penso que pode não ser o caso uma vez que o usuário acessou a atividade e mandou fazer a sincronização, ele sabe que este processo demora um pouco e ao e eu mostro a quantidade de (Veiculos, Clientes, OS, Equipamentos, etc..., o json vem com dados de várias entidades) que foram atualizados, alias eu faço isto dentro do onResponse()
chamando o método presenter.atualizaDados(json)
mais estes TxtView´s com as quantidades também não são atualizados em tempo real( só no fim de tudo), eu julguei que atualizando a View com os dados recebidos já evitaria a caixa de dialogo do Android porque o que demora nem é a requisição a API que estou consumindo mais sim a inserção dos dados no banco, então eu pensei se eu insiro os dados de veiculos atualizo a View, se insiro clientes atualizo a View, e assim sucessivamente, e isto faria o Android "perceber" que o app esta respondendo, mais foi engano.

Mais então entendi que vc aponta para uma solução que é usar o IntentService certo? eu não fiz isto por saber que o Volley ele mesmo cria uma Thread para uso dele então não vi o motivo para criar um Service então usei o Volley na Activity.
Responder
Vinícius Thiengo (1) (0)
03/01/2018
Robson, show de bola que já está com o AsyncHttp.

Sobre o IntentService: essa é uma solução possível para o parser JSON e a inserção em banco de dados, digo, todos estes processos em background, outra Thread, para não travar o aplicativo e gerar o Dialog Android.

A Thread que o Volley (o AsyncHttp também) cria é somente para a requisição / envio de dados, quando os dados são retornados a Thread em execução volta a ser a Thread de origem da requisição do Volley, por isso a necessidade de uma Thread em seu caso, pois provavelmente a Thread onde há a requisição Volley (AsyncHttp), em sua lógica de negócio, é a Thread UI.

Depois, se possível, informe se deu tudo certo.

Abraço.
Responder
Robson (1) (0)
26/01/2018
Olá Thiengo bom dia. Cara eu entendi quando vc diz que apos a requisição do volley (ou seja dentro do onResponse) eu estou executando dentro da UI Thread, blz então o que eu fiz criei uma classe AsyncTask coloquei tudo o que havia em minha classe Modelo nela, tudo a requisição do volley, a atualização da base de dados, tudo agora dentro do doInBackground mais, pra infelicidade tudo funcionou como antes ou seja continuo com o ANR e agora sem entender o motivo, inclusive já postei até no stack mais nem comentários. Eu criei uma classe dentro do presenter e chamei ela
@Override
public void sincronizarFromRemoteServer() {

    if(!Helper.isNetworkAvailable(getContext())){
        showToastErro("Sem conexão");
    } else {
        if (mTarefaAsync == null || mTarefaAsync.getStatus() != AsyncTask.Status.RUNNING) {
            mTarefaAsync = new TarefaAsync(this);
            mTarefaAsync.execute();
        }
    }
}
//AsyncTask
public static class TarefaAsync extends AsyncTask<Void, Integer, Void>{

    private static final String TAG = TarefaAsync.class.getSimpleName();

    public static final int STATE_ERROR=0;
    public static final int STATE_LOADING=1;
    public static final int STATE_EMPTY=2;
    public static final int STATE_UPDATING=3;
    public static final int STATE_END=100;

    private final MVPSincronizar.PresenterImpl presenter;
    private final OrdemServicoService ordemServicoService;
    private final TipoVeiculoService tipoVeiculoService;
    private final TipoAtendimentoService tipoAtendimentoService;
    private final ClienteService clienteService;
    private final CustomSharedPreference shared;


    public TarefaAsync(MVPSincronizar.PresenterImpl presenter) {
        this.presenter = presenter;
        ordemServicoService = new OrdemServicoService(presenter.getContext());
        tipoVeiculoService = new TipoVeiculoService(presenter.getContext());
        tipoAtendimentoService = new TipoAtendimentoService(presenter.getContext());
        clienteService = new ClienteService(presenter.getContext());
        shared = ((Aplicacao) presenter.getContext().getApplicationContext()).getShared();
    }
@Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        switch (values[0]) {
            case STATE_ERROR:
                presenter.hideProgress();
                presenter.showToastErro("Ocorreu um erro");
                break;
            case STATE_LOADING:
                presenter.showProgress("Sincronizando, aguarde...");
                break;
            case STATE_EMPTY:
                presenter.hideProgress();
                break;
            case STATE_UPDATING:
                presenter.showProgress("Aguarde, atualizando dados...");
                break;
            case 10:
                presenter.showQtdClientes(values[1]);
                break;
            case 11:
                presenter.showQtdOrdens(values[1]);
                break;
            case 12:
                presenter.showQtdEquipamentos(values[1]);
                break;
            case 13:
                presenter.showQtdVeiculos(values[1]);
                break;
            case 14:
                presenter.showQtdPortas(values[1]);
                break;
            case 15:
                Log.d(TAG, "Tipos de veiculos = " + String.valueOf(values[1]) );
                presenter.showQtdTipoVeiculos(values[1]);
                break;
            case STATE_END:
                presenter.hideProgress();
                presenter.fimSincronismo(true);
                break;
        }
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        //presenter.hideProgress();
    }

    @Override
    protected Void doInBackground(Void... params) {
        publishProgress(STATE_LOADING);

        // Apagar todos os registros
        tipoAtendimentoService.DeleteTodos();
        statusService.DeleteTodos();
        portasVeiculoService.DeleteTodos();
        clienteService.DeleteTodos();
        tipoVeiculoService.DeleteTodos();
        contatoService.DeleteTodos();
        equipamentoService.DeleteTodos();
        veiculoService.DeleteTodos();
        ordemServicoService.DeleteTodos();
        errosService.DeleteTodos();
        sincronismoService.DeleteTodos();
        //
        shared.setDataAtualizacao((int) new Date().getTime());
        shared.setDataSincronismo((int) new Date().getTime());

        Map<String, String> parametros = new HashMap<String,String>();

        parametros.put(Helper.TOKEN, presenter.getUser().getToken());
        parametros.put(Helper.SERVIDOR, "1");
        parametros.put("completo", "1");

        CustomRequest serverRequest = new CustomRequest(
                Helper.PATH_TO_SINCRONIZAR,
                parametros,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        publishProgress(STATE_EMPTY);
                        try {
                            Log.d(TAG, "Json Response " + response);
                            if (response.getJSONArray("errors").length() == 0) {
                                publishProgress(STATE_UPDATING);
                                JSONObject json = response.getJSONObject("results");
                                Iterator keys = json.keys();

                                Integer[] recupdate = new Integer[2];
                                recupdate[0] = 0;
                                recupdate[1] = 0;

                                while(keys.hasNext()){
                                    String currentDynamicKey = (String)keys.next();
                                    JSONArray records = null;
                                    Log.d(TAG, "Chave: " + currentDynamicKey );

                                    switch (currentDynamicKey) {
                                        case "tipos_veiculo":
                                            try {
                                                records = json.getJSONArray("tipos_veiculo");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaTipoDeVeiculos(records);
                                            Log.d(TAG, "Tipos de veiculos " + String.valueOf(records.length()) );
                                            recupdate[0]=15;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "status":
                                            try {
                                                records = json.getJSONArray("status");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaStatus(records);
                                            Log.d(TAG, "Status " + String.valueOf(records.length()) );
                                            break;
                                        case "contatos":
                                            try {
                                                records = json.getJSONArray("contatos");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaContatos(records);
                                            Log.d(TAG, "Contatos " + String.valueOf(records.length()) );
                                            break;
                                        case "veiculos":
                                            try {
                                                records = json.getJSONArray("veiculos");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaVeiculos(records);
                                            Log.d(TAG, "Veiculos " + String.valueOf(records.length()) );
                                            recupdate[0]=13;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "clientes":
                                            try {
                                                records = json.getJSONArray("clientes");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaClientes(records);
                                            Log.d(TAG, "Clientes " + String.valueOf(records.length()) );
                                            recupdate[0]=10;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "portas_veiculo":
                                            try {
                                                records = json.getJSONArray("portas_veiculo");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaPortasVeiculos(records);
                                            Log.d(TAG, "Portas " + String.valueOf(records.length()) );
                                            recupdate[0]=14;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "equipamentos":
                                            try {
                                                records = json.getJSONArray("equipamentos");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaEquipamentos(records);
                                            Log.d(TAG, "Equipamentos " + String.valueOf(records.length()) );
                                            recupdate[0]=12;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "ordens_servico":
                                            try {
                                                records = json.getJSONArray("ordens_servico");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaOrdemServico(records);
                                            Log.d(TAG, "O. Servicos " + String.valueOf(records.length()) );
                                            recupdate[0]=11;
                                            recupdate[1]=records.length();
                                            publishProgress(recupdate);
                                            break;
                                        case "tipos_atendimento":
                                            try {
                                                records = json.getJSONArray("tipos_atendimento");
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }
                                            SincronizaTipoAtendimento(records);
                                            Log.d(TAG, "Tipos de atendimento " + String.valueOf(records.length()) );
                                            break;
                                    }
                                }
                                shared.setDataAtualizacao( new Date().getTime() );
                                shared.setDataSincronismo( new Date().getTime() );
                                publishProgress(100);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        publishProgress(STATE_ERROR);
                        Log.d(TAG, "Erro - mensagem: " + error.getMessage());
                        error.printStackTrace();
                    }
                });

        serverRequest.setRetryPolicy(new DefaultRetryPolicy(
                Helper.MY_SOCKET_TIMEOUT_MS * 2,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(presenter.getContext()).addToRequestQueue(serverRequest);

        return null;
    }
Errei ou entendi errado? agora tudo esta sendo executado dentro de um AsyncTask
Responder
Vinícius Thiengo (0) (0)
26/01/2018
Robson, tudo bem?

Com a atualização, porém a permanência do erro, até onde os Log.d(TAG, ?) são apresentados nos logs do Android Studio?

Abraço.
Responder
Robson (1) (0)
26/01/2018
Olá! Meu caro, sim os log's são apresentados ou seja tudo ocorre como antes mesmo agora que tudo é feito fora da thread principal, não surtiu nenhum efeito o asynctask
Responder
Vinícius Thiengo (0) (0)
26/01/2018
Robson, o código do Volley fica fora da classe que implementa o AsyncTask, ao menos fora do método doInBackground().

O código que deve vir junto a classe AsyncTask, no método doInBackground(), é o código atual que está dentro do onResponse() do Volley CustomRequest.

Ou seja, reconfigure seu algoritmo para que a classe TarefaAsync possa ter um objeto criado quando houver a resposta do backend Web, isso dentro do método onResponse().

Esse objeto receberá como parâmetro do execute() os dados recebidos em onResponse(). Assim, como informado anteriormente, será o executado o código de parse e cia no doInBackground().

Se mesmo assim o código continuar com ANR, pare o trabalho com AsyncTask e crie uma Thread para a execução em background. Mas lembre que o Volley continua fora, iniciando na Thread principal. Somente o código "pós dados baixados" é que irá para o método run() da Thread.

Abraço.
Responder
19/02/2017
la thiengo !! poxa eu estou montando um app , mas nao sei direito como fazer, tipo, eu queria fazer um app onde eu pudesse criar pastas (categorias) e dentro de cada pasta colocar um foto com uma descrição. exemplo (cadastrar um musicas e dentro da pasta musicas colocar uma imagem e descrição) . Obrigado pela atenção.
Responder
Vinícius Thiengo (0) (0)
19/02/2017
João,

Primeiro você tem que aprender a desenvolver aplicativos Android, para isso siga o artigo do link a seguir: http://www.thiengo.com.br/estudando-android-lista-de-conteudos-do-blog

Depois, com o conhecimento base do Android, você conseguirá construir o aplicativo que descreveu na mensagem. Abraço.
Responder
26/03/2016
Fala Thiengo, blz? Estava querendo implementar no recyclerview, um contador único de visualização. Vc conhece alguma biblioteca ou uma dica que eu possa implementar nesse Recycler?
Responder
Vinícius Thiengo (1) (0)
26/03/2016
Fala Caio, blz
Vc diz um contador de visualização ao RecyclerView ou cada item teria seu próprio contador, tipo, se o item aparecer na tela conta com mais um? Em ambos os casos vc pode utilizar uma base local e rápida como o Realm para incrementar o contador de cada item, por exemplo.

Deixe esse contador vinculado ao id único do item sendo apresentado. Esse id pode ser o mesmo do item em sua base de dados Web (se essa for a fonte de seus itens).

Para cada device vc consegue ter um identificador único, melhor ainda se sua APP for uma de login, dessa forma será mais tranquilo não marcar um item como visualizado mais de uma vez para o mesmo user.

Abaixo o link do Realm e do SQLite caso siga a ideia de utilizar a base local.Abraço

http://www.thiengo.com.br/persistencia-de-dados-com-realm-no-android-parte-1

http://www.thiengo.com.br/sqlite-no-android-entendendo-e-utilizando
Responder
Danilo (1) (0)
19/02/2016
Boa Noite Thiengo, eu tenho recyclerview com objetos parcelable vindos do bd remoto e uma activity de detalhe para mostrar as informações detalhadas dos itens do recyclerview. Acontece que na minha activity de detalhe, eu tenho um botão que, ao ser tocado, abriria uma outra activity com um recyclerview dentro, pois são informações complementares, e devem vir do bd remoto também. Eu tentei passar para outra activity o id do objeto e na activity que carregaria o recyclerview, realizar um wraptoObject para o custom request, conforme você ensinou. Não sei se fui claro na explicação, mas se vc tiver entendido, qual seria a melhor forma de eu fazer outra requisição a partir da activity de detalhe e mostrar um outro recyclerview? Valeu.. Abraço.
Responder
Vinícius Thiengo (0) (0)
20/02/2016
Fala Danilo, blz?
Igualmente fez para preencher o primeiro Recycler, porém somente mude os parâmetros para que o conteúdo seja filtrado corretamente no lado servidor. Veja tb se há a possibilidade de migrar de Volley para Retrofit 2.0 que mais eficiente e simples (http://www.thiengo.com.br/library-retrofit-2-no-android ). Abraço
Responder
Danilo (1) (0)
15/12/2015
Boa Noite, Thiengo. Vc teria alguma dica de biblioteca que possa implementar um leitor de feed RSS? Abraço.
Responder
Vinícius Thiengo (0) (0)
16/12/2015
Fala Danilo, blz?
Dê uma olhada nessas (https://android-arsenal.com/search?q=rss ). Abraço
Responder
19/11/2015
hi
i use cach volly for store data but my problem is when move between tab, page reload or when wifi is turn of, cach not load.
please help
Responder
Vinícius Thiengo (0) (0)
23/11/2015
Hi Ghafoori.
Try to use some local database, like SQLite (http://www.thiengo.com.br/sqlite-no-android-entendendo-e-utilizando ) and Realm (http://www.thiengo.com.br/persistencia-de-dados-com-realm-no-android-parte-1 ), because the volley cache is not powerful to keep all data in a long term. Have a good one.
Responder
18/11/2015
Hi
how cache date receive for when user offline?
Responder
Vinícius Thiengo (0) (0)
18/11/2015
Hi Ghafoori,
You can use a local database like Realm (http://www.thiengo.com.br/persistencia-de-dados-com-realm-no-android-parte-1 ), or if it is so simple, just SharedPreferences is enough (http://www.thiengo.com.br/sharedpreferences-no-android-entendendo-e-utilizando ). Have a good one.
Responder
13/11/2015
Fala Thiengo, blz!?
Parabéns pelo video, show de bola! No projeto, fiz umas alterações no CarActivity, e estou utilizando ViewPager, com ZoomableDraweeView do Fresco, e surgiu uma dificuldade em chamar a string urlPhoto. Estou quero chamar várias imagens em uma só string urlPhoto. Qual a melhor solução?  

@Override
protected void onCreate(Bundle savedInstanceState) {
...
super.onCreate(savedInstanceState);
...
Fresco.initialize(this);
setContentView(R.layout.activity_car);
...
ViewPager vpGallery = (ViewPager) findViewById(R.id.vp_gallery);
vpGallery.setAdapter(new GalleryAdapter());
}

class GalleryAdapter extends PagerAdapter {

         int w = 0;
         String[] items = new String[]{
                DataUrl.getUrlCustom(car.getUrlPhoto(), w)
         };


        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ZoomableDraweeView view = new ZoomableDraweeView(container.getContext());
            view.setController(
                    Fresco.newDraweeControllerBuilder()
                            .setUri(Uri.parse(items[position]))
                            .setOldController(view.getController())
                            .build());

            GenericDraweeHierarchy hierarchy =
                    new GenericDraweeHierarchyBuilder(container.getResources())
                            .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
                            .setProgressBarImage(new ProgressBarDrawable())
                            .build();

            view.setHierarchy(hierarchy);

            container.addView(view,
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

        @Override
        public int getCount() {
            return items.length;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }
    }
Responder
Vinícius Thiengo (1) (0)
29/11/2015
Fala Caio, blz?
Dê uma split na String para separar as urls e então utilize a chamada Fresco para cada url. Veja se assim atende a necessidade de seu projeto. Abraço
Responder
Thiago (1) (0)
02/10/2015
Boa noite, Thiengo!
Thiengo, queria saber onde eu coloco no projeto o método sqlite para inserir os registros que são consumidos pelo webservice! estou tentando inserir no metodo doAfter(), mas não está dando certo! você poderia me ajudar a resolver esse problema?
Obrigado pela atenção.
Responder
Vinícius Thiengo (1) (0)
03/10/2015
Fala Thiago, blz?
Com o SQLite, tem "n" libs hj que facilitam a utilização dela no Android, faça o seguinte, ao invés de ficarmos aqui quebrando a cabeça para colocar o SQlite puro em uma Thread secundária para então gravar os dados que chegam no doAfter() sem travar a Thread de UI, tente a utilização da lib SugarORM (http://satyan.github.io/sugar/ ) que foi até o momento a mais simples e robusta que encontrei para abstrair todo o grande código de utilização do SQlite na unha. Veja no site o modo de instalação e utilização e tente salvar e buscar utilizando ele. Caso tenha dificuldades volte ae. Abraço
Responder
Ruan Alves (1) (0)
30/09/2015
Boa tarde! Blz?

Bom ótima aula, como sempre! Minha dúvida é o seguinte, preciso movimentar de ida e volta muitos dados (tipo 3000 produtos para pegar do webservice), além desta biblioteca tem OkHttp e Retrofit, queria saber se você tem exemplos do mesmo, de implementação, e também exemplos de uso correto de threads ..... obrigado! e parabéns
Responder
Vinícius Thiengo (0) (0)
03/10/2015
Fala Ruan, blz? Ambas as libs encapsulam a utilização da Thread de background e outras particularidades que o developer deveria implementar caso fosse realizar a conexão montando o próprio script. Logo deixe esse trabalho ainda com elas. Porém o Volley foi feito para carregamento de poucos dados, 3000 é um número mt grande caso seja 3000 objetos com outros "n" dados cada um. A um tempo estava estudando outras libs e vi que o OkHttp se sai melhor em muitos dos contextos diferentes utilizados em comparação com o Volley e em alguns casos com o Retrofit tb (mesmo esse ultimo tb podendo utilizar o OkHttp internamente - ambos são da Square). Logo o que recomendo é utilizar o OkHttp para esse tipo de problema, na verdade testar com cada uma seria ideal, mas como já temos dados de developers que já testaram, não há pq faze-lo novamente. Quanto a implementação do OkHttp ou do Retrofit, ainda não tenho. Abraço
Responder
Thiago (1) (0)
29/09/2015
Thiengo blz? Estou com um problema aqui, vou tentar te explicar. Tenho um webservice Restful em java que esta retornando uma String no formato Json, ok? no lado cliente estou implementando as classes do tutorial para realizar a requisição do dado Json, a minha aplicação está retornando e listando os dados convertido no emulador android. O meu problema é que toda vez que realizo o Refresh o mesmo dado é mostrado, queria que você me desse uma luz de como tratar isso, para que não pegasse o mesmo dado que esta no servidor.
Responder
Vinícius Thiengo (1) (0)
03/10/2015
Fala Thiago, blz?
A principio seu problema é de lógica tendo em mente que provavelmente vc está enviando os mesmo dados para o servidor, porém esperando resultados diferentes dos já obtidos. Se tiver carregado uma lista de objetos do servidor remoto em sua APP e então for requisitar itens mais novos do que o mais atual sendo já apresentado então junto aos dados de requisição que for enviar partindo da APP, deve haver algum dado que permita na query de seu database verificar que somente itens com um dado mais atual que o enviado pela APP sejam retornados pela query. Um exemplo clássico é trazer junto aos itens retornados pelo servidor, o id deles no database remoto, dessa forma as requisições por itens mais novos teriam o id do item mais atual na lista sendo enviado como parte da requisição, dessa forma a query seria construída de uma forma que somente itens com ids maiores que o enviado fossem retornados. O contrário seria o mesmo, ou seja, para carregar itens mais antigos a query verificaria somente itens com ids menores do que o enviado, porém nesse caso vc deverá enviar o id do item mais antigo na lista e não o mais atual.

Caso já esteja fazendo isso, enviando o id correto para realizar o filtro no database remoto, então mt provavelmente seu problema é a cache do Volley (que deveria ser improvável se os dados de requisição não são os mesmos já utilizados - o id enviado forçaria essa diferença). Para verificar se realmente não é a cache, desligue a mesma como nesse post (http://stackoverflow.com/a/25333872/2578331 ) e realize as requisições da maneira como já está fazendo para ver se obtém resultados diferentes (os esperados).

Outra hipótese é que seus itens não estão sendo buscados com base em ids ou qualquer outro dados que permita a classificação de mais novos e mais antigos. Esse outro caso é comum quando vc permite o user buscar dados com base em um filtro de pesquisa definido em sua APP, nesse caso os resultados no topo da lista são os mais relevantes descendo para os menos relevantes. Nesse caso o que faço é apenas deixar ligado a parte de "Carregar menos relevantes" para quando tenho um limite de resultados sendo exibidos na tela, porém as requisições posteriores terão além dos itens de filtros definidos pelo user os ids de todos os itens já presentes na lista, dessa forma tenho certeza de que os próximos itens carregados serão sim os menos relevantes. Teste essas hipóteses e veja se alguma funciona. Abraço
Responder
Danilo (1) (0)
26/09/2015
Boa Noite, Thiengo. Pelo que li na documentação do Sqlite, não é possível executar o comando truncate para limpar os registros da tabela. Estou conseguindo salvar meus dados vindos do meu banco remoto. Porém, a cada requisição ou acesso ao fragment que carrega esses dados, ele repete os registros, replicando-os no banco de dados. Como vc faz para não ter essa replicação, de modo só que os dados que o usuário está sendo salvos uma única vez? Já tentei deletar os existentes antes de inserir novos registros, mas ainda não obtive êxito. Abraço!
Responder
Vinícius Thiengo (0) (0)
27/09/2015
Fala Danilo, blz?
Na verdade o SQLite tem algumas particularidades, por exemplo, se vc estiver trazendo o id dos itens em sua base de dados remota, vc pode fazer a correspondência desses itens de acordo com os ids deles, por exemplo: se o item atual que veio do server tem o id igual a 3, no momento de salvar no SQLite vc preenche o campo "_ID" (essa é a forma de uso de id no SQLite Android) com o valor de 3 (e os outros campos, salve tb). Implemente esse script de inserção dentro de um try...catch, onde se houver exception indica que o item de _ID atual já está salvo no bd, logo dentro do catch() chame o método de update, onde a clausula where será "_ID = "+obj.getId(), dessa forma todos os itens novos serão inseridos e os itens já existentes serão atualizados. Tente assim para ver se vai sem problemas. Abraço
Responder
Danilo (1) (0)
19/09/2015
Boa noite, Thiengo. Estou tendo problema no momento de chamar a intent para a activity de detalhe. Quando clico no recyclerview, não acontece nenhuma ação. Estou seguindo a sua aula na íntegra. O que será que posso estar errado? Obrigado!! Abraço...
Responder
Vinícius Thiengo (0) (0)
22/09/2015
Fala Danilo, blz?
No LogCat está sendo printado alguma mensagem de warnning? Alias colocou um LogCat no listener para saber se realmente está entrando no método de listener? Abraço
Responder
Danilo (1) (0)
22/09/2015
Thiengo, eu resolvi colocando o método on click no on bind view holder. Funcionou... Aquela animaçãp de transição não funcionou, tava dando um erro de compatibilidade de api na chamada do beign delayed... Eu comentei e a acitivity abre normalmente, mas sem o efeito de transição. Na activity de detalhe,Será se daria pra colocar um map fragment do collapse ?
Responder
Vinícius Thiengo (0) (0)
24/09/2015
Collapse, vc diz AppBar, Toolbar, CoordinatorLayout, ... e sim, é possível sim
Responder
Danilo (2) (0)
19/09/2015
Bom dia, Thiengo. Eu to montando alguns fragments e todos correspondem a tabelas ou views diferentes. Uma dúvida, existe alguma forma de ter um search view para cada fragment? Tipo, quando eu mudar a fragment, o seachview realizar a busca nos dados relacionados aquele recyclerview. Abraço.
Responder
Vinícius Thiengo (0) (0)
19/09/2015
Fala Danilo, blz?
Colocar o SearchView dentro do fragment vc terá de testar, pois realmente não sei lhe dizer se é possível principalmente por causa dos métodos de menu que o SearchView utiliza.

Porém é tranquilo realizar a busca somente nos dados do fragment atual, uma maneira de fazer isso é realizar a busca e então com o texto informado enviar uma mensagem de Broadcast para com a busca realizada, nesse caso a mensagem será enviada pelo EventBus (http://www.thiengo.com.br/eventbus-lib-comunicacao-entre-entidades-android ). Logo se estiver com o ViewPager, por exemplo, vc consegue facilmente saber qual é a posição atual em execução (tab) e enviar junto a busca pelo EventBus, dessa forma todos os fragments podem estar registrados para receberem a mensagem do EventBus, porém somente a correta (a atual na tela) é que processará e então apresentará os dados corretos no RecyclerView do Fragment atual. Abraço
Responder
Danilo (1) (0)
19/09/2015
Pois é, eu não estou utilizando tabs, pois os fragments são de infornações diferente, entao coloquei um fragment para cada tipo de informação. Tipo, tem um fragment de relatórios de acidentes, outro fragment de investigações, outro fragment de ocorrências aeronáuticas. Queria ver se seria possível o searchview realizar a buscar relacionada a cada fragment. Por exemplo, quando selecionar o fragment de relatório, o searchview mudar para realizar a busca dos relatórios, se o fragment mudar para investigacoes, o searchview realizar a busca das investigações, isso no menu mesmo, na toolbar. Será se teria saída? Abraço!
Responder
Vinícius Thiengo (0) (0)
19/09/2015
Danilo, não precisa de tabs, foi somente um exemplo. Vc não muda o Search, ele continua sendo a mesma entidade, o que muda é qual fragment vai responder as buscas depois da mudança de fragment. Uma estratégia seria nos locais onde há a mudança de fragment seu script informar a Activity, que além dos fragments contém o Search, o número do atual fragment sendo exibido. Se as mudanças de fragment acontecem dentro dos próprios fragments vc pode utilizar uma variavel de instancia public na Activity onde ela poderá ser acessada dentro dos fragments da seguinte forma ((SuaActivity) getActivity() ).suaVariavelIntQueTemOPosition dessa forma as buscas continuariam nos Search sem problemas, porém junto ao EventBus seu script enviaria o int da variavel suaVariavelIntQueTemOPosition, dessa forma todos os fragments que estariam inscritos para ouvir ao evento do EventBus receberiam os dados de busca, porém somente o de indice correto (utilizando if..else vc faz essa verificação) é que utilizaria os dados, incluindo a string de busca, para filtrar a lista que está vinculada ao adapter do RecyclerView e então dar um notifyDataSetChanged() para atualizar a tela. Tente assim, não precisa de tabs e nem de novos SearchView. Abraço
Responder
Danilo (1) (0)
12/09/2015
Boa Noite Thiengo. Estou com uma dúvida a respeito da requisição dos dados no servidor a partir do id. Eu tenho uma tabela que os registros estão em ordem, aí a pesquisa funciona e retornar tudo normal e funciona perfeitamente. Agora tenho outras tabelas que eu busco registros que não estão ordenados por id. Ou seja, o registro mais atual pode não ter o id mais atual, pois é relacionado com a data de uma ocorrência. Vc tem alguma sugestão? Estou tentando fazer por data, visto que os ids não estão em ordem sequencial. Não sei se consegui expor a dúvida. Abraço e obrigado.
Responder
Vinícius Thiengo (0) (0)
12/09/2015
Fala Danilo, blz?
Faça o seguinte, ao invés de enviar junto a requisição de mais itens somente o id do último item altere seu script para enviar todos os ids dos itens que já estão presentes no device do user, dessa forma na SQL no lado servidor ao invés de utilizar id < utilize id not in( todos os ids enviados da APP aqui e separados por virgula ). Dessa forma deve funcionar sem problemas, inclusive para a busca que poderia continuar com o id < . Abraço
Responder
01/09/2015
Hi there, I'm sorry for not say thanks at first, and this is the moment :D you're amazing man, thanks for take to much time and teach us how develop great things,  and I resolve the problem that I had but know I saw again the video where you implemented the swipe view and,  I don't understand where I could change this, I want to show that listview, but just the element that return from my database,  I meant if the user has 3 record showing 3 record and if he swipe, Call my endpoint and show again that 3 records or more if there exists changes,  thanks in advance for read my comments
Responder
Vinícius Thiengo (0) (0)
03/09/2015
Hey Sanchez, what's up?
Before a forget, thanks for the nice words about this Blog, you are welcome.
Take a look at this video (https://www.youtube.com/watch?v=2lcBx4KVUVk ), that is an english version and well explained. Only to know: Did you try put your ListView tag inside a SwipeView tag, I meant, a wrapper one? Talk soon. Have a good one.
Responder
Danilo (1) (0)
20/08/2015
Thiengo, agora complicou... Consegui retirar todas as mensagens.
A saída ficou assim:

[{"id":"8","model":"Mustang","brand":"Ford","description":"Lorem Ipsum \u00e9 simplesmente uma simula\u00e7\u00e3o de texto da ind\u00fastria tipogr\u00e1fica e de impressos, e vem sendo utilizado desde o s\u00e9culo XVI, quando um impressor desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro de modelos de tipos. Lorem Ipsum sobreviveu n\u00e3o s\u00f3 a cinco s\u00e9culos, como tamb\u00e9m ao salto para a editora\u00e7\u00e3o eletr\u00f4nica, permanecendo essencialmente inalterado. Se popularizou na d\u00e9cada de 60, quando a Letraset lan\u00e7ou decalques contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser integrado a softwares de editora\u00e7\u00e3o eletr\u00f4nica como Aldus PageMaker.","category":"2","tel":"33221155","photo":0,"urlPhoto":"http:\/\/192.168.0.108\/TCMaterialDesign\/img\/car\/__w-395-593-790-1185__\/mustang.jpg","isNewer":false}

O erro ainda persiste...
onErrorResponse(): org.json.JSONException: End of input at character 0 of
Responder
Vinícius Thiengo (0) (0)
20/08/2015
Aparentemente agora está ok, o que está vendo é o conteúdo do item que deve ser apresentado no Android, somente não estou vendo o colchetes de fechamento, mas ele deve existir sim, caso contrário o método do json não chegaria nem a printar isso. Tente um utf8_encode() na string resultante do método de conversão do json, pois pode ser a codificação de caracteres o problema. Abraço
Responder
Danilo (1) (0)
20/08/2015
Thiengo, finalmente vou para de te perturbar, pelo menos por enquanto... :) Consegui resolver a pane. O que aconteceu: eu desativei todas as mensagens de erros e debugs do apache2 no php.ini, inclusive os NOTICES. A partir daí, o json ficou limpo, retirando toda aquela api_key. Agora eu tava tão preocupado procurando o erro que acabei encontrando o detalhe que tava evitando o volley receber o json, que foi o caso do String () na frente do json array. Sabe o que era? O var_dump(json_encode())... Não estava dando echo e o var_dump mostra o tipo do array e a quantidade de caracteres antes. Por isso sempre aparecia o String (336) e depois o json. Agora finalmente funcionou. Tudo isso por causa do apache. Mas deu trabalho, viu? Sou imensamente grato por sua ajuda... Com certeza, nessa persistência que tive, consegui aprender muito e a não cair mais no erro. Valeu, Grande Thiengo. Abraço e obrigado de verdade!
Responder
Vinícius Thiengo (1) (0)
20/08/2015
Show de bola. Abraço
Responder
Danilo (2) (0)
19/08/2015
Thiengo, acredito que esteja correto. A informação a ser escrita no arquivo texto foi a seguinte: array(1) {
  ["jsonObject"]=>
  string(75) "{"car":{"category":1,"id":0,"photo":0},"method":"get-cars","isNewer":false}"
}

Então, eu fiz o mesmo procedimento no momento da saída da consulta do banco para a variável $arrayCars. O resultado foi esse:
<br />
<b>Notice</b>:  Constant __API_KEY__ already defined in <b>/var/www/MaterialWeb/conf/conf.php</b> on line <b>3</b><br />
string(3674) "[{"id":"10","model":"CT6","brand":"Cadillac","description":"Lorem Ipsum \u00e9 simplesmente uma simula\u00e7\u00e3o de texto da ind\u00fastria tipogr\u00e1fica e de impressos, e vem sendo utilizado desde o s\u00e9culo XVI, quando um impressor desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro de modelos de tipos. Lorem Ipsum sobreviveu n\u00e3o s\u00f3 a cinco s\u00e9culos, como tamb\u00e9m ao salto para a editora\u00e7\u00e3o eletr\u00f4nica, permanecendo essencialmente inalterado. Se popularizou na d\u00e9cada de 60, quando a Letraset lan\u00e7ou decalques contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser integrado a softwares de editora\u00e7\u00e3o eletr\u00f4nica como Aldus PageMaker.","category":"1","tel":"33221155","photo":0,"urlPhoto":"http:\/\/192.168.0.109:80\/TCMaterialDesign\/img\/car\/__w-395-593-790-1185__\/ct6.jpg","isNewer":false}


Agora existe um onErrorResponse do volley, com a seguinte mensagem: onErrorResponse(): org.json.JSONException: End of input at character 0 of

O que poderia estar causando esse não recebimento do array Json? Muito obrigado
Responder
Vinícius Thiengo (0) (0)
19/08/2015
Danilo, provavelmente o problema é esse Notice que está sendo gerado devido a duplicação da constante __API_KEY__, vc está com mais d uma API configurada como constante. Desligue o Notice em seu php.ini (http://stackoverflow.com/a/2867077/2578331 ), vc não precisa dele por agora. Abraço
Responder
Danilo (1) (0)
20/08/2015
Thiengo, além do Notice, aquele termo "String (3674)" antes do array json também poderia causar esse erro? Valeu, abraço.
Responder
Vinícius Thiengo (0) (0)
20/08/2015
Sim, pois todo o conteúdo deve estar dentro de um JSONArray, pois é isso que o script Android está esperando como resposta. Mas acredito que com a correção do Notice esse dado perdido saia. Abraço
Responder
Danilo (1) (0)
20/08/2015
Thiengo, consegui retirar o Notice, porém, o String ( ) na frente do array json ainda permanece. Acredito que ainda esteja causando o mesmo erro... Você já conseguiu retirar essa tag ? Valeu..
Responder
Vinícius Thiengo (0) (0)
20/08/2015
Deve ser de algum var_dump() ou print_r(), perdido, não?
Responder
18/08/2015
Bom dia, primeiramente parabéns pelo trabalho, são poucos que se dedicam a passar o conhecimento ao próximo.
Pode me dizer onde encontro o inicio do projeto ? Fiquei meio perdido na parte do Transaction.
Obrigado
Responder
Vinícius Thiengo (0) (0)
18/08/2015
Responder
18/08/2015
Fala Thiengo, tudo tranquilo ! Corrigido o problema com o meu PHP, o projeto está executando sem problemas, mas tenho uma dúvida, estou preocupado em relação a utilização de conexão 3G, percebo que o método que testa a conexão funciona perfeitamente, mas ele não tem nenhum tipo de tolerância, é isso mesmo ? comparando com sua APP do Blog, ela é perfeita, sei que ela utiliza o sqllite, mas mesmo na primeira execução de sua APP com 3G, percebo que o processo de envio e recebimento de dados se adequa tranquilamente a conexão 3G, isso é uma característica da class JSONArray ? Obrigado
Responder
Vinícius Thiengo (1) (0)
18/08/2015
Fala Luiz Rogerio, blz sim.
Sua preocupação é se o script vai rodar ok para 3G tb? Se sim, ele vai. O grande problema é que o tempo de resposta cai bastante, a conexão é bem mais lenta, é ai que entra a outra parte importante do sistema mobile que tem extensão Web, o lado servidor. Nesse tipo de caso, ou deixamos a resposta vinda do servidor bem limitada (tamanho em caracteres) e em formato leve (JSON) ou melhoramos o processamento do servidor de forma horizontal (mais servidores trabalhando em paralelo) ou de forma vertical (colocando mais memória e poder de processamento no servidor já sendo utilizado). Hj a APP do Blog (e o Blog tb está começando...) está com esse problema, devido ao número de acessos que vem aumentando a APP está cada vez mais lenta, logo estou estudando para mudar o server, provavelmente vai ser expansão vertical, vou para um server mais robusto. Mas a ideia que quis passar foi qu para o script mobile tanto faz se é 3G ou WiFi, ou ... porém para o servidor que vai responder / receber dados faz diferença sim e 3G é realmente lenta. Abraço
Responder
Danilo (1) (0)
17/08/2015
Bom dia Thiengo. Cara, não consigo entender o pq não estou conseguindo sua estrutura web do push notification mais atual e substitui os arquivos de configuração. Continua dando erro de index jsonObject ao receber o $_POST. Acho que não passa daí... "Undefined index: jsonObject in /var/www/MaterialWeb/package/ctrl/CtrlCar.php on line 10" ; Trying to get property of non-object in /var/www/MaterialWeb/package/ctrl/CtrlCar.php on line 13 ... vc consegue rodar aí sem problemas... meu servidor web está no ubuntu, com php 5.5 e mysql... tenho outras aplicações que estão rodando nele numa boa, utilizando framework symfony... Não sei o que estou errando... Obrigado pela sua ajuda
Responder
Vinícius Thiengo (0) (0)
18/08/2015
Danilo, confirme para mim se no Android, no momento da requisição o método utilizado é o POST e se há algum key-value onde key é igual a "jsonObject", pois é isso que o PHP está esperando. Abraço
Responder
Danilo (1) (0)
18/08/2015
Thiengo, utilizando o seu projeto de carros também, os parâmetro são esses:
params.put("jsonObject", gson.toJson(obj));
...
CustomRequest request = new CustomRequest(Request.Method.POST,
            "http://192.168.0.108/MaterialWeb/package/ctrl/CtrlCar.php",
           params ...

Fiz um log cat para verificar o conteudo na variável params. A saída foi essa:
I/LOG?{jsonObject={"car":{"category":1,"id":0,"photo":0},"method":"get-cars","isNewer":false}}

Acredito que o json_decode ($_POST['jsonObject']) não está encontrando um formato ou será se o volley nao está enviando a requisição? Acho que no lado do app tá tudo ok... Agora não sei o pq... Abraço
Responder
Vinícius Thiengo (0) (0)
18/08/2015
Danilo, a chamada é assim mesmo (o IP é o de sua máquina ou o do seu server). Faça o seguinte, coloque o script abaixo logo no inicio do arquivo php:

ob_start();
var_dump($_POST);
$handle = fopen('data-from-app.txt', 'w');
fwrite($handle, ob_get_clean());
fclose($handle);

Com isso vamos saber o que realmente está sendo enviado para o servidor, esse script criará um arquivo .txt com todos os dados sendo enviados via POST, assim vai dar para debugar melhor. Abraço
Responder
Danilo (1) (0)
16/08/2015
Boa noite Thiengo. Retirei o __PATH__ do autoload, na raiz do projeto. Acontece que rodei o app e me parece que houve um erro de requisição volley, por não passar para objeto json. O erro é o seguinte:  onErrorResponse(): org.json.JSONException: "Value <br of type java.lang.String cannot be converted to JSONArray". Pow cara, to te incomodando muito hein... foi mal...
Responder
Vinícius Thiengo (0) (0)
17/08/2015
Danilo, aparentemente ainda tem bug no lado servidor, essa mensagem é comum quando no PHP ainda está tendo algum error fatal. Certificou-se de que o bd está instalado e rodando com as tabelas corretas? Se possível, utilize o monolog (https://github.com/Seldaek/monolog ) para conseguir pegar os problemas que está ocorrendo, ele é apenas um componente PHP. Ou utilize um depurador se estiver com algum IDE. Abraço
Responder
Danilo (1) (0)
13/08/2015
Boa Noite Thiengo. Consegui fazer os controllers funcionarem. Agora o log cat retorna a seguinte mensagem: "Value controller of type java.lang.String cannot be converted to JSONArray". Será que o server não está retornando nenhum objeto json ? Abraço..
Responder
Vinícius Thiengo (0) (0)
14/08/2015
Fala Danilo, então, não está sendo retornado um JSON, isso costuma acontecer quando há algum erro no lado servidor e o próprio PHP printa a mensagem de erro fazendo com que ela seja o retorno da chamada realizada pelo device. Costumo primeiro realizar a mesma chamada via protocolo GET, passando os mesmo parametros, para então ver se há erro ou não na resposta, depois apenas mudo o script para trabalhar com POST e o Android call passa sem problemas. Abraço
Responder
Danilo (1) (0)
15/08/2015
Thiengo, boa noite. Eu verifiquei os erros que não estão permitindo o retorno do json. Na requisição volley enviada ao servidor, me parece que é um JsonArray, é isso? Pelo erro do php, o controller está tentado acessar um método de um não objeto. Este é o erro: Notice: Trying to get property of non-object ... referente a linha que ele pega $jsonObject->method...
O outro erro é:  Use of undefined constant __PATH__ - assumed '__PATH__' ... Esse no arquivo autoload.php
Valeu... Abrs
Responder
Vinícius Thiengo (0) (0)
15/08/2015
Fala Danilo, blz?
Na verdade é enviado um objeto JSON para o PHP e retornado um jsonArray para o Android. Quanto ao problema com o __PATH__, my mistake, vá ao arquivo autload.php que está na raiz do projeto (não o do vendor) e remova a linha:

$auxPath = substr_count(__PATH__, '.') > 0 ? __PATH__ : '';

Colocando apenas: $auxPath = '';

Assim deve funcionar. Abraço
Responder
Danilo (1) (0)
16/08/2015
E quanto a esse erro "Trying to get property of non-object ."? Vc já chegou a se deparar com ele ao receber o post do volley? Abs
Responder
Vinícius Thiengo (0) (0)
16/08/2015
É exatamente o que a mensagem está dizendo, está tentando acessar propriedades de uma entidade que não é um objeto. Por exemplo: jsonData.getString("name"); porém jsonData não é um objeto json e sim uma String, ou um int, ou ... logo, devido ao error com o uso do __PATH__ (que deve ser removido), pode ser que o retorno da mensagem de erro de forma inesperada e fora de um objeto JSON faça com que seja impossível trabalhar um objeto JSON retornado em quanto esse problema não for resolvido. Abraço
Responder
Danilo (1) (0)
12/08/2015
Boa noite Thiengo. Como poderia fazer o teste desse controller? Eu já fiz tudo que podia. Está exatamente como vc ensinou, mas continua dando status 500. Já estou pensando em testar em outra estrutura mvc para ver o que estou errando... Desde já agradeço. Abraço.
Responder
Vinícius Thiengo (0) (0)
13/08/2015
Fala Danilo, blz?
Com erro 500 não é problema com o controller e sim na conf do servidor, não sei se já perguntei, mas certificou-se de que o server está realmente funcionando? Uma simples chamada no navegador a uma determinada área no server já seria o suficiente para saber se o server está ou não funcionando. Se possível informe o url do servidor e o path do controller nele se ess for de dominio publico. Abraço
Responder
Danilp (1) (0)
13/08/2015
Primeiro eu utilizei em um servidor fora, hospedei em um domínio externo. Agora estou testando em um server local, em uma máquina linux com apache, mysql e php. O server responde quando digito o ip, pois aparece a arvore de diretório do /var/www do apache, com os projetos existentes. Tipo: http://192.168.0.103:80/tc-materialDesign/package/ctrl/CtrlInvestigacao.php
... Uma coisa que aconteceu, foi quando, para testar, modifiquei o controller pra receber um GET, então quando acessei esse endereço acima pela url, o server me retornou status 200, mesmo sendo uma requisicao post. Quando retorno o controller para receber POST novamente, ele retorna status 200 para uma requisicao GET. Talvez eu esteja errando nesse momento... Muito obrigado pela ajuda..
Responder
Danilo (1) (0)
13/08/2015
Enviei o post, porém acho que errei o meu email... Então, acho que devo estar errando no momento de receber os dados POST no controller. Abraço..
Responder
Vinícius Thiengo (0) (0)
14/08/2015
Danilo, a resposta 200 é de conexão bem sucedida, não importa se foi utilizado o POST ou o GET. O que não vai conseguir é a execução adequada do script se estiver trabalhando com POST, porém recebendo GET, mas isso pouco importa para o server (Apache, NGINX, ...) que recebeu a conexão... enfim, o script rodou sem problemas (depois de a base de dados instalada e configurada corretamente tb)? Abraço
Responder
Vinícius Thiengo (0) (0)
14/08/2015
É, já conseguiu a conexão, that's good! Agora é ver se está entrando nos if...else, se é POST ou GET que seu código está adaptado para utilizar e por ai vai. Abraço
Responder
Danilo (1) (0)
11/08/2015
Eu coloco o echo no arquivo php correspondente ao controller? Estou confundindo um pouco, pois vc utiliza o método POST para obter todos os carros, correto? Mas na primeira requisição, o que é enviado via post para obter todos os carros? Como estava fazendo via GET, embaralhei um pouco.
Abraço!
Responder
Vinícius Thiengo (0) (0)
12/08/2015
Danilo, isso, o echo é no controller. Sempre utilize o POST como no APP de exemplo, utilize o GET somente se for testar algo rápido e quiser utilizar a URL para isso. Na primeira vez que realizo a requisição não tem dados, logo o server me retorna o número total possível para cada requisição, já nas outras vezes (carregando dados mais antigos) envio o id do último item apresentado pegando então somente carros com id menor que o enviado. Abraço
Responder
Danilo (1) (0)
10/08/2015
Boa Noite Thiengo. Eu estou seguindo a sua logica de requisição POST do volley para o servidor. Acredito que meu servidor não esteja corretamente configurado, pois aparece a seguinte mensagem: [18811] BasicNetwork.performRequest: Unexpected response code 500 for http://www..... Vc criou algum virtual host no apache para a pasta que contém as classes de controladores para resposta da requisição em json? Abraço.
Responder
Vinícius Thiengo (0) (0)
11/08/2015
Fala Danilo, blz?
Nesse não criei host virtual. Conseguiu acessar o controller diretamente da url? Se ainda não testou, coloque um echo no final do arquivo para certificar-se de que está indo sem problemas. 500 é problema no servidor mesmo. Abraço
Responder
Danilo (1) (0)
06/08/2015
Boa Noite Thiengo. No app que estou desenvolvendo, vou mostrar um recyclerview para cada situação envolvendo ocorrencias aeronauticas, investigacoes e relatorios de acidentes aeronauticos. Nesse caso, eu tenho que criar uma activity e um Fragment para cada tipo, correto? É possível eu chamá-los pelo navigationDrawer, mesmo os fragments estando em activities diferentes? Abraço.
Responder
Vinícius Thiengo (0) (0)
07/08/2015
Fala Danilo, blz?
Vc pode sim construir a APP da maneira que mencionou, porém recomendo utilizar somente uma Activity para trabalhar com os diferentes fragments, pois o resultado tende a ser um código mais tranquilo, uma transição entre fragments mais suave que entre Activities e uma APP mais leve. O NavigationDrawer pode sim ser utilizado para alternar entre os fragments.

Nos vídeos da série Material Design no Android (https://www.youtube.com/playlist?list=PLBA57K2L2RIKq7_IpaZRTL96CyLNDzF2F ) eu utilizo a troca de RecyclerViews por meio de fragments, somente para a área de detalhe que abro outra Activity. Abraço
Responder
Danilo (1) (0)
07/08/2015
Nesse exemplo da video aula, onde está sendo feita a troca de framgments? O switch case que tem no navigation drawer está comentado... Lá tem uma referencia ao layout com um rl_container para realizar o replace dos fragments. Porém, não o encontrei. Abraço.
Responder
Vinícius Thiengo (0) (0)
08/08/2015
Danilo, está no MainActivity, onde estão implementados o ViewPager e o SlidingTab, veja esse vídeo (http://www.thiengo.com.br/sliding-tabs-toolbar-material-design-android-parte-8 ). Abraço
Responder
01/08/2015
Hello Sir,
Thanks for the nice tutorial.I follow your android tutorial.

org.json.jsonexception value <br of type java.lang.string cannot be converted to jsonarray....I am getting this error on running this project.I searched stackoverflow and got this answer.  
http://stackoverflow.com/questions/17997514/value-br-of-type-java-lang-string-cannot-be-converted-to-jsonobject
But still i am not able to get the project run.Is there any problem with php files?
Responder
Vinícius Thiengo (0) (0)
02/08/2015
As you said in YouTube, it's working fine now. You made it! Have a good one.
Responder
Alessandro (1) (0)
31/07/2015
Muito bom Thiengo. Cara, estou olhando alguma forma da app me enviar email quando da algum problema no usuário. Achei o ACRA, mas nele é necessário que o usuário confirme o envio de email, gostaria um que somente avise o usuário e me envio o e-mail sem a necessidade de confirmação ou envio manual do usuário. tu conhece algum pra me indicar? Abraço e parabéns novamente.
Responder
Vinícius Thiengo (0) (0)
01/08/2015
Fala Alessandro, blz?
Uma maneira de fazer é pegando o email primário do user no device via Java code (http://stackoverflow.com/a/2175688/2578331 ) e então no background enviar esse email (junto a mensagem que será enviada) para um servidor Web que então de lá enviará o email, assim é como faço da APP do Blog, por exemplo, quando o user envia algum comentário ou uma mensagem de contato para mim. Tenta assim e ve se vai sem problemas. Abraço
Responder
Alessandro (1) (0)
03/08/2015
Blz Thiengo, obrigado pela dica, abraço
Responder
Danilo (1) (0)
30/07/2015
Boa noite Thiengo. Nesse caso, onde poderia fazer a verificação para ele carregar dados do sqlite em caso de conexão indisponível e fazer ele carregar do server remoto quando a conexão retornar e atualizar o bd com as informações? Abraço.
Responder
Vinícius Thiengo (0) (0)
31/07/2015
Fala Danilo, blz? Esse é o desafio do vídeo! Tem vários locais onde pode colocar o carregamento vindo do SQLite, o primeiro e mais óbvio é quando o no doAfter() é retornado um valor null, ali vc já pode abrir a busca no SQLite. Quando h;a conexão, no doAfter() ainda, vc deixa um código de inserção / atualização de dados no SQlite rodando, ele rodará no background em outra Thread. Isso vai permitir que o SQlite sempre esteja atualizado. Abraço
Responder
Danilo (1) (0)
01/08/2015
O volley consegue verificar se há conectividade efetivamente? Tipo, mesmo tendo sinal wi fi, mas não tendo conectividade por a conexão estar limitada o volley consegue identificar que não há conexão, apesar do sinal wi fi disponível? Ele verifica por timeout também? Abraço!
Responder
Vinícius Thiengo (0) (0)
01/08/2015
Fala Danilo, blz?
Se tiver o WiFi ou o 3G dando o mínimo sinal possível, nenhuma exception será gerada no Volley e então ele vai sim tentar a conexão, mesmo sendo uma conexão limitada (banda mt baixa), ai sim, se não tiver uma resposta dentro do time definido, o listener de error é que será chamado informando o motivo: timeout. Porém ele ainda vai utilizar a retentativa antes do timeout. Abraço
Responder
Mateus Pontes (1) (0)
28/07/2015
Fala a verdade, esse Go-ahead do site merece uma "API" para ser usada em outros sites, claro que contendo seus créditos :D

Muito bom o vídeo, como sempre.
[]'s
Responder
Vinícius Thiengo (2) (0)
28/07/2015
Essa da API foi boa... show de bola. Abraço
Responder
28/07/2015
Fala Thiengo ! Mais uma vez parabéns pela vídeo aula. Agora sim rsrsrs, vou terminar o meu projeto. No download do projeto percebi a falta da pasta package com os arquivos php, será que ela está em outro link para download ? Obrigado
Responder
Vinícius Thiengo (2) (0)
29/07/2015
Fala Luiz Rogerio, blz?
Tem sim, está em outro repo (https://github.com/viniciusthiengo/tc-material-design-web ), abraço
Responder
03/08/2015
Bom dia Thiengo, Estou tendo problemas com os arquivos em PHP, me parece que estou com o mesmo problema do colega Harsha B, que postou anteriormente. Segue mensagem "Value <br of type java.lang.String cannot be converted to JSONArray", Valeu
Responder
Vinícius Thiengo (1) (0)
03/08/2015
Fala Luiz Rogerio, blz?
Dê uma olhada aqui (https://github.com/viniciusthiengo/tc-material-design-web ) e veja se seguindo as dicas de instalação dos arquivos PHP em seu sistema local vc consegue fazer rodar. Mas provavelmente seu problema é o acesso ao seu servidor Localhost, como está utilizando a url de conexão que está em sua APP? O problema do Harsha B foi esse, ele então utilizou um servidor Web publico para realizar os testes e foi sem problemas. Está utilizando seu IPv4 no local de Localhost em sua URL? Instalou a base de dados e a tabela e então testou em um cliente MySQL para ver se estava tudo ok? Abraço
Responder
03/08/2015
Thiengo, na verdade eu já tenho uma estrutura de hospedagem com server Linux e PHP com Mysql no Locaweb. No projeto anterior com Volley rodou tranquilamente, vou repassar tudo novamente pra verificar onde possa ter errado. Muito obrigado !
Responder
13/08/2015
Fala Thiengo ! Blz, em relação ao problema que encontrei acima, criei uma conta gratuita de hospedagem no hostinger e o sistema funcionou sem problemas, mas finalmente identifiquei o meu problema de acesso com o Locaweb, na verdade era o PHP, que como hospedagem padrão Linux, roda o PHP 5.2, mas como seu projeto possui funções anonimas é necessário no mínimo o PHP 5.3, fica aí como sugestão para os colegas que venham a passar pelo mesmo problema. Uma outra dúvida seria, quando vc executa a fragmentcar que busca todos os carros e em seguida vc passa para outra fragment e volta novamente para a fragmentcar, ele começa a processar em loop e as vezes chega a travar o sistema. Será que não está relacionado com parâmetro ID que controla a busca de + carros ? ele provavelmente não está zerando em um novo select no webservice.
Valeu
Responder
Vinícius Thiengo (1) (0)
14/08/2015
Fala Luiz Rogerio, blz?
Vlw a contribuição sobre a versão do PHP, realmente estou com o PHP 5.6.2. Quanto ao problema do loop, nesse vídeo deixei algumas coisas passarem, inclusive a lógica de stop o carregamento de carros antigos, se não me engano isso foi corrigido nesse vídeo (http://www.thiengo.com.br/textinputlayout-e-envio-de-email-no-android-com-phpmailer-e-aws-ses ), logo no começo do vídeo comento sobre. Abraço
Responder