Acelerando a modernização do legado mobile com StackSpot e IA Generativa

Foto de um celular com códigos aparecendo em sua tela sendo segurado por uma mão biônica. No fundo, estão telas de computador também com códigos.
Por meio de um case, conheça o processo geral de modernização de legado mobile usando StackSpot e IA Generativa.

A StackSpot AI busca estimular a eficiência dos times de desenvolvimento por meio da inteligência artificial. A modernização do legado mobile também é uma possibilidade com essa ferramenta, a migração para Compose resulta em 10x mais agilidade usando a StackSpot AI.

Neste artigo, conheça um tutorial para realizar essa modernização em seus projetos.

Introdução à modernização do legado mobile

Fugindo um pouco do “tecniquês”, IA Generativa pode ser considerada como um conjunto de técnicas e algoritmos de deep learning, com intuito de formar grandes modelos de linguagem (Large Language Models ou LLMs em Inglês).

Estes modelos são exaustivamente treinados com uma base de dados gigantesca de informações e que podem gerar conteúdo, interpretar e processar a partir de vários contextos, como texto, áudio, música, vídeo, dentre outros. 

Juntamente, com o Processamento de Linguagem Natural (PLN), as LLMs conseguem entender e interpretar o contexto que pode variar desde um pedido de dicas de como ganhar dinheiro de forma passiva até como interpretar códigos funcionais em determinada linguagem de programação e propor melhorias e boas práticas. 

Nessa linha, já temos várias empresas com modelos de linguagem sendo usados em seus produtos no mercado, como OpenAI (ChatGPT), Google (Bard), Microsoft (Copilot) e Meta (LLaMa 2). 

Para entender melhor, a imagem a seguir ilustra um exemplo de onde a IA Generativa se enquadra dentro das várias camadas da IA.

Imagem de círculos dentro uns dos outros respectivamente. O grande círculo representa Artificial Intelligence, em seguida outro círculo menor dentro do primeiro representa Machine Learning, depois outro menor representa Deep Learning e por último o menor círculo representa Generative AI. A fonte da imagem é Unraveling AI Complexity - A Comparative View of AI, Machine Learning, Deep Learning and Generative AI (Created by Dr. Lily Popova Zhuhadar, 07, 29, 2023)

Artificial Intelligence

É a camada mais externa que representa a disciplina da Inteligência Artificial, que utiliza técnicas e algoritmos para simular o comportamento humano, permitindo a aprendizagem, tomada de decisões, reconhecimento de padrões e resolução de problemas complexos, assim como a inteligência humana. 

Exemplos: Reconhecimento de Padrões, Visão Computacional, Sistemas Especialistas.

Machine Learning

A disciplina Aprendizagem de Máquina é um subconjunto do ramo da IA e está relacionada à utilização de algoritmos avançados de aprendizagem, de maneira supervisionada ou não-supervisionada, para detecção de padrões em base de dados grandes, permitindo que sistemas inteligentes aprendem e se adaptem.

Deep Learning

Na terceira camada, a Aprendizagem Profunda – um subconjunto da ML – utiliza redes neurais para realizar um processamento muito mais aprofundado para processamento e análise de dados, distribuído em várias camadas de redes neurais se comunicando entre si, extraindo informações de alto nível de acordo com entradas de dados.

Generative AI

Na camada mais interna, está a IA Generativa, que é um subconjunto da DL e utiliza modelos que geram conteúdos, como textos, imagens, vídeos e até códigos-fonte. 

Isso é possível pois na Gen AI (termo em Inglês como está sendo chamada) estes modelos são treinados de acordo com várias fontes de dados para detecção de padrões e produção de resultados, sem necessidade de instruções explícitas, usando tanto treinamento supervisionado quanto o não-supervisionado.

O que você irá aprender neste post?

Falando em gerar código, esse artigo tem como objetivo apresentar um case prático de como a IA Generativa pode nos auxiliar no dia a dia do desenvolvimento de software, aumentando exponencialmente a produtividade de forma inteligente e baseada no seu contexto. 

Para isso, utilizaremos a plataforma StackSpot AI e seus principais componentes, como Studio, Stack AI e Knowledge Source. Ao terminar este post, você conseguirá:

  • Entender quais os principais componentes da plataforma StackSpot AI;
  • Entender as principais vantagens e limitações de utilizar um Code Agent inteligente no dia a dia do desenvolvimento de software;
  • Entender o que é e como criar Studios, Stacks AI e Knowledge Sources;
  • Configurar o plugin da StackSpot AI no Visual Studio Code;
  • Elaborar e executar prompts mais assertivos para o processo de migração para Compose;
  • Entender as vantagens, desafios e limitações de utilizar IA Generativa e Engenharia de Prompts.

Consuma inovação,
comece a transformação

Assine nosso boletim informativo para se manter atualizado sobre as práticas recomendadas mais recentes para aproveitar a tecnologia para gerar impacto nos negócios

O legado no mobile: qual o custo desse débito técnico?

