Partilhar via


Como usar o SDK de Aplicativos Móveis do Azure para Android

Este guia mostra como usar o SDK do cliente Android para aplicativos móveis para implementar cenários comuns, como:

  • Consulta de dados (inserção, atualização e exclusão).
  • Autenticação.
  • Tratamento de erros.
  • Personalização do cliente.

Este guia se concentra no SDK do Android do lado do cliente. Para saber mais sobre os SDKs do lado do servidor para aplicativos móveis, consulte Trabalhar com o SDK de back-end do .NET ou Como usar o SDK de back-end Node.js.

Documentação de Referência

Você pode encontrar a referência da API Javadocs para a biblioteca de cliente Android no GitHub.

Plataformas suportadas

O SDK de Aplicativos Móveis do Azure para Android dá suporte aos níveis de API 19 a 24 (KitKat a Nougat) para fatores forma de telefone e tablet. A autenticação, em particular, utiliza uma abordagem comum de estrutura da Web para coletar credenciais. A autenticação de fluxo de servidor não funciona com dispositivos de formato pequeno, como relógios.

Configuração e pré-requisitos

Conclua o tutorial de início rápido de aplicativos móveis. Esta tarefa garante que todos os pré-requisitos para desenvolver Aplicativos Móveis do Azure foram atendidos. O Guia de início rápido também ajuda você a configurar sua conta e criar seu primeiro back-end de aplicativo móvel.

Se você decidir não concluir o tutorial de início rápido, conclua as seguintes tarefas:

Atualizar o arquivo de compilação do Gradle

Altere ambos os arquivos build.gradle :

  1. Adicione este código ao arquivo build.gradle de nível de projeto:

    buildscript {
        repositories {
            jcenter()
            google()
        }
    }
    
    allprojects {
        repositories {
            jcenter()
            google()
        }
    }
    
  2. Adicione este código ao arquivo build.gradle no nível do aplicativo do módulo dentro da tag dependencies:

    implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
    

    Atualmente, a versão mais recente é 3.4.0. As versões suportadas estão listadas no bintray.

Ativar permissão de Internet

Para acessar o Azure, seu aplicativo deve ter a permissão INTERNET habilitada. Se ainda não estiver habilitado, adicione a seguinte linha de código ao seu arquivo AndroidManifest.xml :

<uses-permission android:name="android.permission.INTERNET" />

Criar uma conexão de cliente

As Aplicações Móveis do Azure fornecem quatro funções à sua aplicação móvel:

  • Acesso a Dados e Sincronização Offline com um Serviço de Aplicações Móveis do Azure.
  • Chame APIs personalizadas escritas com o SDK do Servidor de Aplicativos Móveis do Azure.
  • Autenticação com Autenticação e Autorização do Serviço de Aplicativo do Azure.
  • Registro de notificação por push com Hubs de notificação.

Cada uma dessas funções requer primeiro que você crie um MobileServiceClient objeto. Apenas um MobileServiceClient objeto deve ser criado dentro do seu cliente móvel (ou seja, deve ser um padrão Singleton). Para criar um MobileServiceClient objeto:

MobileServiceClient mClient = new MobileServiceClient(
    "<MobileAppUrl>",       // Replace with the Site URL
    this);                  // Your application Context

O <MobileAppUrl> é uma cadeia de caracteres ou um objeto de URL que aponta para o back-end móvel. Se você estiver usando o Serviço de Aplicativo do Azure para hospedar seu back-end móvel, certifique-se de usar a versão segura https:// da URL.

O cliente também requer acesso à Atividade ou Contexto - o this parâmetro no exemplo. A construção MobileServiceClient deve acontecer dentro do onCreate() método da atividade referenciada AndroidManifest.xml no arquivo.

Como prática recomendada, você deve abstrair a comunicação do servidor em sua própria classe (padrão singleton). Nesse caso, você deve passar a Activity dentro do construtor para configurar adequadamente o serviço. Por exemplo:

package com.example.appname.services;

import android.content.Context;
import com.microsoft.windowsazure.mobileservices.*;

public class AzureServiceAdapter {
    private String mMobileBackendUrl = "https://myappname.azurewebsites.net";
    private Context mContext;
    private MobileServiceClient mClient;
    private static AzureServiceAdapter mInstance = null;

    private AzureServiceAdapter(Context context) {
        mContext = context;
        mClient = new MobileServiceClient(mMobileBackendUrl, mContext);
    }

    public static void Initialize(Context context) {
        if (mInstance == null) {
            mInstance = new AzureServiceAdapter(context);
        } else {
            throw new IllegalStateException("AzureServiceAdapter is already initialized");
        }
    }

    public static AzureServiceAdapter getInstance() {
        if (mInstance == null) {
            throw new IllegalStateException("AzureServiceAdapter is not initialized");
        }
        return mInstance;
    }

    public MobileServiceClient getClient() {
        return mClient;
    }

    // Place any public methods that operate on mClient here.
}

Agora você pode chamar AzureServiceAdapter.Initialize(this); o onCreate() método da sua atividade principal. Quaisquer outros métodos que necessitem de acesso ao cliente usam AzureServiceAdapter.getInstance(); para obter uma referência ao adaptador de serviço.

Operações de Dados

O núcleo do SDK de Aplicativos Móveis do Azure é fornecer acesso aos dados armazenados no SQL Azure no back-end do Aplicativo Móvel. Você pode acessar esses dados usando classes fortemente tipadas (preferencial) ou consultas não tipadas (não recomendadas). A maior parte desta seção trata do uso de classes fortemente tipadas.

Definir classes de dados do cliente

Para acessar dados de tabelas do SQL Azure, defina classes de dados de cliente que correspondam às tabelas no back-end do Aplicativo Móvel. Os exemplos neste tópico pressupõem uma tabela chamada MyDataTable, que tem as seguintes colunas:

  • id
  • texto
  • completo

O objeto do lado do cliente digitado correspondente reside em um arquivo chamado MyDataTable.java:

public class ToDoItem {
    private String id;
    private String text;
    private Boolean complete;
}

Adicione métodos getter e setter para cada campo adicionado. Se sua tabela do SQL Azure contiver mais colunas, você adicionará os campos correspondentes a essa classe. Por exemplo, se o DTO (objeto de transferência de dados) tiver uma coluna Prioridade inteira, você poderá adicionar esse campo, juntamente com seus métodos getter e setter:

