O blog da AWS

Criação de aplicativos nativos no Android com a AWS

Por Kevin Cortés, Arquiteto de Soluções AWS Argentina

 

Hoje podemos dizer que todos contamos com nossos dispositivos móveis para as mais diversas funcionalidades: fazer e controlar pedidos de delivery, chamar um carro ou até mesmo entreter-se com um bom filme sexta-feira a noite. O telefone celular tornou-se uma ferramenta indispensável para o nosso dia a dia, e assim também, a AWS tem sabido entender essa demanda.

Nesta publicação, mostraremos como projetar e implantar um aplicativo móvel no Android usando SDKs e diferentes serviços da AWS . O aplicativo terá usuários que terão que se autenticar digitando um e-mail e senha, validando essas informações usando um código de 6 dígitos gerado aleatoriamente. Tendo entrado, o usuário pode realizar diferentes funções do aplicativo enviando solicitações para uma API Rest com um token de autenticação.

Por sua vez, este blog será acompanhado por este código publicado no AWS Sample Repository que pode ser usado como referência.

O diagrama de arquitetura a seguir descreve como os serviços da AWS envolvidos nesta publicação interagem.

 

 

Para esta ocasião, utilizaremos os seguintes serviços:

  • AWS Amplify
  • Amazon API Gateway
  • Amazon Cognito
  • AWS Lambda
  • Amazon DynamoDB

O AWS Amplify é um conjunto de produtos e ferramentas que permite que desenvolvedores de web móveis e frontend criem e implementem aplicativos seguros e escaláveis. Com o Amplify, você pode configurar back-ends de aplicativos em minutos e conectá-los ao seu aplicativo em apenas algumas linhas de código.

Com isso, criaremos grupos de usuários no Cognito. Este serviço permite que você incorpore de forma rápida e fácil controle de acesso, inscrição e login do usuário. Por outro lado, ele também suporta login através de provedores de identidade social, como Apple, Facebook, Google e Amazon, e provedores de identidade de negócios através do SAML 2.0 e OpenID Connect. Para este estudo de caso, os usuários precisarão se registrar para acessar nosso aplicativo.

Além disso, a API Rest será exposta publicamente com o serviço API Gateway que receberá solicitações HTTPS autenticadas pelo Cognito. Dessa forma, solicitações bem-sucedidas devem ser autorizadas usando o token fornecido pelo Cognito para executar. Caso contrário, a API retornará o código de erro ‘403 – Proibido’.

A API exibirá apenas informações essenciais do cliente, como seu nome e sobrenome. Esses registros serão associados a um identificador exclusivo fornecido pelo Cognito que identifica univocamente o usuário. Os dados serão armazenados em uma tabela do DynamoDB, um banco de dados de chave-valor não relacional que oferece desempenho de milissegundos de um dígito em qualquer escala.

A primeira coisa a fazer é criar um novo projeto no Android Studio. Inicialmente, o projeto terá uma única Atividade. Este é um componente principal da interface no Android e está associado a uma janela onde a interface é definida para o usuário interagir com os componentes criados nele (um texto, um botão, uma caixa de diálogo e muito mais).

O aplicativo terá duas Atividades. Quando o aplicativo é executado, por padrão, ele lançará o InitActivity, que conterá um FrameLayout. Isso é usado para conter fragmentos. Um fragmento pode ser definido como uma parte da interface que pode ser adicionada a uma Atividade de forma independente e destina-se a ser reutilizado em outras atividades

Para a execução do cargo, precisaremos de duas Atividades:

  • InitActivity: Ele conterá a lógica do Login, Register e CodeValidation.
  • HomeActivity: Navegará entre diferentes interfaces (fragmentos) para exibir informações de API assim que o usuário tiver entrado.

 

 

Depois de criar a primeira Atividade, você precisará configurar o Amplify em nosso projeto. Para fazer isto, você deve primeiro baixar a CLI para interagir usando o terminal:

npm installl -g @aws -amplify/cli

 

Em seguida, você deve configurar o projeto com um nome, região e usuário do IAM (Identity and Access Management) com as permissões necessárias. Para fazer isso, execute o seguinte comando:

amplify configure

 

Se formos para o serviço Amplify a partir do console da AWS, você poderá ver que um aplicativo já está disponível.

 

 

Em seguida, você precisa fazer modificações no projeto do Android Studio. Para fazer isso, você deve adicionar as seguintes linhas ao build.gradle:

 

