Skip to main content

Modificar o aplicativo nativo forcedroid

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:

  • Modificar o aplicativo modelo Android nativo para personalizar a tela de lista.
  • Tratar de uma resposta REST.
  • Excluir um registro do Salesforce por meio de solicitação REST.

Como personalizar a tela de lista

Como visto anteriormente, o aplicativo modelo Android nativo mostra uma lista de contatos ou contas da organização Salesforce do usuário. Ela é atualmente uma lista somente leitura criada com uma simples consulta SELECT SOQL processada pelas classes REST do Mobile SDK. Vamos adicionar capacidade de edição anexando uma ação de exclusão ao gesto de pressionamento longo. Quando o usuário toca e mantém o nome do contato pressionado no modo de exibição de lista, sua nova ação de exclusão tenta excluir o registro do Salesforce associado. Se a tentativa for bem-sucedida, seu aplicativo removerá permanentemente a linha do modo de exibição de lista de Contatos do aplicativo. Se a solicitação falhar, seu aplicativo indicará o motivo da falha ao usuário e restabelecerá a linha no modo de exibição de lista.

Sobre o ouvinte de clique longo

A implantação de um ouvinte de clique longo é simples. A escolha de como criar uma classe de ouvinte de clique longo, no entanto, é mais complicada. Se você explorar as várias opções de codificação, a primeira coisa a saber é que não se ouve cliques no nível do modo de exibição de lista. Em vez disso, você ouve no nível do item da lista. Por sorte, a implantação de ouvintes de itens de lista não é uma tarefa cansativa graças à interface OnItemLongClickListener do Android. Essa interface define um único ouvinte que é anexado ao modo de exibição de lista e responde a uma pressão de longa duração em um item da lista. Crie uma instância dessa classe implantando uma interface pública na sua classe de modo de exibição.

O desafio seguinte é descobrir qual classe de modo de exibição implanta o ouvinte de clique longo. Com objetos ListView, é possível fornecer um objeto de dados que oferece as informações exibidas na lista. O aplicativo modelo do Mobile SDK cria um objeto ArrayAdapter<String> para esse fim e o anexa a ListView.

listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new 
ArrayList<String>());
((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter);

Mas ArrayAdapter<String> é um objeto de dados, não um modo de exibição, certo? Sim, e mais. O ArrayAdapter no modo de exibição de lista de contatos cria um objeto AdapterView para cada item no conjunto de dados da lista. Como esses modos de exibição de adaptador representam os objetos de interesse, use a classe AdapterView para implantar OnItemLongClickListener. Em seguida, associe o objeto ouvinte ao objeto ListView, que recebe todas as notificações para seus filhos. Essa associação limita o OnItemLongClickListener para interagir somente com os itens no modo de exibição de lista do aplicativo de exemplo. Por fim, implante o comportamento de exclusão no método de interface único.

Um último detalhe: onde você coloca esse código do ouvinte de clique longo? O Mobile SDK dá os seguintes métodos de retorno de chamada de ponto de entrada:

public abstract void onResume(RestClient client);
@Override
protected void onCreate(Bundle savedInstanceState);
@Override 
public void onResume();

Podemos eliminar um: OnCreate (Bundle savedInstanceState). Esse método configura a atividade e trata os fluxos de autenticação antes da criação de uma instância dos modos de exibição. Os modos de exibição entram em cena no método onResume(). Esse método parece ser o mais indicado. O método onResume(RestClient client) é chamado pela superclasse no login para capturar o objeto RestClient autenticado. Vamos ignorá-lo. Assim, os resultados chegaram. Adicione seu código de ouvinte de clique longo a onResume().

Como implementar um ouvinte de clique longo básico

Bom, vamos começar a codificar. No Android Studio, abra a classe MainActivity e veja o método onResume().
  1. No Android Studio, abra seu arquivo MainActivity.java.
  2. Encontre o método onResume().
    @Override 
    public void onResume() {
        // Hide everything until we are logged in
        findViewById(R.id.root).setVisibility(View.INVISIBLE);
    
        // Create list adapter
        listAdapter = new ArrayAdapter<String>(this, 
            android.R.layout.simple_list_item_1, new ArrayList<String>());
        ((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter);			
        // ADD CODE HERE!
        super.onResume();
    }
    Vamos começar a codificar onde está marcado, depois que listAdapter foi definido para ListView, mas antes da chamada super.onResume().
  3. Declare e atribua uma variável ListView de conveniência que faça referência ao modo de exibição de lista de Contatos. Use o método Activity.findViewById() para procurar o recurso de modo de exibição de lista.
    ((ListView) findViewById(R.id.contacts_list)).setAdapter(listAdapter);	
    ListView lv = ((ListView) findViewById(R.id.contacts_list));
    super.onResume();
  4. Usando a variável lv, chame o método AdapterView.setOnItemLongClickListener() para definir um ouvinte de eventos de clique longo. Para o parâmetro listener, crie uma instância de um stub em linha da interface AdapterView.OnItemLongClickListener.
    ListView lv = ((ListView) findViewById(R.id.contacts_list));
    lv.setOnItemLongClickListener(
        new AdapterView.OnItemLongClickListener() {
            @Override
    	 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                return false;
            }
        });
    
    Observe que o Android Studio até inclui o método de interface virtual único para você. Isso é útil!
  5. (Opcional) Se você receber um erro sobre a ausência de uma importação, adicione import android.widget.AdapterView às importações de classe.
  6. No corpo de AdapterView.OnItemLongClickListener, substitua a instrução return clichê pelo código que apresenta uma mensagem de notificação de confirmação.
    ListView lv = ((ListView) findViewById(R.id.contacts_list));
    lv.setOnItemLongClickListener (new AdapterView.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view,
            int position, long id) {
            Toast.makeText(getApplicationContext(),
                "Long press received", Toast.LENGTH_SHORT).show();
            return true;
            }
    });
  7. Crie o aplicativo e execute-o.
  8. Quando estiver conectado ao aplicativo, clique em Fetch Contacts (Buscar contatos).
  9. Toque em uma entrada na lista de Contatos e mantenha pressionada por alguns segundos. Se tudo correr bem, aparecerá uma mensagem de notificação.

Como adicionar solicitações REST do Mobile SDK

Agora, já estamos quase prontos para adicionar elementos do Mobile SDK. No método onItemLongClick(), crie uma solicitação REST para excluir o registro do Salesforce associado à linha que foi tocada. Envie essa solicitação ao Salesforce. Antes de se aprofundar no código, examine estes pontos importantes.

Como obter uma instância RestClient

Lembre-se de que você nunca cria objetos RestClient diretamente. O Mobile SDK cria um para você e o retorna para MainActivity pelo método onResume(RestClient client). Essa instância RestClient é autenticada com o token de acesso do usuário atual. Para seu uso, o método onResume(RestClient client) atribui essa instância à variável de classe client.

Como criar uma solicitação REST

Para criar a solicitação REST deste exercício, chame o método de fábrica RestRequest para excluir um registro:

public static RestRequest getRequestForDelete(String apiVersion, String objectType, String objectId);

De onde você obtém os valores do argumento? Verifique esta tabela.

Parâmetro Valor
apiVersion Definido nos recursos do seu aplicativo: getString(R.string.api_version)
objectType “Contact” (Contato) (embutido)
objectId ??
O parâmetro objectId é um pouco chatinho. Ele exige um valor do Salesforce que seu aplicativo forcedroid bruto não reconhece. Por que você não o tem e o que pode fazer para obtê-lo? As respostas são simples:
  • Você não tem as IDs porque as solicitações REST originais, as que preencheram as listas, não as solicitam.
  • É possível obter a ID alterando as solicitações REST.

Como ajustar a solicitação SOQL do aplicativo modelo