private Integer priority;

/**
* Returns the item priority
*/
public Integer getPriority() {
    return mPriority;
}

/**
* Sets the item priority
*
* @param priority
*            priority to set
*/
public final void setPriority(Integer priority) {
    mPriority = priority;
}

Para saber como criar tabelas adicionais em seu back-end de Aplicativos Móveis, consulte Como definir um controlador de tabela (back-end .NET) ou Definir tabelas usando um esquema dinâmico (back-end Node.js).

Uma tabela de back-end de Aplicativos Móveis do Azure define cinco campos especiais, quatro dos quais estão disponíveis para clientes:

  • String id: O ID globalmente exclusivo para o registro. Como prática recomendada, torne o id a representação String de um objeto UUID .
  • DateTimeOffset updatedAt: A data/hora da última atualização. O campo updatedAt é definido pelo servidor e nunca deve ser definido pelo código do cliente.
  • DateTimeOffset createdAt: A data/hora em que o objeto foi criado. O campo createdAt é definido pelo servidor e nunca deve ser definido pelo código do cliente.
  • byte[] version: Normalmente representada como uma cadeia de caracteres, a versão também é definida pelo servidor.
  • boolean deleted: Indica que o registro foi excluído, mas ainda não foi limpo. Não utilize deleted como propriedade na sua classe.

O campo id é obrigatório. O campo e version o updatedAt campo são usados para sincronização offline (para sincronização incremental e resolução de conflitos, respectivamente). O createdAt campo é um campo de referência e não é usado pelo cliente. Os nomes são nomes "transversais" das propriedades e não são ajustáveis. No entanto, você pode criar um mapeamento entre seu objeto e os nomes "cross-the-wire" usando a biblioteca gson . Por exemplo:

package com.example.zumoappname;

import com.microsoft.windowsazure.mobileservices.table.DateTimeOffset;

public class ToDoItem
{
    @com.google.gson.annotations.SerializedName("id")
    private String mId;
    public String getId() { return mId; }
    public final void setId(String id) { mId = id; }

    @com.google.gson.annotations.SerializedName("complete")
    private boolean mComplete;
    public boolean isComplete() { return mComplete; }
    public void setComplete(boolean complete) { mComplete = complete; }

    @com.google.gson.annotations.SerializedName("text")
    private String mText;
    public String getText() { return mText; }
    public final void setText(String text) { mText = text; }

    @com.google.gson.annotations.SerializedName("createdAt")
    private DateTimeOffset mCreatedAt;
    public DateTimeOffset getCreatedAt() { return mCreatedAt; }
    protected void setCreatedAt(DateTimeOffset createdAt) { mCreatedAt = createdAt; }

    @com.google.gson.annotations.SerializedName("updatedAt")
    private DateTimeOffset mUpdatedAt;
    public DateTimeOffset getUpdatedAt() { return mUpdatedAt; }
    protected void setUpdatedAt(DateTimeOffset updatedAt) { mUpdatedAt = updatedAt; }

    @com.google.gson.annotations.SerializedName("version")
    private String mVersion;
    public String getVersion() { return mVersion; }
    public final void setVersion(String version) { mVersion = version; }

    public ToDoItem() { }

    public ToDoItem(String id, String text) {
        this.setId(id);
        this.setText(text);
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof ToDoItem && ((ToDoItem) o).mId == mId;
    }

    @Override
    public String toString() {
        return getText();
    }
}

Criar uma referência de tabela

Para acessar uma tabela, primeiro crie um objeto MobileServiceTable chamando o método getTable no MobileServiceClient. Este método tem duas sobrecargas:

public class MobileServiceClient {
    public <E> MobileServiceTable<E> getTable(Class<E> clazz);
    public <E> MobileServiceTable<E> getTable(String name, Class<E> clazz);
}

No código a seguir, mClient é uma referência ao seu objeto MobileServiceClient. A primeira sobrecarga é usada onde o nome da classe e o nome da tabela são os mesmos e é o usado no Guia de início rápido:

MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable(ToDoItem.class);

A segunda sobrecarga é usada quando o nome da tabela é diferente do nome da classe: o primeiro parâmetro é o nome da tabela.

MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable("ToDoItemBackup", ToDoItem.class);

Consultar uma tabela de back-end

Primeiro, obtenha uma referência de tabela. Em seguida, execute uma consulta na referência da tabela. Uma consulta é qualquer combinação de:

As cláusulas devem ser apresentadas na ordem anterior.

Filtrar os Resultados

A forma geral de uma consulta é:

List<MyDataTable> results = mDataTable
    // More filters here
    .execute()          // Returns a ListenableFuture<E>
    .get()              // Converts the async into a sync result

O exemplo anterior retorna todos os resultados (até o tamanho máximo de página definido pelo servidor). O .execute() método executa a consulta no back-end. A consulta é convertida em uma consulta OData v3 antes da transmissão para o back-end de Aplicativos Móveis. Após o recebimento, o back-end de Aplicativos Móveis converte a consulta em uma instrução SQL antes de executá-la na instância do SQL Azure. Como a atividade de rede leva algum tempo, o .execute() método retorna um ListenableFuture<E>arquivo .

Filtrar dados retornados

A execução da consulta a seguir retorna todos os itens da tabela ToDoItem onde complete é igual a false.

List<ToDoItem> result = mToDoTable
    .where()
    .field("complete").eq(false)
    .execute()
    .get();

mToDoTable é a referência à tabela de serviço móvel que criamos anteriormente.

Defina um filtro usando a chamada do método where na referência da tabela. O método where é seguido por um método field seguido por um método que especifica o predicado lógico. Os possíveis métodos de predicados incluem eq (igual), ne (não igual), gt (maior que), ge (maior ou igual a), lt (menor que), le (menor ou igual a). Esses métodos permitem comparar campos de número e cadeia de caracteres com valores específicos.

Você pode filtrar por datas. Os métodos a seguir permitem comparar todo o campo de data ou partes da data: ano, mês, dia, hora, minuto e segundo. O exemplo a seguir adiciona um filtro para itens cuja data de vencimento é igual a 2013.

List<ToDoItem> results = MToDoTable
    .where()
    .year("due").eq(2013)
    .execute()
    .get();

