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 = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList()) findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
Mas ArrayAdapter é 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 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().
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().
- No Android Studio, abra seu arquivo MainActivity.kt.
- Encontre o método (method).
override fun onResume() {
// Hide everything until we are logged in
findViewById<ViewGroup>(root).visibility = View.INVISIBLE
// Create list adapter
listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList())
findViewById<ListView>(R.id.contacts_list).adapter = 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().
- 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.
findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
val listView = findViewById<ListView>(R.id.contacts_list) super.onResume()
- Usando a variável listView, chame a função lambda 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.
val listView = findViewById<ListView>(R.id.contacts_list)
listView.setOnItemLongClickListener { parent, view, position, id -> 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 o statement de retorno padrão por um código que exiba uma mensagem de confirmação de notificação.
val listView = findViewById<ListView>(R.id.contacts_list)
listView.setOnItemLongClickListener { parent, view, position, id ->
Toast.makeText(
applicationContext,
"Long press received",
Toast.LENGTH_SHORT,
).show()
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.
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.
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.
Criar uma solicitação REST
Para criar a solicitação REST para este 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 | ApiVersionStrings.getVersionNumber(Context context) |
objectType |
|
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.
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.kt.
- Encontre o método onFetchContactsClick().
fun onFetchContactsClick(v: View) {
sendRequest("SELECT Name FROM Contact")
}- Altere a consulta SOQL para selecionar os campos Name (Nome) 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.
fun onFetchContactsClick(v: View) {
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.
Adaptar o método sendRequest() do aplicativo modelo
Role para baixo até ao 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:
val 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, object : AsyncRequestCallback {
override fun onSuccess(request: RestRequest, result: RestResponse) {
result.consumeQuietly() // consume before going back to main thread
runOnUiThread {
try {
listAdapter!!.clear()
val records = result.asJSONObject().getJSONArray("records")
for (i in 0..<records.length()) {
listAdapter!!.add(records.getJSONObject(i).getString("Name"))
}
} catch (e: Exception) {
onError(e)
}
}
}
override fun onError(exception: Exception) {
runOnUiThread {
Toast.makeText(this@MainActivity,
this@MainActivity.getString(R.string.sf__generic_error),
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.
- 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:
class MainActivity : SalesforceActivity() {
private var client: RestClient? = null
private var listAdapter: ArrayAdapter<String>? = null
private lateinit var records: JSONArray
…- No método onSuccess(), remova a declaração de tipo da variável records:
override fun onSuccess(request: RestRequest, result: RestResponse) {
result.consumeQuietly() // consume before going back to main thread
runOnUiThread {
try {
listAdapter!!.clear()
records = result.asJSONObject().getJSONArray("records")
for (i in 0..<records.length()) {
listAdapter!!.add(records.getJSONObject(i).getString("Name"))
}
} catch (e: Exception) {
onError(e)
}
}
}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 setOnItemLongClickListener no método onResume().
- Depois da chamada Toast.makeText(), declare um objeto RestRequest local chamado restRequest.
var restRequest: RestRequest
- Adicione uma construção try...catch vazia.
try {
} catch (e: Exception) {
}- 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().
try {
restRequest = RestRequest.getRequestForDelete(
// arguments?
)
} catch (e: Exception) {
}- Para o primeiro parâmetro, recupere a versão da API Salesforce especificada nos recursos de Mobile SDK do seu projeto.
try {
restRequest = RestRequest.getRequestForDelete(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
// ..
)
} catch (e: Exception) {
}- No parâmetro objectType, especifique
"Contact"(Contato).
try {
restRequest = RestRequest.getRequestForDelete(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
/* objectType = */ "Contact",
// ..
)
} catch (e: Exception) {
}- 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:
try {
restRequest = RestRequest.getRequestForDelete(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
/* objectType = */ "Contact",
/* objectId = */ records.getJSONObject(position).getString("Id"),
)
} catch (e: Exception) {
}- No bloco catch, chame printStackTrace() no argumento Exception.
try {
restRequest = RestRequest.getRequestForDelete(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
/* objectType = */ "Contact",
/* objectId = */ records.getJSONObject(position).getString("Id"),
)
// Send the request
// ...
} catch (e: Exception) {
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(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
/* objectType = */ "Contact",
/* objectId = */ records.getJSONObject(position).getString("Id"),
)
client!!.sendAsync(restRequest, object : AsyncRequestCallback {
override fun onSuccess(request: RestRequest?, result: RestResponse) {
result.consumeQuietly()
runOnUiThread {
// The 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 (e: Exception) {
onError(e)
}
}
}
}
override fun onError(exception: Exception) {
runOnUiThread {
Toast.makeText(this@MainActivity,
getString(R.string.sf__generic_error),
Toast.LENGTH_LONG,
).show()
}
}
})Implementar o método de retorno de chamada onSuccess()
No método onSuccess() de AsyncRequestCallback(), faça o seguinte:
- 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.
Use a variável da classe listAdapter para remover a linha. Você pode chamar ArrayAdapter.remove(T object), usando o valor de posição transmitido ao método onItemLongClick(), para obter o objeto. Por exemplo:
listAdapter?.run {
remove(getItem(position))
}- 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) {
try {
listAdapter?.run {
remove(getItem(position))
}
sendRequest("SELECT Name, Id FROM Contact")
} catch (e: Exception) {
onError(e)
}
}- Em seguida, publique uma caixa de alerta mostrando uma mensagem de sucesso. Do contrário, relate a mensagem de erro.
if (result.isSuccess) {
try {
listAdapter?.run {
remove(getItem(position))
}
sendRequest("SELECT Name, Id FROM Contact")
AlertDialog.Builder(this@MainActivity)
.setMessage("Record successfully deleted!")
.setCancelable(true)
.create()
.show()
} catch (e: Exception) {
onError(e)
}
} else {
Toast.makeText(this@MainActivity,
getString(R.string.sf__generic_error),
Toast.LENGTH_LONG,
).show()
}Aqui está a função setOnItemLongClickListener() concluída.
override fun onResume() {
// Hide everything until we are logged in
findViewById<ViewGroup>(root).visibility = View.INVISIBLE
// Create list adapter
listAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, ArrayList())
findViewById<ListView>(R.id.contacts_list).adapter = listAdapter
val listView = findViewById<ListView>(R.id.contacts_list)
listView.setOnItemLongClickListener { parent, view, position, id ->
Toast.makeText(
applicationContext,
"Long press received",
Toast.LENGTH_SHORT,
).show()
var restRequest: RestRequest
try {
restRequest = RestRequest.getRequestForDelete(
/* apiVersion = */ ApiVersionStrings.getVersionNumber(this),
/* objectType = */ "Contact",
/* objectId = */ records.getJSONObject(position).getString("Id"),
)
client!!.sendAsync(restRequest, object : AsyncRequestCallback {
override fun onSuccess(request: RestRequest?, result: RestResponse) {
result.consumeQuietly()
runOnUiThread {
// The 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 {
listAdapter?.run {
remove(getItem(position))
}
sendRequest("SELECT Name, Id FROM Contact")
AlertDialog.Builder(this@MainActivity)
.setMessage("Record successfully deleted!")
.create()
.show()
} catch (e: Exception) {
onError(e)
}
} else {
Toast.makeText(this@MainActivity,
getString(R.string.sf__generic_error),
Toast.LENGTH_LONG,
).show()
}
}
}
override fun onError(exception: java.lang.Exception) {
runOnUiThread {
Toast.makeText(
this@MainActivity,
this@MainActivity.getString(R.string.sf__generic_error),
Toast.LENGTH_LONG
).show()
}
}
})
} catch (e: Exception) {
e.printStackTrace()
}
true
}
super.onResume()
}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.kt | Exclua a função onFetchAccountsClick(v: View). |
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 Run (Executar) | Run ‘app’ (Executar ‘aplicativo’).
- 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.
Recursos
- Guia para desenvolvedores do Salesforce: Como usar APIs REST
- Desenvolvedores do Salesforce: RestClient Class
- Desenvolvedores do Salesforce: RestRequest Class