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
- No Android Studio, abra seu arquivo MainActivity.java.
- Encontre o método onResume().Vamos começar a codificar onde está marcado, depois que listAdapter foi definido para ListView, mas antes da chamada super.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(); }
- 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();
- 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.Observe que o Android Studio até inclui o método de interface virtual único para você. Isso é útil!
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; } });
- (Opcional) Se você receber um erro sobre a ausência de uma importação, adicione import android.widget.AdapterView às importações de classe.
- 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; } });
- Crie o aplicativo e execute-o.
- Quando estiver conectado ao aplicativo, clique em Fetch Contacts (Buscar contatos).
- 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 | ?? |
- 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.
- No Android Studio, abra o arquivo MainActivity.java.
- Encontre o método onFetchContactsClick().
public void onFetchContactsClick(View v) throws UnsupportedEncodingException { sendRequest("SELECT Name FROM Contact"); }
- 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
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.
- 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; ….
- 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:
- 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.
- Envie o objeto “solicitar exclusão” para o Salesforce usando o objeto RestClient gerado.
- Trate do resultado REST nos métodos de retorno de chamada.
Obter um objeto RestRequest
- Volte para o método onItemLongClick() no método onResume().
- Depois da chamada Toast.makeText(), declare um objeto RestRequest local chamado restRequest. Inicialize-o como null.
RestRequest restRequest = null;
- Adicione uma construção try...catch vazia.
RestRequest restRequest = null; try { } catch (Exception e) { }
- 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) { }
- 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) { }
- No parâmetro objectType, especifique “Contact” (Contato).
RestRequest restRequest = null; try { restRequest = RestRequest.getRequestForDelete( getString(R.string.api_version), "Contact", //...); } catch (Exception e) { }
- 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) { }
- 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.
- Em onItemLongClick(), depois da chamada getRequestForDelete(), copie e cole o código de RestClient.sendAsync() do método sendRequest().
- 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.
- 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()
- 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.
- Se a operação foi bem-sucedida, remova o item na posição especificada do modo de exibição de lista.
- Publique uma mensagem de sucesso.
listAdapter.remove(listAdapter.getItem(position));
- 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;
- Na primeira linha do método onItemLongClick(), capture o valor position:
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { pos = position; ...
- 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); } }
- 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)); }
- 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”); }
- 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:
|
res/values/strings.xml | Executar uma das seguintes opções:
|
Finalmente, seu aplicativo foi concluído e está pronto para ser executado!
- No Android Studio, clique em .
- Selecione um emulador ou dispositivo conectado compatível com Mobile SDK.
- 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.