A partir do momento que uma feature entra em produção, ela já se torna legado e precisará de manutenção, seja evolutiva ou corretiva. Em mobile, especificamente no Android, o legado pode ser associado a várias características, como:

  • Bibliotecas com versões antigas;
  • APIs depreciadas ainda em utilização;
  • Código escrito em Java (Kotlin é a linguagem de programação oficial para desenvolvimento Android desde 2017);
  • Padrões antigos sendo utilizados, como Activity-only pattern, MVC (Model-View-Controller), MVP (Model-View-Presenter);
  • Mecanismos assíncronos e não vinculados ao ciclo de vida, como Threads, AsyncTask, Executors;
  • Formatos de View Bindings obsoletos que usa annotation processing, como, ButterKnife, AndroidAnnotations, etc.

Consequentemente, todo esse débito técnico tem um custo e impacta todas as jornadas, da pessoa desenvolvedora a pessoa cliente final. 

Builds mais lentas

Numa perspectiva de desenvolvimento, lidar com apps legados implica deixar builds mais lentas e demoradas, demorando entre 20 a 30 minutos a cada build, dependendo do tamanho do projeto. 

Clientes insatisfeitos com a experiência do app

Já para pessoas usuárias, o app pode apresentar problemas de performance, suscetível a vazamento de memórias e possíveis crashes em produção, influenciando diretamente na insatisfação do cliente e remoção do app do seu dispositivo ou notas muito baixas nas lojas.

Não promove inovação

Quando o app possui muitas partes antigas, é difícil inovar para trazer uma melhor experiência para clientes. 

Como o legado possui muitas limitações (ex: determinada feature só funciona a partir de determinada versão do SO), qualquer alteração nova, por mais que seja feita, é realizada com restrições para “não quebrar” o que já existe.

Menor qualidade e cobertura de testes

Com muitas partes antigas, a qualidade do app e do produto ficam prejudicadas, pois é muito improvável que testes unitários estejam sendo atualizados para refletir os novos cenários ou cenários atualizados. 

Mas remover o legado não é uma tarefa simples e rápida, pois envolve vários aspectos, de negócio e técnicos, como perda da fatia de Daily Access Users (DAU) com a atualização do SO, complexidade de atualização, tempo de entrega, convívio com o legado existente, dentre outros. 

Muitas vezes, o custo é tão alto (custo de horas de desenvolvimento vs. complexidade vs. tempo de entrega) que o legado acaba ficando esquecido ou não é analisado por completo. Muitas empresas acabam por optar por reescrever a aplicação do zero seguindo os padrões mais modernos e deixando o legado.

Estratégia de migração para Compose

Seguindo a sugestão da Google, os passos para migrar para Compose podem ser visualizados na imagem e animação abaixo.

Texto alternativo: Imagem que mostra os passos para migrar para Compose. Primeiro, uma imagem de captura de tela de celular com Views, uma seta indica a mesma imagem com Views e Compose e depois a mesma imagem com Compose.

Texto alternativo: Gif que representa a hierarquia de layouts através de um esquema com um círculo com Root View no topo, duas setas levam para dois círculos com View Groups e cada um desses View Groups levam para três círculos de View.

A imagem/animação acima representa a hierarquia de layouts de uma tela inteira usando Views e ViewGroups, no qual, numa abordagem bottom-up, cada View ou ViewGroup (containers e layouts) filha é migrada para Compose, subindo até chegar na View mãe (Root View).

Migração Manual: quais as principais dores?

Por mais que o Google e a comunidade tenha elaborado excelentes docs de migração, todo esse processo ainda é bastante custoso, pois é realizado manualmente, o que abre espaço para vários problemas:

  • Possíveis erros de mapeamento entre os atributos da View e Composable;
  • Aumento no tempo da modernização do legado, consequentemente demora no time-to-market;
  • Testes regressivos dos Composables mais longos;
  • Falta de padronização durante o processo de migração;
  • Tempo excessivo consultando a documentação para garantir uma migração mais assertiva possível.

Migração Assistida por Assistente de Código: quais as vantagens?

Já na migração assistida, utiliza-se um Assistente de Código (ou Code Assistant em inglês) que irá gerar aproximadamente 80% do código funcional necessário para rodar o seu software, direcionados de acordo com prompts ou comandos em Linguagem Natural informados ao assistente. 

Quais as vantagens dessa abordagem? Várias, dentre elas:

  • Aumento da velocidade em até 10x de produtividade e entrega de valor para clientes;
  • Drástica redução do tempo de modernização do legado, onde aproximadamente 80% do código é gerado automaticamente pelos assistentes de código, sendo que 20% do tempo ou carga cognitiva é ganho para aplicar em outros contextos, como melhoria da qualidade do projeto e do produto;
  • Processo altamente padronizado e seguindo as boas práticas e principais padrões do mercado;
  • Redução no tempo de consulta à documentação;
  • Geração de documentação automatizada.

Essa abordagem está ficando mais popular depois que plataformas como ChatGPT, Microsoft Copilot, Amazon CodeWhisperer, Studio Bot e, recentemente, StackSpot AI, assumiram um papel importante no dia a dia de pessoas desenvolvedoras de software, que passa a ter seu protagonismo compartilhado com esses “chatbots com superpoderes”. 

Qual a diferença da StackSpot AI para outros Code Assistants?

Nessa “Guerra dos Tronos” da IA Generativa, a StackSpot AI torna-se muito promissora, pois a mesma possui conceitos que são diferenciais, como Stack AI e Knowledge Sources.

Ambos fazem com que a geração de código seja realizada de forma muito mais contextualizada em termos de regras de negócio, core business, plataforma, padrões e muito mais, trazendo um código muito mais verossímil ao dia a dia da pessoa desenvolvedora.