Os seguintes métodos suportam filtros complexos em campos de cadeia de caracteres: startsWith, endsWith, concat, subString, indexOf, replace, toLower, toUpper, trim e length. O exemplo a seguir filtra linhas de tabela em que a coluna de texto começa com "PRI0".

List<ToDoItem> results = mToDoTable
    .where()
    .startsWith("text", "PRI0")
    .execute()
    .get();

Os seguintes métodos de operador são suportados em campos numéricos: add, sub, mul, div, mod, floor, ceiling e round. O exemplo a seguir filtra linhas de tabela em que a duração é um número par.

List<ToDoItem> results = mToDoTable
    .where()
    .field("duration").mod(2).eq(0)
    .execute()
    .get();

Você pode combinar predicados com estes métodos lógicos: e, ou e não. O exemplo a seguir combina dois dos exemplos anteriores.

List<ToDoItem> results = mToDoTable
    .where()
    .year("due").eq(2013).and().startsWith("text", "PRI0")
    .execute()
    .get();

Operadores lógicos de grupo e aninhamento:

List<ToDoItem> results = mToDoTable
    .where()
    .year("due").eq(2013)
    .and(
        startsWith("text", "PRI0")
        .or()
        .field("duration").gt(10)
    )
    .execute().get();

Para obter uma discussão mais detalhada e exemplos de filtragem, consulte Explorando a riqueza do modelo de consulta do cliente Android.

Classificar dados retornados

O código a seguir retorna todos os itens de uma tabela de ToDoItems classificados em ordem crescente pelo campo de texto. mToDoTable é a referência à tabela de back-end que você criou anteriormente:

List<ToDoItem> results = mToDoTable
    .orderBy("text", QueryOrder.Ascending)
    .execute()
    .get();

O primeiro parâmetro do método orderBy é uma cadeia de caracteres igual ao nome do campo no qual classificar. O segundo parâmetro usa a enumeração QueryOrder para especificar se deve classificar ascendente ou decrescente. Se você estiver filtrando usando o método where, o método where deve ser invocado antes do método orderBy.

Selecionar colunas específicas

O código a seguir ilustra como retornar todos os itens de uma tabela de ToDoItems, mas exibe apenas os campos completo e de texto . mToDoTable é a referência à tabela de back-end que criamos anteriormente.

List<ToDoItemNarrow> result = mToDoTable
    .select("complete", "text")
    .execute()
    .get();

Os parâmetros para a função select são os nomes de cadeia de caracteres das colunas da tabela que você deseja retornar. O método select precisa seguir métodos como where e orderBy. Pode ser seguido por métodos de paginação como skip e top.

Retornar dados em páginas

Os dados são SEMPRE devolvidos em páginas. O número máximo de registros retornados é definido pelo servidor. Se o cliente solicitar mais registros, o servidor retornará o número máximo de registros. Por padrão, o tamanho máximo da página no servidor é de 50 registros.

O primeiro exemplo mostra como selecionar os cinco principais itens de uma tabela. A consulta retorna os itens de uma tabela de ToDoItems. mToDoTable é a referência à tabela de back-end que você criou anteriormente:

List<ToDoItem> result = mToDoTable
    .top(5)
    .execute()
    .get();

Aqui está uma consulta que ignora os cinco primeiros itens e, em seguida, retorna os próximos cinco:

List<ToDoItem> result = mToDoTable
    .skip(5).top(5)
    .execute()
    .get();

Se você deseja obter todos os registros em uma tabela, implemente o código para iterar em todas as páginas:

List<MyDataModel> results = new ArrayList<>();
int nResults;
do {
    int currentCount = results.size();
    List<MyDataModel> pagedResults = mDataTable
        .skip(currentCount).top(500)
        .execute().get();
    nResults = pagedResults.size();
    if (nResults > 0) {
        results.addAll(pagedResults);
    }
} while (nResults > 0);

Uma solicitação para todos os registros usando esse método cria um mínimo de duas solicitações para o back-end de aplicativos móveis.

Gorjeta

Escolher o tamanho de página certo é um equilíbrio entre o uso de memória enquanto a solicitação está acontecendo, o uso de largura de banda e o atraso no recebimento completo dos dados. O padrão (50 registros) é adequado para todos os dispositivos. Se você operar exclusivamente em dispositivos de memória maiores, aumente até 500. Descobrimos que aumentar o tamanho da página além de 500 registros resulta em atrasos inaceitáveis e grandes problemas de memória.

Como: Concatenar métodos de consulta

Os métodos usados na consulta de tabelas de back-end podem ser concatenados. Encadear métodos de consulta permite selecionar colunas específicas de linhas filtradas que são classificadas e paginadas. Você pode criar filtros lógicos complexos. Cada método de consulta retorna um objeto Query. Para encerrar a série de métodos e realmente executar a consulta, chame o método execute . Por exemplo:

List<ToDoItem> results = mToDoTable
        .where()
        .year("due").eq(2013)
        .and(
            startsWith("text", "PRI0").or().field("duration").gt(10)
        )
        .orderBy(duration, QueryOrder.Ascending)
        .select("id", "complete", "text", "duration")
        .skip(200).top(100)
        .execute()
        .get();

Os métodos de consulta encadeados devem ser ordenados da seguinte forma:

  1. Métodos de filtragem (onde).
  2. Métodos de classificação (orderBy).
  3. Métodos de seleção (seleção).
  4. métodos de paginação (pulo e topo).

Vincular dados à interface do usuário

A vinculação de dados envolve três componentes:

  • A fonte de dados
  • O layout da tela
  • O adaptador que une os dois.

Em nosso código de exemplo, retornamos os dados da tabela ToDoItem do SQL Azure de Aplicativos Móveis em uma matriz. Essa atividade é um padrão comum para aplicativos de dados. As consultas de banco de dados geralmente retornam uma coleção de linhas que o cliente obtém em uma lista ou matriz. Neste exemplo, a matriz é a fonte de dados. O código especifica um layout de tela que define a exibição dos dados que aparecem no dispositivo. Os dois são ligados juntos com um adaptador, que neste código é uma extensão do ArrayAdapter<ToDoItem> classe.

Definir o layout

O layout é definido por vários trechos de código XML. Dado um layout existente, o código a seguir representa o ListView que queremos preencher com nossos dados do servidor.

    <ListView
        android:id="@+id/listViewToDo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:listitem="@layout/row_list_to_do" >
    </ListView>