android {

    compileOptions {

        // Support for Java 8 features

        coreLibraryDesugaringEnabled true

        sourceCompatibility JavaVersion.VERSION_1_8

        targetCompatibility JavaVersion.VERSION_1_8

    }

}


dependencies {

    // Amplify core dependency

    implementation 'com.amplifyframework:core:1.6.8'
 

    // Support for Java 8 features

    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'

}

 

Para iniciar o provisionamento de recursos na AWS, você deve primeiro inicializar o projeto localizado no diretório raiz com o comando:

amplify init

 

E no método onCreate do InitActivity adicione o seguinte trecho de inicialização do Amplify no código:

       

try {

            Amplify.addPlugin(new AWSCognitoAuthPlugin());

            AmplifyConfiguration config = AmplifyConfiguration.builder(getApplicationContext()).devMenuEnabled(false).build();

            Amplify.configure(config, getApplicationContext());

 

            Log.i("Tutorial", "Initialized Amplify");

        } catch (AmplifyException e) {

            Log.e("Tutorial", "Could not initialize Amplify", e);

        }

 

Depois de chegar a esse ponto, você pode começar a implantar nossos serviços na AWS usando o SDK do Amplify a partir do nosso código. A próxima etapa é configurar a autenticação.

 

Adicionando autenticação ao nosso aplicativo

Como mencionado na etapa anterior, initActivity executa a renderização de 3 tipos de fragmentos: Login, Register e CodeValidation. Cada um executará a função como o nome indica. Desta vez, vamos nos concentrar em como implementar o componente de login.

O aplicativo deve ter permissões adequadas e acesso à Internet, portanto, no arquivo AndroidManifest.xml, devemos adicionar:

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

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

 

 

O próximo passo é adicionar o recurso de autenticação ao nosso projeto do Amplify. Faremos isso executando um comando e definindo os parâmetros que queremos. Neste caso, será por padrão. O comando é:

amplify add auth

 

Se navegarmos para o serviço Amplify novamente a partir do console da AWS, dentro do nosso projeto, veremos que há um recurso de back-end criado.

 

 

Este recurso é gerenciado pelo Cognito e quando você for ao serviço Cognito, notaremos que um grupo de usuários foi criado:

 

 

 

Voltando ao código novamente, você pode ver que um novo diretório chamado amplify foi criado com nossas configurações de projeto e autenticação.

Por outro lado, as dependências do Cognito precisam ser adicionadas ao código Java. Em build.gradle adicionamos a seguinte linha

implementation 'com.amplifyframework:aws-auth-cognito:1.6.8'

Nosso objetivo é que a aplicação mostre inicialmente 4 campos: 2 entradas de texto para usuário e senha respectivamente, e dois botões para entrar ou, caso não seja registrado, ir para a página de registro. Toda a lógica será tratada pelo LogInfragment em InitActivity . Para fazer isso, vamos criar um layout com dois TextInputEditText. Finalmente, você também deve adicionar 2 botões: um para ir para o fragmento de registro e outro para fazer login no Cognito. Na captura de tela a seguir, podemos ver um exemplo do objetivo de layout que precisaremos.

 

 

Voltando ao código do fragmento, dentro do método onCreateView você deve declarar as variáveis dos dois TextInputEditText para obter o texto de e-mail e senha. Também declararemos os botões Login e Register para definir as ações posteriormente.

 

final TextInputEditText passwordEditText = view.findViewById(R.id.password_edit_text);

final TextInputEditText usernameEditText = view.findViewById(R.id.username_edit_text);

MaterialButton nextButton = view.findViewById(R.id.next_button);

MaterialButton registerButton = view.findViewById(R.id.register_button);




nextButton.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View view) {

 

        String email = usernameEditText.getText().toString();

        String password = passwordEditText.getText().toString();

 

        Amplify.Auth.signIn(

                email,

                password,

                result -> {

                    if ( result.isSignInComplete() ){

                        Intent intent = new Intent(getActivity(), HomeActivity.class);

                        startActivity(intent);

                    } else {

                        Log.d("Amplify-Login", "User Sign In is not complete.");

                    }

                },

                error -> {

                    Log.e("Amplify-Login", error.toString());

                }

        );

    }

});

 

registerButton.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View view) {

        ((NavigationHost) getActivity()).navigateTo(new RegisterFragment(), false); // Navigate to the next Fragment

    }

});

 