Compose AI: Migrando de Views para Compose 10x mais rápido!

Para explorar todo o potencial da plataforma, o case será criar um conversor de XML para Kotlin, mais especificamente para Jetpack Compose. Para construir este conversor, serão realizados os seguintes passos:

  • Converter XML para Kotlin (Compose);
  • Adicionar ViewModel, não será exigido nenhum conhecimento prévio da StackSpot, pois durante a criação do conversor, serão explicados os conceitos essenciais. Caso queira se aprofundar na plataforma, sugiro a leitura dos Docs e outros artigos super interessantes que estão no blog do StackSpot.

Setup

Conhecendo a plataforma StackSpot AI

A plataforma StackSpot é subdividida em várias peças, como: StackSpot Enterprise Development Platform (EDP), StackSpot Cloud Services (CS) e StackSpot AI.

A última é a mais recente iniciativa da StackSpot para integrar IA Generativa no contexto de desenvolvimento, trazendo uma Dev Experience totalmente personalizada e hipercontextualizada.

Primeiramente, vamos conhecer o portal da StackSpot AI. Para isso, acesse o portal da plataforma StackSpot AI no link https://ai.stackspot.com. Depois, clique em Login para se autenticar na plataforma.

No momento da escrita desse post, a plataforma está em sua versão Beta e disponível para o público do mundo todo explorar o potencial da plataforma.

Captura de tela da página de StackSpot AI Alpha com o texto em destaque à esquerda Welcome to the alpha release of StackSpot AI. Ao centro temos um polígono com os símbolos de Sping e Java dentro. Do lado direito, está escrito Choose your project’ coding patterns e uma captura de tela da própria plataforma.

A autenticação pode ser realizada de duas maneiras: usuário e senha ou você pode usar sua própria conta do GitHub clicando no botão “Sign in with GitHub”. 

Captura de tela da página de login da StackSpot AI. Nela, temos Welcome to StackSpot AI, um campo para e-mail, um botão de Continue, outro de Sign in with GitHub e mais outro com Sign up with GitHub.

Depois de ter realizado a autenticação na plataforma, você irá para a página principal da plataforma, como mostra a imagem abaixo.

Captura de tela da página inicial da plataforma. Nela, acima está StackSpot AI e botões, do lado esquerdo os ícones de home, studios, workplace, code buddy, analytics e tutorials e no centro temos uma mensagem de boas-vindas e opções de criar studios, stack AI, workplace ou knowledge source.

Criando um Estúdio

De acordo com o StackSpot Docs, um Estúdio é onde o seu conteúdo é criado na StackSpot, basicamente um repositório global onde as Stacks estão vinculadas. 

É possível, por exemplo, ter um Estúdio que envolve stacks e plugins para determinada linguagem (ex: Java, Kotlin, Python), que serão aplicados de acordo com determinado contexto.

Para criar um Estúdio, aponte para a seção Studios no menu lateral esquerdo; ou clique no botão laranja no canto superior direito e depois em Studio.

Captura de tela da página quando se clica em studios e stk-android-studio.

Preencha as informações do Estúdio como Name, Slug (URL pública para o seu Estúdio)

Criando uma Stack AI

Uma Stack AI é uma abstração criada para trazer mais contextualização e fazer com que as respostas ou códigos gerados sejam mais precisos e fiéis ao contexto que você estiver imerso.

Informações como descrição, patterns (ex: MVVM, Clean Architecture, SOLID), atributos de cloud (ex: S3, EC2, EKS, etc), frameworks, dependências serão utilizadas para que, no momento que receber um prompt, o assistente de código “lembre”.

Para criar uma Stack AI, siga os mesmos passos que você executou para criar um Studio, só que dessa vez escolha Stack AI. Nesse momento, uma tela modal irá abrir com várias informações para você preencher, como descrição e nome.

Captura de tela da página de Create Stack AI especificamente com Stack Information. Nela aparecem as opções de name e use case.

Criando Knowledge Sources

Knowledge Sources, como o termo em inglês sugere, são fontes de conhecimento estáticas ou dinâmicas, utilizadas para trazer mais contexto para a engine de StackAI para interpretar, inferir e entender como realizar determinada ação, por exemplo, como converter códigos XML para Kotlin, no formato aceito pelo Compose. 

Atualmente, na versão Beta, a plataforma StackSpot AI disponibiliza dois tipos de Knowledge Sources:

  • API: arquivos JSON no formato swagger com todos os protocolos que deverão ser utilizados como base para a Stack AI. Ex: Bankly, uma API open source que possui várias funcionalidades de core bancário, bem como gerar boletos.
  • Snippets Groups: conjuntos de trechos de códigos que serão utilizados para trazer mais contexto para a engine da plataforma interpretar, inferir e entender como gerar códigos XML e em Kotlin, no formato esperado pelo Compose. 

Adicionando Knowledge Sources

Para esse case, iremos adicionar dois Snippets Groups: um contendo três códigos XML exemplos de layouts de Views, nos seguintes cenários: tela de Login, simples formulário e uma tela genérica de erros; da mesma forma, o outro snippet será composto por três snippets em Kotlin usando Composables nesses mesmos cenários. Então, para adicionar Snippets Groups:

  1. Clique no item “StackSpot AI” no meu ao lado esquerdo;
  2. Clique no sinal de “+” ao lado direito da área Knowledge Sources;
  3. Clique em Snippets Groups;
  4. Clique em Add snippet e vá adicionando os exemplos de código em cada snippet.