No código anterior, o atributo listitem especifica a id do layout para uma linha individual na lista. Esse código especifica uma caixa de seleção e seu texto associado e é instanciado uma vez para cada item na lista. Esse layout não exibe o campo id e um layout mais complexo especificaria campos adicionais na exibição. Esse código está no arquivo row_list_to_do.xml .

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <CheckBox
        android:id="@+id/checkToDoItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/checkbox_text" />
</LinearLayout>

Definir o adaptador

Como a fonte de dados de nossa exibição é uma matriz de ToDoItem, subclassificamos nosso adaptador de uma classe ArrayAdapter<ToDoItem> . Essa subclasse produz um View para cada ToDoItem usando o layout row_list_to_do . Em nosso código, definimos a seguinte classe que é uma extensão da classe ArrayAdapter<E> :

public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> {
}

Substitua os adaptadores getView método. Por exemplo:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        final ToDoItem currentItem = getItem(position);

        if (row == null) {
            LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
            row = inflater.inflate(R.layout.row_list_to_do, parent, false);
        }
        row.setTag(currentItem);

        final CheckBox checkBox = (CheckBox) row.findViewById(R.id.checkToDoItem);
        checkBox.setText(currentItem.getText());
        checkBox.setChecked(false);
        checkBox.setEnabled(true);

        checkBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (checkBox.isChecked()) {
                    checkBox.setEnabled(false);
                    if (mContext instanceof ToDoActivity) {
                        ToDoActivity activity = (ToDoActivity) mContext;
                        activity.checkItem(currentItem);
                    }
                }
            }
        });
        return row;
    }

Criamos uma instância dessa classe em nossa atividade da seguinte maneira:

    ToDoItemAdapter mAdapter;
    mAdapter = new ToDoItemAdapter(this, R.layout.row_list_to_do);

O segundo parâmetro para o construtor ToDoItemAdapter é uma referência ao layout. Agora podemos instanciar o ListView e atribuir o adaptador ao ListView.

    ListView listViewToDo = (ListView) findViewById(R.id.listViewToDo);
    listViewToDo.setAdapter(mAdapter);

Usar o adaptador para vincular à interface do usuário

Agora você está pronto para usar a vinculação de dados. O código a seguir mostra como obter itens na tabela e preenche o adaptador local com os itens retornados.

    public void showAll(View view) {
        AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    final List<ToDoItem> results = mToDoTable.execute().get();
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            mAdapter.clear();
                            for (ToDoItem item : results) {
                                mAdapter.add(item);
                            }
                        }
                    });
                } catch (Exception exception) {
                    createAndShowDialog(exception, "Error");
                }
                return null;
            }
        };
        runAsyncTask(task);
    }

Chame o adaptador sempre que modificar a tabela ToDoItem . Como as modificações são feitas registro a registro, você manipula uma única linha em vez de uma coleção. Quando você insere um item, chame o método add no adaptador, ao excluir, chame o método remover .

Você pode encontrar um exemplo completo no Android Quickstart Project.

Inserir dados no back-end

Instancie uma instância da classe ToDoItem e defina suas propriedades.

ToDoItem item = new ToDoItem();
item.text = "Test Program";
item.complete = false;

Em seguida, use insert() para inserir um objeto:

ToDoItem entity = mToDoTable
    .insert(item)       // Returns a ListenableFuture<ToDoItem>
    .get();

A entidade retornada corresponde aos dados inseridos na tabela de back-end, incluindo o ID e quaisquer outros valores (como os createdAtcampos , updatedAte version ) definidos no back-end.

As tabelas de Aplicativos Móveis exigem uma coluna de chave primária chamada id. Esta coluna deve ser uma cadeia de caracteres. O valor padrão da coluna ID é um GUID. Você pode fornecer outros valores exclusivos, como endereços de e-mail ou nomes de usuário. Quando um valor de ID de cadeia de caracteres não é fornecido para um registro inserido, o back-end gera um novo GUID.

Os valores de ID de cadeia de caracteres oferecem as seguintes vantagens:

  • Os IDs podem ser gerados sem fazer uma viagem de ida e volta ao banco de dados.
  • Os registros são mais fáceis de mesclar a partir de diferentes tabelas ou bancos de dados.
  • Os valores de ID integram-se melhor com a lógica de um aplicativo.

Os valores de ID de cadeia de caracteres são NECESSÁRIOS para suporte de sincronização offline. Não é possível alterar um Id depois que ele é armazenado no banco de dados de back-end.

Atualizar dados em um aplicativo móvel

Para atualizar dados em uma tabela, passe o novo objeto para o método update( ).

mToDoTable
    .update(item)   // Returns a ListenableFuture<ToDoItem>
    .get();

Neste exemplo, item é uma referência a uma linha na tabela ToDoItem , que teve algumas alterações feitas nele. A linha com o mesmo id é atualizada.

Excluir dados em um aplicativo móvel

O código a seguir mostra como excluir dados de uma tabela especificando o objeto de dados.

mToDoTable
    .delete(item);

Você também pode excluir um item especificando o campo id da linha a ser excluída.

String myRowId = "2FA404AB-E458-44CD-BC1B-3BC847EF0902";
mToDoTable
    .delete(myRowId);

Procurar um item específico por Id

Procure um item com um campo id específico com o método lookUp():

ToDoItem result = mToDoTable
    .lookUp("0380BAFB-BCFF-443C-B7D5-30199F730335")
    .get();

Como: Trabalhar com dados não tipados

O modelo de programação sem tipo oferece controle exato sobre a serialização JSON. Existem alguns cenários comuns em que você pode querer usar um modelo de programação sem tipo. Por exemplo, se sua tabela de back-end contiver muitas colunas e você só precisar fazer referência a um subconjunto das colunas. O modelo tipado requer que você defina todas as colunas definidas no back-end de Aplicativos Móveis em sua classe de dados. A maioria das chamadas de API para acessar dados são semelhantes às chamadas de programação digitadas. A principal diferença é que, no modelo sem tipo, você invoca métodos no objeto MobileServiceJsonTable , em vez do objeto MobileServiceTable .

Criar uma instância de uma tabela sem tipo

Semelhante ao modelo digitado, você começa obtendo uma referência de tabela, mas neste caso é um objeto MobileServicesJsonTable . Obtenha a referência chamando o método getTable em uma instância do cliente:

private MobileServiceJsonTable mJsonToDoTable;
//...
mJsonToDoTable = mClient.getTable("ToDoItem");