Sua classe MainActivity emite duas solicitações REST: uma para contatos e outra para contas. Cada solicitação contém uma instrução SOQL. Não vamos usar contas. Vamos atualizar a solicitação de registro de Contatos para retornar valores de ID.

  1. No Android Studio, abra o arquivo MainActivity.java.
  2. Encontre o método onFetchContactsClick().
    public void onFetchContactsClick(View v) throws UnsupportedEncodingException
    {
       sendRequest("SELECT Name FROM Contact");
    }
    
  3. Altere a consulta SOQL para selecionar os campos Name e Id. Use a grafia em letras concatenadas (Camel case) do nome do campo da ID. Os nomes de campo diferenciam maiúsculas de minúsculas.
    public void onFetchContactsClick(View v) throws UnsupportedEncodingException
    {
       sendRequest("SELECT Name, Id FROM Contact");
    }

Agora que você está preparado para receber valores de ID na resposta REST, onde é o melhor lugar para guardá-los? O aplicativo modelo simplesmente copia o valor Name de cada registro em uma linha do modo de exibição de lista; ele não armazena os valores de ID em cache. Para permitir pesquisa em seu manipulador de pressionamento longo, armazene as IDs no escopo da classe.

Como adaptar o método sendRequest() do aplicativo modelo

Desça até o método sendRequest(String soql), que é onde a resposta fetch chega. Esse método mostra claramente como o mecanismo REST do Mobile SDK funciona. Vamos analisá-lo rapidamente. Primeiro, o método chama um método de fábrica RestRequest que define uma solicitação REST para determinada consulta SOQL:
RestRequest restRequest = RestRequest.getRequestForQuery(
    ApiVersionStrings.getVersionNumber(this), soql);

Em seguida, ele envia o novo objeto RestRequest para o Salesforce na chamada client.sendAsync().

client.sendAsync(restRequest, new AsyncRequestCallback() {
    @Override
    public void onSuccess(RestRequest request, final RestResponse result) {
        result.consumeQuietly(); // consume before going back to main thread
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    listAdapter.clear();
                    JSONArray records = result.asJSONObject().getJSONArray("records");
                    for (int i = 0; i < records.length(); i++) {
                        listAdapter.add(records.getJSONObject(i).getString("Name"));
                    }
                } catch (Exception e) {
                    onError(e);
                }
            }
        });
    }
 
    @Override
    public void onError(final Exception exception) {
        runOnUiThread(new Runnable() {
        @Override
            public void run() {
                Toast.makeText(MainActivity.this,
                    MainActivity.this.getString(
                        R.string.sf__generic_error, 
                        exception.toString()),
                    Toast.LENGTH_LONG).show();
            }
        });
    }
});

Além do objeto RestRequest, a chamada sendAsync() exige uma implantação da interface AsyncRequestCallback do Mobile SDK. O método onSuccess() dessa interface recebe a resposta REST de forma assíncrona por um retorno de chamada. Sua implantação de AsyncRequestCallback padrão trata somente as consultas SOQL definidas no aplicativo forcedroid.

Esse método onSuccess() já faz o que é necessário. Ele contém código que extrai os registros retornados pela resposta REST e os atribui à variável records local. Vamos mover essa variável para o escopo da classe redeclarando-a fora do corpo do método.
  1. Quase no topo da definição da classe MainActivity, declare JSONArray records como uma variável privada nas declarações de variável de classe existentes:
    public class MainActivity extends SalesforceActivity {
        private RestClient client;
        private ArrayAdapter<String> listAdapter;
        private JSONArray records;
       ….
  2. No método onSuccess(), remova a declaração de tipo da variável records:
    public void onSuccess(RestRequest request, final RestResponse result) {
        result.consumeQuietly(); // consume before going back to main thread
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    listAdapter.clear();
                    records = result.asJSONObject().getJSONArray("records");
                    for (int i = 0; i < records.length(); i++) {
                        listAdapter.add(records.getJSONObject(i).getString("Name"));
                    }
                } catch (Exception e) {
                    onError(e);
                }
            }
        });
    }

Como concluir o método onItemLongClick()