Pronto! Agora a base de fontes de conhecimento já está configurada. Na próxima seção, vamos praticar a Engenharia de Prompts para gerar código contextualizado.

Tela da StackSpot, nela está selecionado Knowledge Sources, mais em API e Snippets Group. 
Tela da StackSpot com as opções Knowledge Sources, Snippet e XML Snippet selecionadas. Ocupando parte da tela estão arquivos com códigos em xml.
Tela da StackSpot com as opções Knowledge Sources, Snippet e Compose Snippet selecionadas. Ocupando parte da tela estão arquivos com códigos em kotlin.

Desenvolvendo com StackSpot AI

Configurando o plugin

Agora, chegou a hora de implementar! Uma das grandes vantagens da StackSpot AI é a integração com as IDEs existentes. 

Para “conversar” com a StackSpot AI, precisamos baixar e instalar o plugin, disponível neste momento apenas para VS Code, mas já planejado para IntelliJ IDEA. 

Esse plugin permite não só interagir com o agente inteligente via prompts mas também realizar várias ações, ou Quick Commands, como gerar classes de teste para o código em questão, refatorar classes, dentre outras funcionalidades. 

Para continuar a configuração, siga as instruções no StackSpot AI QuickStart, que explica passo a passo como baixar, instalar e configurar o StackSpot AI no VS Code.

Vinculando o plugin ao workspace e stack

Após instalado, você precisará logar na conta sua conta da StackSpot AI, que pode ser realizada basicamente de duas formas: usando e-mail e senha ou usando a sua própria conta do GitHub, clicando em “Sign in with GitHub”. Após a autenticação ser realizada, você já pode começar a interagir com o StackSpot AI.

Logo em seguida, é apresentada a tela de boas-vindas ao StackSpot AI. Escolha mobile-code-migrations-ws e android-compose-migration-stack em Workspace e Stack AI, respectivamente. Depois disso, você poderá começar a “conversar” com o agente de código da StackSpot.

Aplicando a Engenharia de Prompts

A partir de agora, já podemos escrever alguns prompts para o assistente inteligente gerar códigos. O fluxo de desenvolvimento utilizará a arquitetura MVVM (Model-View-ViewModel). 

Para fins didáticos e validação na camada de apresentação, não iremos gerar código para camadas mais abaixo, como Repository, Data Sources ou Use Cases, mas fica como lição de casa para você implementar.

Passo 1: Gerando Composables a partir de Views

Prompt

Converta estas Views para Composables

login_layout.xml

Resposta do assistente de código:

@Composable
fun LoginScreen() {
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var rememberMe by remember { mutableStateOf(true) }

    Column(
        …
    ) {
        Image(
            painter = painterResource(id = R.mipmap.ic_launcher),
            contentDescription = null // Provide a proper content description for accessibility
        )
        Spacer(modifier = Modifier.height(16.dp))
        OutlinedTextField(
            …
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
        )
        Spacer(modifier = Modifier.height(8.dp))
        OutlinedTextField(
            …
            visualTransformation = PasswordVisualTransformation()
        )
        Spacer(modifier = Modifier.height(16.dp))
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Checkbox(
                checked = rememberMe,
                onCheckedChange = { rememberMe = it }
            )
            Text(
                text = "Remember Me",
                modifier = Modifier.padding(start = 8.dp)
            )
            Spacer(modifier = Modifier.weight(1f))
            Text(
                text = "Forgot Password?",
                modifier = Modifier.clickable { /* Handle forgot password */ }
            )
        }
        Spacer(modifier = Modifier.height(10.dp))
        Button(
            onClick = { /* Handle login */ },
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 10.dp)
        ) {
            Text("Log In")
        }
        Button(
            onClick = { /* Handle sign up */ },
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 10.dp)
        ) {
            Text("Sign Up")
        }
    }
}

Análise da Resposta

Vale enfatizar que, mesmo não mencionando o termo “XML” e “Compose”, a engine da StackSpot AI conseguiu inferir muito bem ao realizar a conversão para Composables.

Um exemplo disso foi a utilização do Column e Row para representar um arranjo de componentes dispostos na vertical ou na horizontal, respectivamente. Outro exemplo é o Spacer, que foi adicionado para representar os espaçamentos adicionados nas Views como marginTop, marginBottom ou marginHorizontal. 

A seguir, é exibida uma tabela comparativa entre as configurações das Views e Composables equivalentes após essa conversão.

ViewComposableDescrição
<LinearLayout orientation=”vertical” />ColumnArranja os componentes na vertical ou em formato de coluna
<LinearLayout orientation=”horizontal” />RowArranja os componentes na horizontal ou em formato de linha
<ImageView />ImageVisualiza uma imagem (drawables) na tela
<EditText />OutlinedTextFieldRepresenta um campo de texto
<CheckBox />
CheckboxRepresenta uma caixa de seleção
<Button />ButtonRepresenta um botão
Espaçamento (interno e externo)SpacerSpacer representa o container visual do espaçamento
Espaçamento(conteúdo)ModifierComponente que aplica as modificações no Composable, que pode ser de espaçamento, ajuste de conteúdo, alinhamento, etc.