Se o login for bem-sucedido, ele será redirecionado para a outra atividade que criamos: HomeActividade. Nele, mostraremos informações básicas das chamadas feitas a API. Nossa próxima etapa é configurar a API Rest usando o Amazon API Gateway, o AWS Lambda e o Amazon DynamoDB.

No serviço API Gateway, vamos gerar uma nova api (android-app-api) e criar um novo recurso chamado profile, com 2 métodos: GET e POST. Cada método se refere a duas funções lambda diferentes: uma que consulta o banco de dados no DynamoDB e a outra que persiste os dados, respectivamente. Em paralelo, ambas as funções têm uma role de execução que permite que elas tenham o nível de privilégio necessário para consultar e inserir dados na nossa tabela no Amazon DynamoDB. A próxima coisa a fazer é verificar se ambas as funções têm as permissões de leitura e gravação apropriadas.

 

 

 

Uma vez que os métodos e recursos são configurados, devemos configurar os autorizadores, associando o pool de usuários do Cognito à nossa API. Caso contrário, vamos expor a API aberta a qualquer usuário. Na seção Autorizadores, criaremos um novo e adicionaremos o pool criado pelo Amplify anteriormente. No Token Source, devemos adicionar o nome do cabeçalho com o qual a API interpretará o token, neste caso é “Autorização”.

 

 

Depois de salvar, devemos editar a solicitação, alterando a Autorização de “NONE” pelo autorizador criado anteriormente.

 

 

A execução do método deve ser parecida com a seguinte captura:

 

 

Precisamos implantar a API para aplicar as alterações.

 

 

Na seção de Stages veremos o URL fornecido.

 

 

Em seguida, precisamos criar nossa tabela no banco de dados. Neste blog, não nos concentraremos em como criar uma tabela no DynamoDB, embora existam referências sobre como fazer isso no link a seguir. Como mencionado anteriormente, vamos criar uma tabela no DynamoDB e usar uma String com a chave “user_id” como chave primária. Isso fará referência ao ID de usuário do Cognito, por isso garantimos que não haverá duplicatas deste campo.

 

 

Finalmente, precisamos de uma função que serve como um conector entre a API e o banco de dados. Neste caso, vou dar-lhe um exemplo da função associada ao método GET. A função usa o user_id da solicitação enviada pelo API Gateway. Dessa forma, ele então faz uma busca por esse valor. Se não existir, ele retorna um erro genérico.

 

Um exemplo de função Lambda que traz valores da tabela seria:

 

import json

import boto3

from botocore.exceptions import ClientError

from boto3.dynamodb.conditions import Key, Attr

 

dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('UserData') #Name of your table created in DynamoDB

 

def lambda_handler(event, context):

   

    user_id = event["requestContext"]["authorizer"]["claims"]["event_id"]

   

    try:

        response = table.get_item(Key={'user_id': user_id})

    except ClientError as e:

        print(e)

       

        response_to_lambda = {

            "statusCode" : 500,

            "body" : "Something went wrong. Please, check your data again"

        }

    else:

        response_to_lambda = {

            "statusCode" : 200,

            "body" : json.dumps(response["Item"])

        }

       

    return response_to_lambda

 

Neste ponto, teremos uma API com diferentes recursos e métodos (GET, POST) autenticados, fazendo chamadas para funções Lambda e estas fazem chamadas para o DynamoDB.

O próximo passo é modificar a atividade HomeActivity e criar um fragmento chamado UserProfileFragment, que será mostrado no aplicativo quando o login for bem-sucedido. O layout do fragmento deve ter 4 TextInputEditText para que dentro de cada um possamos incluir as informações fornecidas pela API.

 

 

No método onCreateView do fragmento, devemos instanciar os 4 tipos de TextInputEditText:

 

@Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_user_data, container, false);

 

        TextInputEditText user_id = view.findViewById(R.id.user_data_user_id);

        TextInputEditText email = view.findViewById(R.id.user_data_user_email);

        TextInputEditText name = view.findViewById(R.id.user_data_user_name);

        TextInputEditText surname = view.findViewById(R.id.user_data_user_surname);

 

        APIInfo api = new APIInfo();

 

        Amplify.Auth.fetchAuthSession(

                result -> {

                    AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result;

 

                    switch(cognitoAuthSession.getIdentityId().getType()) {

                        case SUCCESS:

                            String accessToken = cognitoAuthSession.getUserPoolTokens().getValue().getIdToken();

 

                            api.getPersonalInfo(getContext(), accessToken, new UserInfoRunInterface() {

                                @Override

                                public void run(User user) {

                                    user_id.setText(user.getUserID());

                                    email.setText(user.getEmail());

                                    name.setText(user.getName());

                                    surname.setText(user.getSurname());

                                }

                            });

 

                            break;

 

                        case FAILURE:

                            Log.e("Amplify-Authentication", "IdentityId not present because: " + cognitoAuthSession.getIdentityId().getError().toString());

                    }

 

                },

                error -> {

                    Log.d("Amplify-Authentication", error.toString());

                }

        );

 

        return view;

    }

 

 