Agora, você está pronto para concluir o método onItemLongClick(). O algoritmo base é o seguinte:

  1. Obtenha um objeto “solicitar exclusão” chamando um método de fábrica Mobile SDK RestRequest apropriado. Todos os métodos RestRequest criam exceções e, portanto, coloque a chamada em um bloco try...catch.
  2. Envie o objeto “solicitar exclusão” para o Salesforce usando o objeto RestClient gerado.
  3. Trate do resultado REST nos métodos de retorno de chamada.

Obter um objeto RestRequest

  1. Volte para o método onItemLongClick() no método onResume().
  2. Depois da chamada Toast.makeText(), declare um objeto RestRequest local chamado restRequest. Inicialize-o como null.
    RestRequest restRequest = null;
  3. Adicione uma construção try...catch vazia.
    RestRequest restRequest = null;
    try {
    
    } catch (Exception e) {
     
    }
  4. No bloco try, chame um método de fábrica que obtém um objeto de solicitação REST para uma operação de exclusão. DICA: Use o método estático RestRequest.getRequestForDelete().
    RestRequest restRequest = null;
    try {
        restRequest = RestRequest.getRequestForDelete(
                   // arguments?
                );
    } catch (Exception e) {
     
    }
  5. Para o primeiro parâmetro, recupere a versão da API Salesforce especificada nos recursos de Mobile SDK do seu projeto.
    RestRequest restRequest = null;
    try {
         restRequest = RestRequest.getRequestForDelete(
            getString(R.string.api_version), //...);
    } catch (Exception e) {
    
    }
  6. No parâmetro objectType, especifique “Contact” (Contato).
    RestRequest restRequest = null;
    try {
        restRequest = RestRequest.getRequestForDelete(
            getString(R.string.api_version), "Contact", //...);
    } catch (Exception e) {
    
    }
  7. Transmita RestRequest.getRequestForDelete() – a ID da entrada na matriz de registros que corresponde à posição do modo de exibição de lista atual. Veja como recuperar a ID:
    RestRequest restRequest = null;
    try {
        restRequest = RestRequest.getRequestForDelete(
            getString(R.string.api_version), "Contact",
            records.getJSONObject(position).getString("Id"));
        // Send the request
        // ...
    } catch (Exception e) {
    
    }
  8. No bloco catch, chame printStackTrace() no argumento Exception.
    RestRequest restRequest = null;
    try {
        restRequest = RestRequest.getRequestForDelete(
            getString(R.string.api_version), "Contact",
            records.getJSONObject(position).getString("Id"));
        // Send the request
        // ...
    } catch (Exception e) {
        e.printStackTrace();
    }

Depois de obter o objeto “solicitar exclusão”, envie-o ao Salesforce e trate do resultado nos métodos de retorno de chamada.

Adicionar o método RestClient.sendAsync()

Estamos quase acabando! A última peça do quebra-cabeça é enviar a solicitação usando o método RestClient.sendAsync(). Esse método exige a implantação da interface AsyncRequestCallback. Como você sabe, o Mobile SDK envia respostas REST para seus métodos AsyncRequestCallback.

  1. Em onItemLongClick(), depois da chamada getRequestForDelete(), copie e cole o código de RestClient.sendAsync() do método sendRequest().
  2. Remova o código interno da ramificação try do método onSuccess(). Não remova a ramificação catch, já que ela apenas se reporta a um manipulador de erro.
  3. Mantenha a implantação de substituição onError(); ela é genérica o suficiente para funcionar com qualquer resposta do Salesforce.

Eis a chamada com exclusão para RestClient.sendAsync().

restRequest = RestRequest.getRequestForDelete(
        getString(R.string.api_version), "Contact",
        records.getJSONObject(position).getString("Id"));
client.sendAsync(restRequest, new AsyncRequestCallback() {
    @Override
    public void onSuccess(RestRequest request, final RestResponse result) {
        result.consumeQuietly();
        runOnUiThread(new Runnable() { 
            @Override
            public void run() {
                // Network component doesn’t report app layer status.
                // Use Mobile SDK RestResponse.isSuccess() method to check
                // whether the REST request itself succeeded.
                if (result.isSuccess()) {
                    try {

                    } catch (Exception e) {
                        onError(e);
                    }
                }
            }
        });
    }

    @Override
    public void onError(final Exception exception) {
        runOnUiThread(new Runnable() {
        @Override
            public void run() {
                Toast.makeText(MainActivity.this,
                        MainActivity.this.getString(R.string.sf__generic_error, exception.toString()),
                        Toast.LENGTH_LONG).show();
            }
        });
    }
});

Implementar o método de retorno de chamada onSuccess()

No método onSuccess() de AsyncRequestCallback(), faça o seguinte:
  1. Verifique se a operação de exclusão foi bem-sucedida testando o status HTTP. Essa verificação é necessária porque o componente de rede subjacente relata somente falhas da camada de transporte, não falhas de solicitação REST.
  2. Se a operação foi bem-sucedida, remova o item na posição especificada do modo de exibição de lista.
  3. Publique uma mensagem de sucesso.
Use a variável da classe listAdapter para remover a linha. Chame ArrayAdapter.remove(T object), usando o valor de posição transmitido ao método onItemLongClick(), para obter o objeto. Por exemplo:
listAdapter.remove(listAdapter.getItem(position));
Se você adicionar esse código, terá um problema de escopo. Como você está trabalhando em um contexto de implantação de interface, não pode usar a variável position local do contexto onItemLongClick(). Em vez disso, você pode adicionar uma variável de classe e atribuir a variável de posição a ela.
  1. Na parte superior da classe, declare e inicialize uma variável de classe privada chamada pos do tipo int.
    public class MainActivity extends SalesforceActivity {
    
        private RestClient client;
        private ArrayAdapter<String> listAdapter;
        private JSONArray records;
        private int pos = -1;
  2. Na primeira linha do método onItemLongClick(), capture o valor position:
    public boolean onItemLongClick(AdapterView<?> parent, View view,
    	int position, long id) {
    	pos = position;
    	...
  3. No método onSuccess() da sua implementação de AsyncRequestCallback, desça algumas linhas até o bloco if incluído.
    if (result.isSuccess()) {
        try {
    
        } catch (Exception e) {
            onError(e);
        }
    }
  4. Se result.isSuccess() for true, remova a linha chamando o método listAdapter.remove(). Use pos em vez de position para remover a linha:
    if (result.isSuccess()) {
        listAdapter.remove(listAdapter.getItem(pos));
    
    }
  5. Depois de remover um item da lista, chame sendRequest(request) para atualizar a lista reordenada e mantenha-a sincronizada com sua matriz records local:
    if (result.isSuccess()) {
        listAdapter.remove(listAdapter.getItem(pos));
        sendRequest(”SELECT Name, Id FROM Contact”);
    
    }
  6. Por fim, publique uma caixa de alerta mostrando uma mensagem de sucesso. Do contrário, relate a mensagem de erro.
    if (result.isSuccess()) {
        listAdapter.remove(listAdapter.getItem(pos));
        sendRequest(”SELECT Name, Id FROM Contact”);
        AlertDialog.Builder b = new AlertDialog.Builder(findViewById(R.id.contacts_list).getContext());
        b.setMessage("Record successfully deleted!");
        b.setCancelable(true);
        AlertDialog a = b.create();
        a.show();
    } else {
       Toast.makeText(MainActivity.this,
             MainActivity.this.getString(R.string.sf__generic_error, result.toString()),
             Toast.LENGTH_LONG).show();
    }

Aqui está o método onItemLongClick() concluído.

@Override
public boolean onItemLongClick(AdapterView<?> parent, 
        View view, int position, long id) {
    pos = position;

    Toast.makeText(getApplicationContext(),
        "Long press detected", Toast.LENGTH_SHORT).show();
    RestRequest restRequest = null;
    try {
       RestRequest request = RestRequest.getRequestForDelete(
           getString(R.string.api_version), "Contact", 
               records.getJSONObject(position).getString("Id"));
       client.sendAsync(request, new AsyncRequestCallback() {
            @Override
            public void onSuccess(RestRequest request, final RestResponse result) {
                result.consumeQuietly();
                runOnUiThread(new Runnable() { 
                    @Override
                    public void run() {
                        try {
                            // Network component doesn’t report app layer status.
                            // Use Mobile SDK RestResponse.isSuccess() method to check
                            // whether the REST request itself succeeded. 
                            if (result.isSuccess()) {                                        
                                listAdapter.remove(listAdapter.getItem(pos));
                                sendRequest(”SELECT Name, Id FROM Contact”);
                                AlertDialog.Builder b = 
                                    new AlertDialog.Builder(findViewById
                                        (R.id.contacts_list).getContext());
                                b.setMessage("Record successfully deleted!");
                                b.setCancelable(true);
                                AlertDialog a = b.create();
                                a.show();
                            } else {
                                Toast.makeText(MainActivity.this,
                                    MainActivity.this.getString(
                                        R.string.sf__generic_error, 
                                        result.toString()),
                                    Toast.LENGTH_LONG).show(); 
                            }   
                        } catch (Exception e) {
                            onError(e);
                        }
                    }});
                }

                @Override
                public void onError(final Exception exception) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {		          
                            Toast.makeText(MainActivity.this,
                                MainActivity.this.getString(
                                    R.string.sf__generic_error, 
                                    exception.toString()),
                                Toast.LENGTH_LONG).show();
                        }
                    });
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    return true;
}

Limpeza final e tempo de execução!

Uma parte da limpeza final é a remoção do botão Fetch Accounts (Buscar contas). Como o modo de exibição de lista é compartilhado entre Fetch Contacts (Buscar contatos) e Fetch Accounts (Buscar contas), o manipulador de pressionamento longo se aplica igualmente a ambos. No entanto, esse manipulador é inútil para contas porque a ID usada para excluir o registro só se aplica a um contato. Como alternativa – e “ponto extra” – você pode aplicar o que aprendeu e adaptar o manipulador de pressionamento longo para excluir tanto contatos quanto contas. Para este tutorial, no entanto, vamos remover o código relativo a contas.

Exclua os seguintes itens dos arquivos indicados:

Arquivo Ação
MainActivity.java Excluir o método onFetchAccountsClick(View v).
res/layout/Main.xml Executar uma das seguintes opções:
  • No layout gráfico, excluir o botão “Fetch Accounts” (Buscar contas).
  • No modo de exibição XML, excluir o nó <Button> cuja ID é "@+id/fetch_accounts".
res/values/strings.xml Executar uma das seguintes opções:
  • Na guia Resources (Recursos): Selecionar “fetch_accounts_button (String)” e clicar em Remove (Remover).
  • No modo de exibição XML: Excluir o nó <string> chamado “fetch_accounts_button”.

Finalmente, seu aplicativo foi concluído e está pronto para ser executado!

  1. No Android Studio, clique em Run (Executar) | Run ‘app’ (Executar ‘aplicativo’).
  2. Selecione um emulador ou dispositivo conectado compatível com Mobile SDK.
  3. Quando o aplicativo estiver em execução, entre na sua organização do Salesforce e clique em Fetch Contacts (Buscar contatos) para ver a lista. Mantenha um item pressionado na lista até ver uma notificação confirmando o pressionamento longo.

Observe que você recebe uma resposta de erro quando tenta excluir um contato padrão no banco de dados da Developer Edition. Esses erros ocorrem porque cada contato que vem pré-empacotado em uma organização Developer Edition é pai de outros registros. Para se preparar para o teste, faça login na sua organização Developer Edition e crie um ou mais contatos de teste que não tenham outros registros. Se a exclusão for bem-sucedida, uma mensagem aparecerá para indicar que o registro foi excluído.

Continue a aprender de graça!
Inscreva-se em uma conta para continuar.
O que você ganha com isso?
  • Receba recomendações personalizadas para suas metas de carreira
  • Pratique suas habilidades com desafios práticos e testes
  • Monitore e compartilhe seu progresso com os empregadores
  • Conecte-se a orientação e oportunidades de carreira