Vale destacar que a engine também adiciona informações contextuais referente à arquitetura do Compose, como os operadores remember e mutableStateOf(), que são operadores do Compose relacionados ao gerenciamento de estados.

Antes de copiar e colar, você vai perceber que ao copiar e colar o código Composable gerado, um erro é exibido no momento que OutlinedTextField é chamado. 

Isso acontece pois esse Composable, assim como TextField, está como API experimental na lib do Material 3 do Compose. Para removê-lo, basta adicionar a anotação @ExperimentalMaterial3Api ao Composable LoginScreen.

@Composable

@ExperimentalMaterial3Api

fun LoginScreen() {

  // Outros composables vão aqui

}

Passo 2: Geração do Preview para LoginScreen

Prompt

“Agora gere um Preview para o composable principal”.

Resultado gerado pelo assistente de código:

LoginScreen.kt

@Composable

fun LoginScreen() {

    // ... (o código do LoginScreen vai aqui)

}

@Preview(showBackground = true)

@Composable

fun LoginScreenPreview() {

    // Aqui você pode definir um tema, se estiver usando um, ou simplesmente chamar o LoginScreen diretamente.

    // Se você estiver usando um tema, substitua 'Theme' pelo nome do seu tema.

    // Theme {

    //     LoginScreen()

    // }

    LoginScreen()

}

Análise da Resposta

Nesse momento, a StackSpot AI identificou o composable principal – LoginScreen – e aplicou a anotação @Preview para que seja possível visualizar o Composable no Android Studio.

Pré-visualizar o Composable no Android Studio

Agora iremos validar se o código gerado é funcional. Para isso, iremos executar o composable gerado para visualizar o Preview. 

No momento da escrita deste artigo, o VS Code ainda não possui suporte para desenvolvimento em Compose. Dessa forma, teremos que usar o Android Studio. Faça download e instale-o ou abra-o caso já tenha instalado. 

Crie um projeto novo como de costume indo em FileNewNew Project…A tela iniciará com o item Empty Activity selecionado. Deixe assim mesmo, pois esse template já virá com suporte à Compose. Finalize a criação do projeto e espere o projeto terminar o processo de build

Assim que terminar, crie uma nova classe Kotlin indo em New → Kotlin Class/File → Nomeie “LoginScreen” e salve. Navegue até o Preview, clique no botão do play ao lado e selecione “Run LoginScreenPreview”. O app será carregado no emulador ou no device físico, como mostra a imagem abaixo.

Tela com o app carregado no emulador ou device físico, nela é possível conferir Run ‘LoginScreenPreview’ selecionado.
Tela de um celular android, nela aparece email e password como campos a serem preenchidos para fazer login.

Como é possível verificar, o agente de código gerou um código funcional, onde é possível visualizar os Composables na tela, tudo isso em segundos! Manualmente, provavelmente esse processo iria durar muito mais tempo. 

Claro que o código ainda não está 100% completo, ainda falta aplicar ações para logar e validar as credenciais, que iremos fazer isso no próximo passo, utilizando um ViewModel.

Troubleshooting em devices rodando Oreo (Android 12)

Caso você esteja executando o preview em um device ou emulador com Android 12 ou superior, provavelmente o app irá quebrar ao executar, exibindo o seguinte erro: java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG. 

Ele acontece pois, no momento da escrita deste artigo, o Compose ainda não suporta Adaptive Icons. Uma solução de contorno para isso pode ser visualizada neste link. Outra alternativa é atualizar o Image para utilizar PNG, JPG ou WebP.

Passo 3: Geração do ViewModel

Prompt 

“Agora adicione um ViewModel com as lógicas de validação de credenciais válida e de erro. Uma credencial será válida se o campo de email for igual a {seu_email} e senha igual a {sua_senha}. Caso contrário, será considerado um cenário de erro.

Resultado gerado pelo assistente de código:

LoginUiState.kt

class LoginUiState(

    val email: String = "",

    val password: String = "",

    val isValidCredentials: Boolean? = null

)

LoginViewModel.kt

class LoginViewModel : ViewModel() {

    // Define the UI state with LiveData/State API/Flow

    var uiState by mutableStateOf(LoginUiState())

        private set

    // Constants for valid credentials (switch to your correct values)

    private val validEmail = "{seu_email}"

    private val validPassword = "{sua_senha}"

    // Validates credentials

    fun validateCredentials(email: String, password: String) {

        if (email == validEmail && password == validPassword) {

            // Credenciais válidas

            uiState = uiState.copy(isError = false, isSuccess = true)

        } else {

            // Credenciais inválidas

            uiState = uiState.copy(isError = true, isSuccess = false)

        }

    }

}

LoginScreen + LoginViewModel.kt

@Composable

fun LoginScreen(viewModel: LoginViewModel = viewModel()) {

  val uiState by viewModel.uiState.collectAsState()

    Column(

        modifier = Modifier

            .fillMaxSize()

            .padding(horizontal = 16.dp),

        horizontalAlignment = Alignment.CenterHorizontally

    ) {

        // ... outros composables ...

        Button(

            onClick = { viewModel.validateCredentials(uiState.email, uiState.password) },

            modifier = Modifier

                .fillMaxWidth()

                .padding(bottom = 10.dp)

        ) {

            Text("Log In")

        }

        // Exibir mensagem de erro se as credenciais forem inválidas

        if (uiState.isValidCredentials == false) {

            Text("Credenciais inválidas", color = Color.Red)

        }

    }

}