Depois de criar uma instância do MobileServiceJsonTable, ele tem praticamente a mesma API disponível como com o modelo de programação digitado. Em alguns casos, os métodos usam um parâmetro não tipado em vez de um parâmetro digitado.

Inserir em uma tabela sem tipo

O código a seguir mostra como fazer uma inserção. O primeiro passo é criar um JsonObject, que faz parte da biblioteca gson .

JsonObject jsonItem = new JsonObject();
jsonItem.addProperty("text", "Wake up");
jsonItem.addProperty("complete", false);

Em seguida, use insert() para inserir o objeto não tipado na tabela.

JsonObject insertedItem = mJsonToDoTable
    .insert(jsonItem)
    .get();

Se você precisar obter a ID do objeto inserido, use o método getAsJsonPrimitive().

String id = insertedItem.getAsJsonPrimitive("id").getAsString();

Excluir de uma tabela sem tipo

O código a seguir mostra como excluir uma instância, neste caso, a mesma instância de um JsonObject que foi criado no exemplo de inserção anterior. O código é o mesmo que com o caso digitado, mas o método tem uma assinatura diferente, pois faz referência a um JsonObject.

mToDoTable
    .delete(insertedItem);

Você também pode excluir uma instância diretamente usando sua ID:

mToDoTable.delete(ID);

Retornar todas as linhas de uma tabela sem tipo

O código a seguir mostra como recuperar uma tabela inteira. Como você está usando uma tabela JSON, você pode recuperar seletivamente apenas algumas das colunas da tabela.

public void showAllUntyped(View view) {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                final JsonElement result = mJsonToDoTable.execute().get();
                final JsonArray results = result.getAsJsonArray();
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        mAdapter.clear();
                        for (JsonElement item : results) {
                            String ID = item.getAsJsonObject().getAsJsonPrimitive("id").getAsString();
                            String mText = item.getAsJsonObject().getAsJsonPrimitive("text").getAsString();
                            Boolean mComplete = item.getAsJsonObject().getAsJsonPrimitive("complete").getAsBoolean();
                            ToDoItem mToDoItem = new ToDoItem();
                            mToDoItem.setId(ID);
                            mToDoItem.setText(mText);
                            mToDoItem.setComplete(mComplete);
                            mAdapter.add(mToDoItem);
                        }
                    }
                });
            } catch (Exception exception) {
                createAndShowDialog(exception, "Error");
            }
            return null;
        }
    }.execute();
}

O mesmo conjunto de métodos de filtragem, filtragem e paginação que estão disponíveis para o modelo tipado estão disponíveis para o modelo não tipado.

Implementar sincronização offline

O SDK do Cliente de Aplicativos Móveis do Azure também implementa a sincronização offline de dados usando um banco de dados SQLite para armazenar uma cópia dos dados do servidor localmente. As operações executadas em uma tabela offline não exigem conectividade móvel para funcionar. A sincronização offline ajuda na resiliência e no desempenho em detrimento de lógicas mais complexas para a resolução de conflitos. O SDK do Cliente de Aplicativos Móveis do Azure implementa os seguintes recursos:

  • Sincronização incremental: Somente registros atualizados e novos são baixados, economizando largura de banda e consumo de memória.
  • Simultaneidade otimista: Presume-se que as operações sejam bem-sucedidas. A Resolução de Conflitos é adiada até que as atualizações sejam executadas no servidor.
  • Resolução de conflitos: o SDK deteta quando uma alteração conflitante foi feita no servidor e fornece ganchos para alertar o usuário.
  • Exclusão suave: os registros excluídos são marcados como excluídos, permitindo que outros dispositivos atualizem seu cache offline.

Inicializar sincronização offline

Cada tabela offline deve ser definida no cache offline antes de ser usada. Normalmente, a definição da tabela é feita imediatamente após a criação do cliente:

AsyncTask<Void, Void, Void> initializeStore(MobileServiceClient mClient)
    throws MobileServiceLocalStoreException, ExecutionException, InterruptedException
{
    AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        @Override
        protected void doInBackground(Void... params) {
            try {
                MobileServiceSyncContext syncContext = mClient.getSyncContext();
                if (syncContext.isInitialized()) {
                    return null;
                }
                SQLiteLocalStore localStore = new SQLiteLocalStore(mClient.getContext(), "offlineStore", null, 1);

                // Create a table definition.  As a best practice, store this with the model definition and return it via
                // a static method
                Map<String, ColumnDataType> toDoItemDefinition = new HashMap<String, ColumnDataType>();
                toDoItemDefinition.put("id", ColumnDataType.String);
                toDoItemDefinition.put("complete", ColumnDataType.Boolean);
                toDoItemDefinition.put("text", ColumnDataType.String);
                toDoItemDefinition.put("version", ColumnDataType.String);
                toDoItemDefinition.put("updatedAt", ColumnDataType.DateTimeOffset);

                // Now define the table in the local store
                localStore.defineTable("ToDoItem", toDoItemDefinition);

                // Specify a sync handler for conflict resolution
                SimpleSyncHandler handler = new SimpleSyncHandler();

                // Initialize the local store
                syncContext.initialize(localStore, handler).get();
            } catch (final Exception e) {
                createAndShowDialogFromTask(e, "Error");
            }
            return null;
        }
    };
    return runAsyncTask(task);
}

Obter uma referência à Tabela de Cache Offline

Para uma mesa online, você usa .getTable()o . Para uma tabela offline, use .getSyncTable():

MobileServiceSyncTable<ToDoItem> mToDoTable = mClient.getSyncTable("ToDoItem", ToDoItem.class);

Todos os métodos disponíveis para tabelas online (incluindo filtragem, classificação, paginação, inserção de dados, atualização de dados e exclusão de dados) funcionam igualmente bem em tabelas online e offline.

Sincronizar o cache offline local

A sincronização está dentro do controle do seu aplicativo. Aqui está um exemplo de método de sincronização:

private AsyncTask<Void, Void, Void> sync(MobileServiceClient mClient) {
    AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
        @Override
        protected Void doInBackground(Void... params) {
            try {
                MobileServiceSyncContext syncContext = mClient.getSyncContext();
                syncContext.push().get();
                mToDoTable.pull(null, "todoitem").get();
            } catch (final Exception e) {
                createAndShowDialogFromTask(e, "Error");
            }
            return null;
        }
    };
    return runAsyncTask(task);
}