O Aplicativo deve executar o código Amplify que retorna um token JWT dado pelo Cognito quando o usuário fez login no app. Se não houver erros, faça uma chamada de API usando o método GetPersonalInfo (detalhado abaixo neste documento). Para este método, devemos passar 3 parâmetros: o contexto do fragmento, o token e a implementação de uma interface com um método run. Dentro deste método, devemos configurar o setText de cada um dos TextInputEditText com os dados fornecidos pela API.

 

O método GetPersonalInfo é um método dentro de outra classe para simplificar chamadas de API e tornar o modelo de fragmento mais dissociado dele.

 

public void getPersonalInfo(Context appContext, String userToken, UserInfoRunInterface userInfoRunInterface) {

        RequestQueue queue = Volley.newRequestQueue(appContext);

        String url = APIConstants.getAPIPersonalInfo();

 

        JSONObject obj = new JSONObject();

 

        JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, obj,

 

                new Response.Listener<JSONObject>() {

                    @Override

                    public void onResponse(JSONObject response) {

                        User u = User.parseUser(response);

 

                        //Call the interface’s method

                        userInfoRunInterface.run(u);

                    }

                },

                new Response.ErrorListener() {

                    @Override

                    public void onErrorResponse(VolleyError error) {

                        Log.e(“API”, error.toString());

                    }

                }

        ) {

            @Override

            public Map<String, String> getHeaders() throws AuthFailureError {

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

 

                //Because it is an authenticated method, we must add this header with the token provided.

 

                params.put(“Authorization”, userToken);

                return params;

            }

        };

 

        queue.add(jsObjRequest);

    }

 

 

Conclusões

A maioria das empresas e startups estão pensando em mobile precisamente porque hoje todos nós temos um telefone celular e este se tornou uma obrigação para nossas tarefas diárias. O objetivo deste blog é que você possa guiar-se com noções básicas de como adicionar alguns recursos ao aplicativo que geralmente são difíceis inicialmente. Por exemplo, no caso de você querer configurar a autenticação do usuário, devemos levar em conta vários fatores como a segurança e um banco de dados cujas informações devem ser criptografadas. Vimos que isso poderia ser resolvido em questão de minutos com o Cognito, adicionando uma camada extra de segurança com o segundo fator de autenticação (MFA).

Por outro lado, configurar novos recursos na nuvem e gerenciá-los a partir do nosso projeto ou código geralmente parece difícil. Descobrimos que, com o Amplify, isso não é mais um problema porque com comandos simples e um SDK intuitivo, podemos obter todos os recursos em questão de segundos.

Para terminar, criamos uma API Rest segura cujo acesso deve ser feito através de tokens de usuário autenticados e cujo objetivo é que o usuário navegue pelo aplicativo e, consequentemente, faça chamadas para o back-end. Com o API Gateway, não só conseguimos fazer essa configuração facilmente, mas também oferecemos segurança e escalabilidade.

O objetivo deste post é ser um guia para começar a desenvolver um primeiro aplicativo nativo no Android com recursos da AWS com a ajuda do código carregado neste repositório público no aws-samples. Mesmo no blog temos esclarecido quais são alguns conceitos Android (como Atividade ou Fragmento), assumimos que quem lê tem algum conhecimento básico nessa área. De qualquer forma, seguindo o blog, lendo as documentações de serviços e o código este projeto pode ser executado sem problemas.

Para obter mais informações, consulte a documentação oficial do AWS Amplify.

 

 

 


Sobre o autor

Kevin Cortés é Arquiteto de Soluções na AWS. Ele trabalha auxiliando e apoiando clientes do setor público em sua jornada na nuvem da AWS, trabalhando em projetos que envolvem arquiteturas escaláveis serverless. Ele tem grande interesse nas áreas de desenvolvimento móvel, web, IOT e análise de dados.