Análise da Resposta

Durante a criação da Stack AI, foi informado para utilizar MVVM como um dos patterns. Dessa forma, o assistente de código “lembrou” que o ViewModel faz parte do padrão de apresentação e já gerou não apenas o código em si, mas separou em uma classe de estados LoginUiState as informações e uma flag para indicar que as credenciais são válidas ou não.

Além disso, o agente de código inteligente atualizou também o Composable principal para referenciar uiState exposto no ViewModel, podendo chamar o método collectAsState() para observar mudanças nos estados dos campos encapsulados pela classe LoginUiState.

Vale também ressaltar que o agente de código atualizou o principal composable para receber um ViewModel como parâmetro e também vinculou a chamada ao método  validateCredentials() do ViewModel, de acordo com o trecho abaixo:

@Composable

fun LoginScreen(viewModel: LoginViewModel = viewModel()) {

  val uiState by viewModel.uiState.collectAsState()

  // trecho de outros composables vem aqui

  Button(

           onClick = {

                viewModel.validateCredentials(uiState.email, uiState.password)

           },

            modifier = Modifier

                .fillMaxWidth()

                .padding(bottom = 10.dp)

        ) {

            Text("Log In")

        }

    // Exibir mensagem de erro se as credenciais forem inválidas

        if (uiState.errorMessage != null) {

            Text("Credenciais inválidas", color = Color.Red)

        } else {

      Text("Credenciais válidas", color = Color.Green)

    }

}

Uma dica importante

Ao executar o primeiro prompt, a engine gerou uma implementação para o ViewModel baseado em LiveData (componente que notifica a UI sobre mudança de estados). 

Apesar de não estar errado, essa abordagem já não é mais a recomendação oficial da Google, que sugere a utilização do Flow, um mecanismo de Kotlin Coroutines mais adequado para gerenciar fluxo de dados ou streams

Sendo assim, caso isso aconteça, basta refazer o prompt solicitando para que o agente de código gere o ViewModel utilizando Flow.

Executando o Preview para validar as credenciais

Atualize o código de LoginScreen e crie uma nova classe Kotlin para LoginViewModel, como realizado no passo anterior. Para simular um cenário mais próximo do real, vamos alterar os atributos referente ao email e senha para “me@stackspot.com” e “123456”, respectivamente. Fica a seu critério utilizar qualquer valor para esses atributos.

Depois disso, basta navegar até LoginScreenPreview e clicar no botão de play para para executar o aplicativo no emulador ou device. Ao informar as credenciais esperadas, a mensagem “Credenciais válidas” é exibida. Caso contrário, a mensagem “Credenciais inválidas” é exibida.

Passo 4: Refatorando LoginUiState para representar os estados Authenticating, Success e Error

Prompt

Agora atualize LoginUiState para utilizar os seguintes estados: Authenticating, Success e Error.

Resultado gerado pelo assistente de código:

LoginUiState.kt

sealed class LoginUiState {

    object Authenticating : LoginUiState()

    object Success : LoginUiState()

    data class Error(val message: String) : LoginUiState()

    data class Data(

      val email: String = "", 

    val password: String = "",  

    val isRememberMeChecked: Boolean = false

  ) : LoginUiState()

}

LoginViewModel.kt


class LoginViewModel : ViewModel() {

    // MutableStateFlow para manter e emitir mudanças no estado da UI

    private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Data())

    // StateFlow para expor apenas uma versão imutável do estado da UI

    val uiState: StateFlow<LoginUiState> = _uiState

    // Função para atualizar o email

    fun updateEmail(email: String) {

        val currentState = _uiState.value as? LoginUiState.Data

        currentState?.let {

            _uiState.value = it.copy(email = email)

        }

    }

    // Função para atualizar a senha

    fun updatePassword(password: String) {

        val currentState = _uiState.value as? LoginUiState.Data

        currentState?.let {

            _uiState.value = it.copy(password = password)

        }

    }

    // Função para atualizar o estado do checkbox "Lembrar-me"

    fun updateRememberMe(isChecked: Boolean) {

        val currentState = _uiState.value as? LoginUiState.Data

        currentState?.let {

            _uiState.value = it.copy(isRememberMeChecked = isChecked)

        }

    }

    // Função para iniciar o processo de autenticação

    fun onLoginClicked() {

        viewModelScope.launch {

            // Atualiza o estado da UI para autenticando

            _uiState.value = LoginUiState.Authenticating

            try {

                // Aqui você colocaria a lógica de autenticação

                // Por exemplo, chamar um repositório que faz a autenticação

                val result = authenticateUser()

                // Atualiza o estado da UI para sucesso ou erro com base no resultado

                _uiState.value = if (result) LoginUiState.Success else LoginUiState.Error("Falha na autenticação")

            } catch (e: Exception) {

                // Atualiza o estado da UI para erro com a mensagem de exceção

                _uiState.value = LoginUiState.Error(e.message ?: "Invalid Credentials")

            }

        }

    }

    suspend fun onLoginClicked(): Boolean {

        // Simula uma chamada de rede ou de banco de dados

        // Aqui você implementaria a lógica de autenticação real

        return true // Retorna true para simular um sucesso na autenticação

    }

}