Se um nome de consulta for fornecido para o .pull(query, queryname) método, a sincronização incremental será usada para retornar somente registros que foram criados ou alterados desde o último pull concluído com êxito.

Manipular conflitos durante a sincronização offline

Se um conflito acontece durante uma .push() operação, um MobileServiceConflictException é lançado. O item emitido pelo servidor é incorporado na exceção e pode ser recuperado por .getItem() na exceção. Ajuste o push chamando os seguintes itens no objeto MobileServiceSyncContext:

  • .cancelAndDiscardItem()
  • .cancelAndUpdateItem()
  • .updateOperationAndItem()

Assim que todos os conflitos estiverem marcados como desejar, ligue .push() novamente para resolver todos os conflitos.

Chamar uma API personalizada

Uma API personalizada permite definir pontos de extremidade personalizados que expõem a funcionalidade do servidor que não é mapeada para uma operação de inserção, atualização, exclusão ou leitura. Usando uma API personalizada, você pode ter mais controle sobre as mensagens, incluindo a leitura e a configuração de cabeçalhos de mensagens HTTP e a definição de um formato de corpo de mensagem diferente de JSON.

Em um cliente Android, você chama o método invokeApi para chamar o ponto de extremidade de API personalizado. O exemplo a seguir mostra como chamar um ponto de extremidade de API chamado completeAll, que retorna uma classe de coleção chamada MarkAllResult.

public void completeItem(View view) {
    ListenableFuture<MarkAllResult> result = mClient.invokeApi("completeAll", MarkAllResult.class);
    Futures.addCallback(result, new FutureCallback<MarkAllResult>() {
        @Override
        public void onFailure(Throwable exc) {
            createAndShowDialog((Exception) exc, "Error");
        }

        @Override
        public void onSuccess(MarkAllResult result) {
            createAndShowDialog(result.getCount() + " item(s) marked as complete.", "Completed Items");
            refreshItemsFromTable();
        }
    });
}

O método invokeApi é chamado no cliente, que envia uma solicitação POST para a nova API personalizada. O resultado retornado pela API personalizada é exibido em uma caixa de diálogo de mensagem, assim como quaisquer erros. Outras versões de invokeApi permitem opcionalmente enviar um objeto no corpo da solicitação, especificar o método HTTP e enviar parâmetros de consulta com a solicitação. Versões não tipadas de invokeApi também são fornecidas.

Adicionar autenticação à aplicação

Os tutoriais já descrevem em detalhes como adicionar esses recursos.

O Serviço de Aplicativo dá suporte à autenticação de usuários de aplicativos usando vários provedores de identidade externos: Facebook, Google, Conta da Microsoft, Twitter e Azure Ative Directory. Você pode definir permissões em tabelas para restringir o acesso de operações específicas apenas a usuários autenticados. Você também pode usar a identidade de usuários autenticados para implementar regras de autorização em seu back-end.

Há suporte para dois fluxos de autenticação: um fluxo de servidor e um fluxo de cliente . O fluxo do servidor fornece a experiência de autenticação mais simples, pois depende da interface da Web dos provedores de identidade. Nenhum SDK adicional é necessário para implementar a autenticação de fluxo do servidor. A autenticação de fluxo de servidor não fornece uma integração profunda com o dispositivo móvel e só é recomendada para cenários de prova de conceito.

O fluxo de cliente permite uma integração mais profunda com recursos específicos do dispositivo, como logon único, pois depende de SDKs fornecidos pelo provedor de identidade. Por exemplo, você pode integrar o SDK do Facebook em seu aplicativo móvel. O cliente móvel troca para o aplicativo do Facebook e confirma seu logon antes de trocar de volta para seu aplicativo móvel.

Quatro etapas são necessárias para habilitar a autenticação em seu aplicativo:

  • Registre seu aplicativo para autenticação com um provedor de identidade.
  • Configure o back-end do Serviço de Aplicativo.
  • Restrinja as permissões de tabela para usuários autenticados somente no back-end do Serviço de Aplicativo.
  • Adicione código de autenticação ao seu aplicativo.

Você pode definir permissões em tabelas para restringir o acesso de operações específicas apenas a usuários autenticados. Você também pode usar o SID de um usuário autenticado para modificar solicitações. Para obter mais informações, consulte Introdução à autenticação e a documentação HOWTO do SDK do servidor.

Autenticação: Fluxo do servidor

O código a seguir inicia um processo de login de fluxo de servidor usando o provedor do Google. A configuração adicional é necessária devido aos requisitos de segurança para o provedor do Google:

MobileServiceUser user = mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);

Além disso, adicione o seguinte método à classe Activity principal:

// You can choose any unique number here to differentiate auth providers from each other. Note this is the same code at login() and onActivityResult().
public static final int GOOGLE_LOGIN_REQUEST_CODE = 1;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // When request completes
    if (resultCode == RESULT_OK) {
        // Check the request code matches the one we send in the login request
        if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
            MobileServiceActivityResult result = mClient.onActivityResult(data);
            if (result.isLoggedIn()) {
                // login succeeded
                createAndShowDialog(String.format("You are now logged in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                createTable();
            } else {
                // login failed, check the error message
                String errorMessage = result.getErrorMessage();
                createAndShowDialog(errorMessage, "Error");
            }
        }
    }
}

O GOOGLE_LOGIN_REQUEST_CODE definido em sua atividade principal é usado para o login() método e dentro do onActivityResult() método. Você pode escolher qualquer número exclusivo, desde que o mesmo número seja usado dentro do login() método e do onActivityResult() método. Se você abstrair o código do cliente em um adaptador de serviço (como mostrado anteriormente), você deve chamar os métodos apropriados no adaptador de serviço.

Você também precisa configurar o projeto para guias personalizadas. Primeiro, especifique um URL de redirecionamento. Adicione o seguinte trecho a AndroidManifest.xml:

<activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="{url_scheme_of_your_app}" android:host="easyauth.callback"/>
    </intent-filter>
</activity>

Adicione o redirectUriScheme ao build.gradle arquivo do seu aplicativo:

android {
    buildTypes {
        release {
            // … …
            manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
        }
        debug {
            // … …
            manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
        }
    }
}