Complementando o código manualmente

Observe que o método onLoginClicked() foi gerado sem os parâmetros de login e senha e a função para validar as credenciais, pois não informamos isso no prompt. Não se esqueça de alterar também a chamada a onClick do botão de login para chamar viewModel.onLoginClicked(dataState.email, dataState.password). 

Depois, vamos implementar novamente a função para validação de credenciais também. Para finalizar, vamos adicionar uma chamada ao método delay() com duração de dois segundos, para simular uma chamada à API de autenticação. 

Dessa forma, o código final fica assim:

class LoginViewModel : ViewModel() {

    private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Data())

    val uiState: StateFlow<LoginUiState> = _uiState

companion object {

    const val VALID_EMAIL = "me@stackspot.com"

    const val VALID_PASSWORD = "123456"

    const val MOCK_DELAY = 2_000L

}

// outros métodos omitidos aqui

                                                                                                                                                                                                                       fun onLoginClicked(email: String, password: String) {

        // Simulate authentication process

        _uiState.value = LoginUiState.Authenticating

        viewModelScope.launch {

            delay(MOCK_DELAY)

            _uiState.value = 

                if (hasValidCredentials()){

                   LoginUiState.Success

                } else {

                    LoginUiState.Error("Invalid credentials")

                } 

        }

    }

}

fun hasValidCredentials() =

    VALID_EMAIL == email && VALID_PASSWORD == password

LoginScreen.kt

@Composable

@ExperimentalMaterial3Api

fun LoginScreen(viewModel: LoginViewModel = viewModel()) {

    val uiState by viewModel.uiState.collectAsState()

    Column(

        modifier = Modifier

            .fillMaxSize()

            .padding(16.dp),

        horizontalAlignment = Alignment.CenterHorizontally,

        verticalArrangement = Arrangement.Center

    ) {

        when (uiState) {

            is LoginUiState.Data -> {

                val dataState = uiState as LoginUiState.Data

                TextField(

                    value = dataState.email,

                    onValueChange = { viewModel.onEmailChanged(it) },

                    label = { Text("Email") },

                    modifier = Modifier.fillMaxWidth()

                )

                Spacer(modifier = Modifier.height(8.dp))

                TextField(

                    value = dataState.password,

                    onValueChange = { viewModel.onPasswordChanged(it) },

                    label = { Text("Password") },

                    modifier = Modifier.fillMaxWidth()

                )

                Spacer(modifier = Modifier.height(8.dp))

                Row(

                    modifier = Modifier.fillMaxWidth(),

                    verticalAlignment = Alignment.CenterVertically

                ) {

                    Checkbox(

                        checked = dataState.isRememberMeChecked,

                        onCheckedChange = { viewModel.onRememberMeChecked(it) }

                    )

                    Text(text = "Remember Me")

                }

                Spacer(modifier = Modifier.height(16.dp))

                Button(

                    onClick = { loginViewModel.onLoginClicked(dataState.email, dataState.password) },

                    modifier = Modifier.fillMaxWidth()

                ) {

                    Text("Login")

                }

            }

            LoginUiState.Authenticating -> {

                Row(

         modifier = Modifier.fillMaxWidth(),

                    verticalAlignment = Alignment.CenterVertically

       ) {

         Text("Authenticating...")

         Spacer(modifier = Modifier.padding(16.dp))

           CircularProgressIndicator() 

       }

            }

            is LoginUiState.Success -> {

                Text("Login Successful", Color.Green)

            }

            is LoginUiState.Error -> {

                Text((uiState as LoginUiState.Error).message, Color.Red)

            }

        }

    }

Análise da Resposta

No trecho acima, o ViewModel não estava vinculado à Activity principal, pois até o momento estávamos usando apenas o Preview. Agora, a classe MainActivity deverá chamar o Composable LoginScreen.

Passo 5: Chamar LoginScreen no contexto da Activity.

Prompt

Chame o principal composable dentro da MainActivity

Resultado gerado pelo assistente de código:

@ExperimentalMaterial3Api

class MainActivity : ComponentActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {

       super.onCreate(savedInstanceState)

       setContent {

           ComposeAITheme {

               LoginScreen(viewModel = viewModel())

           }

       }

   }

}

Análise da Resposta

O método setContent foi adicionado e realizada a chamada ao contexto do Compose, no caso o LoginScreen que utiliza o tema ComposeAITheme, que foi gerado pelo Android Studio automaticamente após a criação do projeto. 

Como a classe MainActivity está chamando o composable, você vai perceber o mesmo erro relacionado à API Experimental do Material 3. Assim como feito anteriormente, basta adicionar a anotação @ExperimentalMaterial3Api na classe MainActivity.

Executando o app no emulador ou device físico

Agora que finalizamos a implementação, vamos executar o app no Android Studio para garantir que tanto a interface visual quanto o comportamento estão de acordo com as regras de negócio. 

Clique no botão que executa o aplicativo. Caso não tenha nenhum device físico disponível, utilize o emulador. As imagens abaixo mostram o app no cenário de credenciais válidas quanto credenciais inválidas.

Cenário 1: Credenciais válidas

Três imagens lado a lado da tela de login da StackSpot. A primeira possui a logo da StackSpot, o polígono formado por pequenas bolas em tons de laranja, e os campos e-mail e password. A segunda também possui a logo, mas com Authenticating. O último possui a logo e Login Successful.

Cenário 2: Credenciais inválidas

Três imagens lado a lado da tela de login da StackSpot. A primeira possui a logo da StackSpot, o polígono formado por pequenas bolas em tons de laranja, e os campos e-mail e password. A segunda também possui a logo, mas com Authenticating. O último possui a logo e Invalid credentials.

Destaques

  • Conversão de código bem assertiva: toda vez que a conversão de código foi utilizada, os resultados foram mais de 80% assertivos, muitas vezes até adicionando mais características no código de destino (ex: PasswordVisualTransformation no campo de senha).
  • Códigos gerados sem erros de sintaxe: os códigos que foram gerados não apresentaram nenhum erro de sintaxe, apesar de ter sido necessário adicionar a anotação @ExperimentalMaterial3Api para o código ficar funcional. Mas isso é por conta da característica e versão do Compose. 
  • Padrões e boas práticas sendo respeitados: no momento da criação da Stack AI para este exemplo, MVVM, Clean Architecture e SOLID foram informados como Patterns a serem utilizados. No momento da geração de código, o assistente de código da StackSpot AI “lembrou” desses padrões.
  • Rapidez nas respostas dos prompts: na maioria dos casos, o resultado não demorou mais do que 10 segundos para prompts simples; e 30 segundos no máximo para prompts mais complexos. 
  • Ganho de produtividade: por conta da rapidez, o ganho de produtividade é absurdo, pois tarefas que poderiam demorar provavelmente dias, com StackSpot AI dura aproximadamente segundos!
  • Interface amigável: tanto o portal quanto o plugin possuem interfaces bem amigáveis, tornando a Dev Experience muito melhor e mais produtiva.

Desafios e limitações

Durante a implementação deste exemplo, foram encontrados alguns desafios que estão mais relacionados às LLMs do que à plataforma da StackSpot AI:

  • Definir um prompt mais assertivo: o grande desafio no contexto da IA Generativa é utilizar um prompt para garantir um resultado mais próximo do esperado. Porém, para isso, é necessário achar o prompt mais ideal possível. Como conseguimos isso? Através de muitos experimentos, até achar o que faça sentido para o seu negócio.
  • Perda de contexto entre prompts: algumas vezes, o code assistant atualizava o código, porém “esquecia” de alguns parâmetros que já tinham sido gerados, sendo necessário realizar um prompt adicional para corrigir. Ex: atualização do LoginViewModel quando LoginUiState foi atualizado para tratar estados.
  • Utilização de duas IDEs: usar o VS Code para gerar os prompts e o Android Studio para pré-visualização e execução do app, por mais que não tenha sido trabalhoso, exigia copiar de uma IDE, colar e executar na outra, organizar imports, etc. Atualmente, o plugin da StackSpot AI não é totalmente compatível com o Android Studio, pois a IDE – que tem base no IntelliJ – foi projetada com uma versão resumida da plataforma, suportando apenas até a versão 2022.*. Entretanto, existe uma solução de contorno para fazer com que o plugin funcione no Android Studio, mas esse ajuste está fora do escopo deste artigo.
  • Perda do histórico: algumas vezes, ao voltar para o VS Code, foi necessário logar novamente e executar todos os prompts, muitas vezes gerando resultados totalmente diferentes, uma vez que o histórico dos prompts e respostas não são mantidos.

Conclusão

Esse artigo demonstrou como podemos nos beneficiar da utilização da IA Generativa para agilizar e sistematizar o processo de modernização do legado mobile de forma mais produtiva, rápida e hipercontextualizada utilizando a StackSpot AI. 

Para isso, uma tela de login foi utilizada como exemplo, gerando os Composables a partir do XML da views, seguindo o padrão MVVM e UI State Pattern, gerando também o ViewModel e aplicando a validação das credenciais, tudo através de prompts.

Dúvidas que você pode ter

Você deve estar se perguntando: vale a pena mesmo utilizar Code Assistants durante o ciclo de desenvolvimento? Sem dúvida! Principalmente pelo ganho de produtividade em cima de tarefas cotidianas que atualmente são feitas manualmente por devs. 

Então quer dizer que Code Assistants irão substituir as pessoas engenheiras de Software? Vale ressaltar e desmistificar o mito da “IA predatória”: por mais que a geração de código esteja sendo realizada pelos assistentes inteligentes, ela ainda não é 100% funcional, pois ela ainda dependerá do contexto ao qual está sendo aplicado. 

Sendo assim, é e sempre será papel da pessoa engenheira de software fornecer os inputs corretos e complementar o código para que o mesmo fique 100% funcional. Então, antes de enviar o código para produção, é preciso validar se a implementação gerada está seguindo os requisitos de negócio.

Agora é hora de colocar em prática o que você aprendeu aqui, faça seu login na plataforma e comece a usar nosso assistente de código.

Consuma inovação,
comece a transformação

Assine nosso boletim informativo para se manter atualizado sobre as práticas recomendadas mais recentes para aproveitar a tecnologia para gerar impacto nos negócios

Summary

Related posts

*Material em Inglês

Baixe grátis o eBook
e descubra caminhos para evoluir sua empresa