Finalmente, adicione com.android.support:customtabs:28.0.0 à lista de dependências no build.gradle arquivo:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.code.gson:gson:2.3'
    implementation 'com.google.guava:guava:18.0'
    implementation 'com.android.support:customtabs:28.0.0'
    implementation 'com.squareup.okhttp:okhttp:2.5.0'
    implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
    implementation 'com.microsoft.azure:azure-notifications-handler:1.0.1@jar'
}

Obtenha a ID do usuário conectado de um MobileServiceUser usando o método getUserId . Para obter um exemplo de como usar o Futures para chamar as APIs de login assíncronas, consulte Introdução à autenticação.

Aviso

O esquema de URL mencionado diferencia maiúsculas de minúsculas. Certifique-se de que todas as ocorrências de {url_scheme_of_you_app} caso de correspondência.

Tokens de autenticação de cache

O armazenamento em cache de tokens de autenticação exige que você armazene o ID de usuário e o token de autenticação localmente no dispositivo. Na próxima vez que o aplicativo for iniciado, você verificará o cache e, se esses valores estiverem presentes, poderá ignorar o procedimento de login e reidratar o cliente com esses dados. No entanto, esses dados são confidenciais e devem ser armazenados criptografados para segurança no caso de o telefone ser roubado. Você pode ver um exemplo completo de como armazenar tokens de autenticação em cache na seção Tokens de autenticação de cache.

Quando você tenta usar um token expirado, você recebe uma resposta não autorizada 401. Você pode lidar com erros de autenticação usando filtros. Os filtros intercetam solicitações para o back-end do Serviço de Aplicativo. O código de filtro testa a resposta para um 401, dispara o processo de entrada e, em seguida, retoma a solicitação que gerou o 401.

Usar tokens de atualização

O token retornado pela Autenticação e Autorização do Serviço de Aplicativo do Azure tem um tempo de vida definido de uma hora. Após esse período, você deve autenticar novamente o usuário. Se você estiver usando um token de longa duração que recebeu por meio da autenticação de fluxo de cliente, poderá autenticar novamente com a Autenticação e Autorização do Serviço de Aplicativo do Azure usando o mesmo token. Outro token do Serviço de Aplicativo do Azure é gerado com um novo tempo de vida.

Você também pode registrar o provedor para usar Refresh Tokens. Um token de atualização nem sempre está disponível. É necessária uma configuração adicional:

  • Para o Azure Ative Directory, configure um segredo do cliente para o Aplicativo Azure Ative Directory. Especifique o segredo do cliente no Serviço de Aplicativo do Azure ao configurar a Autenticação do Azure Ative Directory. Ao chamar .login(), passe response_type=code id_token como parâmetro:

    HashMap<String, String> parameters = new HashMap<String, String>();
    parameters.put("response_type", "code id_token");
    MobileServiceUser user = mClient.login
        MobileServiceAuthenticationProvider.AzureActiveDirectory,
        "{url_scheme_of_your_app}",
        AAD_LOGIN_REQUEST_CODE,
        parameters);
    
  • Para o Google, passe o access_type=offline como parâmetro:

    HashMap<String, String> parameters = new HashMap<String, String>();
    parameters.put("access_type", "offline");
    MobileServiceUser user = mClient.login
        MobileServiceAuthenticationProvider.Google,
        "{url_scheme_of_your_app}",
        GOOGLE_LOGIN_REQUEST_CODE,
        parameters);
    
  • Para Conta da Microsoft, selecione o wl.offline_access escopo.

Para atualizar um token, chame .refreshUser():

MobileServiceUser user = mClient
    .refreshUser()  // async - returns a ListenableFuture<MobileServiceUser>
    .get();

Como prática recomendada, crie um filtro que detete uma resposta 401 do servidor e tente atualizar o token de usuário.

Faça login com autenticação de fluxo de cliente

O processo geral para efetuar login com autenticação de fluxo de cliente é o seguinte:

  • Configure a Autenticação e Autorização do Serviço de Aplicativo do Azure como faria com a autenticação de fluxo de servidor.

  • Integre o SDK do provedor de autenticação para autenticação para produzir um token de acesso.

  • Chame o .login() método da seguinte forma (result deve ser um AuthenticationResult):

    JSONObject payload = new JSONObject();
    payload.put("access_token", result.getAccessToken());
    ListenableFuture<MobileServiceUser> mLogin = mClient.login("{provider}", payload.toString());
    Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
        @Override
        public void onFailure(Throwable exc) {
            exc.printStackTrace();
        }
        @Override
        public void onSuccess(MobileServiceUser user) {
            Log.d(TAG, "Login Complete");
        }
    });
    

Consulte o exemplo de código completo na próxima seção.

Substitua o onSuccess() método por qualquer código que você deseja usar em um login bem-sucedido. A {provider} cadeia de caracteres é um provedor válido: aad (Azure Ative Directory), facebook, google, microsoftaccount ou twitter. Se você implementou a autenticação personalizada, também pode usar a tag do provedor de autenticação personalizada.

Autenticar usuários com a Biblioteca de Autenticação do Ative Directory (ADAL)

Você pode usar a Biblioteca de Autenticação do Ative Directory (ADAL) para entrar usuários em seu aplicativo usando o Azure Ative Directory. Usar um login de fluxo de cliente geralmente é preferível ao uso dos loginAsync() métodos, pois fornece uma sensação de UX mais nativa e permite personalização adicional.

  1. Configure seu back-end de aplicativo móvel para entrada no AAD seguindo o tutorial Como configurar o Serviço de Aplicativo para logon do Ative Directory. Certifique-se de concluir a etapa opcional de registrar um aplicativo cliente nativo.

  2. Instale o ADAL modificando seu arquivo build.gradle para incluir as seguintes definições:

    repositories {
        mavenCentral()
        flatDir {
            dirs 'libs'
        }
        maven {
            url "YourLocalMavenRepoPath\\.m2\\repository"
        }
    }
    packagingOptions {
        exclude 'META-INF/MSFTSIG.RSA'
        exclude 'META-INF/MSFTSIG.SF'
    }
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation('com.microsoft.aad:adal:1.16.1') {
            exclude group: 'com.android.support'
        } // Recent version is 1.16.1
        implementation 'com.android.support:support-v4:28.0.0'
    }
    
  3. Adicione o seguinte código ao seu aplicativo, fazendo as seguintes substituições:

    • Substitua INSERT-AUTHORITY-HERE pelo nome do locatário no qual você provisionou seu aplicativo. O formato deve ser https://login.microsoftonline.com/contoso.onmicrosoft.com.
    • Substitua INSERT-RESOURCE-ID-HERE pelo ID do cliente para o back-end do seu aplicativo móvel. Você pode obter a ID do cliente na guia Avançado em Configurações do Ative Directory do Azure no portal.
    • Substitua INSERT-CLIENT-ID-HERE pelo ID do cliente copiado do aplicativo cliente nativo.
    • Substitua INSERT-REDIRECT-URI-HERE pelo ponto de extremidade /.auth/login/done do seu site, usando o esquema HTTPS. Este valor deve ser semelhante a https://contoso.azurewebsites.net/.auth/login/done.
private AuthenticationContext mContext;

private void authenticate() {
    String authority = "INSERT-AUTHORITY-HERE";
    String resourceId = "INSERT-RESOURCE-ID-HERE";
    String clientId = "INSERT-CLIENT-ID-HERE";
    String redirectUri = "INSERT-REDIRECT-URI-HERE";
    try {
        mContext = new AuthenticationContext(this, authority, true);
        mContext.acquireToken(this, resourceId, clientId, redirectUri, PromptBehavior.Auto, "", callback);
    } catch (Exception exc) {
        exc.printStackTrace();
    }
}

private AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
    @Override
    public void onError(Exception exc) {
        if (exc instanceof AuthenticationException) {
            Log.d(TAG, "Cancelled");
        } else {
            Log.d(TAG, "Authentication error:" + exc.getMessage());
        }
    }

    @Override
    public void onSuccess(AuthenticationResult result) {
        if (result == null || result.getAccessToken() == null
                || result.getAccessToken().isEmpty()) {
            Log.d(TAG, "Token is empty");
        } else {
            try {
                JSONObject payload = new JSONObject();
                payload.put("access_token", result.getAccessToken());
                ListenableFuture<MobileServiceUser> mLogin = mClient.login("aad", payload.toString());
                Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
                    @Override
                    public void onFailure(Throwable exc) {
                        exc.printStackTrace();
                    }
                    @Override
                    public void onSuccess(MobileServiceUser user) {
                        Log.d(TAG, "Login Complete");
                    }
                });
            }
            catch (Exception exc){
                Log.d(TAG, "Authentication error:" + exc.getMessage());
            }
        }
    }
};

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (mContext != null) {
        mContext.onActivityResult(requestCode, resultCode, data);
    }
}

Ajustar a comunicação cliente-servidor

A conexão de cliente é normalmente uma conexão HTTP básica usando a biblioteca HTTP subjacente fornecida com o Android SDK. Há várias razões pelas quais você gostaria de mudar isso:

  • Você deseja usar uma biblioteca HTTP alternativa para ajustar os tempos limites.
  • Você deseja fornecer uma barra de progresso.
  • Você deseja adicionar um cabeçalho personalizado para dar suporte à funcionalidade de gerenciamento de API.
  • Você deseja intercetar uma resposta com falha para que possa implementar a reautenticação.
  • Você deseja registrar solicitações de back-end em um serviço de análise.

Usando uma biblioteca HTTP alternativa

Chame o método imediatamente após criar sua referência de .setAndroidHttpClientFactory() cliente. Por exemplo, para definir o tempo limite de conexão para 60 segundos (em vez dos 10 segundos padrão):

mClient = new MobileServiceClient("https://myappname.azurewebsites.net");
mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
    @Override
    public OkHttpClient createOkHttpClient() {
        OkHttpClient client = new OkHttpClient();
        client.setReadTimeout(60, TimeUnit.SECONDS);
        client.setWriteTimeout(60, TimeUnit.SECONDS);
        return client;
    }
});

Implementar um filtro de progresso

Você pode implementar uma intercetação de cada solicitação implementando um ServiceFilterarquivo . Por exemplo, o seguinte atualiza uma barra de progresso pré-criada:

private class ProgressFilter implements ServiceFilter {
    @Override
    public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
        final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (mProgressBar != null) mProgressBar.setVisibility(ProgressBar.VISIBLE);
            }
        });

        ListenableFuture<ServiceFilterResponse> future = next.onNext(request);
        Futures.addCallback(future, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable e) {
                resultFuture.setException(e);
            }
            @Override
            public void onSuccess(ServiceFilterResponse response) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (mProgressBar != null)
                            mProgressBar.setVisibility(ProgressBar.GONE);
                    }
                });
                resultFuture.set(response);
            }
        });
        return resultFuture;
    }
}

Você pode anexar esse filtro ao cliente da seguinte maneira:

mClient = new MobileServiceClient(applicationUrl).withFilter(new ProgressFilter());

Personalizar cabeçalhos de solicitação

Use o seguinte ServiceFilter e anexe o filtro da mesma forma que o ProgressFilter:

private class CustomHeaderFilter implements ServiceFilter {
    @Override
    public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                request.addHeader("X-APIM-Router", "mobileBackend");
            }
        });
        SettableFuture<ServiceFilterResponse> result = SettableFuture.create();
        try {
            ServiceFilterResponse response = next.onNext(request).get();
            result.set(response);
        } catch (Exception exc) {
            result.setException(exc);
        }
    }
}

Configurar a serialização automática

Você pode especificar uma estratégia de conversão que se aplique a cada coluna usando a API gson . A biblioteca de cliente Android usa gson nos bastidores para serializar objetos Java para dados JSON antes que os dados sejam enviados para o Serviço de Aplicativo do Azure. O código a seguir usa o método setFieldNamingStrategy() para definir a estratégia. Este exemplo excluirá o caractere inicial (um "m") e, em seguida, minúscula o próximo caractere, para cada nome de campo. Por exemplo, transformaria "mId" em "id". Implemente uma estratégia de conversão para reduzir a necessidade de SerializedName() anotações na maioria dos campos.

FieldNamingStrategy namingStrategy = new FieldNamingStrategy() {
    public String translateName(File field) {
        String name = field.getName();
        return Character.toLowerCase(name.charAt(1)) + name.substring(2);
    }
}

client.setGsonBuilder(
    MobileServiceClient
        .createMobileServiceGsonBuilder()
        .setFieldNamingStrategy(namingStrategy)
);

Esse código deve ser executado antes de criar uma referência de cliente móvel usando o MobileServiceClient.