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

Capa do artigo "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

Antes de tudo, 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).

Assim, 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 do artigo sobre modernização de legado mobile com  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. 

Por exemplo, reconhecimento de Padrões, Visão Computacional e 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?

A princípio, 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. Dessa forma, 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.

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.

Por fim, a consequência é que todo esse débito técnico tem um custo e impacta todas as jornadas, da pessoa desenvolvedora a pessoa cliente final. 

Builds mais lentas

Por outro lado, 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

Porém 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 fica esquecido ou não é analisado por completo. Por isso, 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, confira os passos para migrar para Compose:

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. 

Mas afinal 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.

Afinal, ambos fazem com que a geração de código seja 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, realiza-se 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.

No momento da escrita desse post, a plataforma está em sua versão Beta.

Imagem do artigo sobre modernização de legado mobile com  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.

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

Imagem do artigo sobre modernização de legado mobile com  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 vinculamos as Stacks. 

É possível, por exemplo, ter um Estúdio que envolve stacks e plugins para determinada linguagem (ex: Java, Kotlin, Python), 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.

Imagem do artigo sobre modernização de legado mobile com 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.

Imagem do artigo sobre modernização de legado mobile com  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 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 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, adicionamos 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 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.

Imagem do artigo sobre modernização de legado mobile com  tela da StackSpot, nela está selecionado Knowledge Sources, mais em API e Snippets Group. 
Imagem do artigo sobre modernização de legado mobile com  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.
Imagem do artigo sobre modernização de legado mobile com  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! Aliás 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 na StackSpot AI QuickStart, que explica passo a passo como baixar, instalar e configurar a 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 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, você já começa a interagir com a StackSpot AI.

Logo em seguida, apresentamos a tela de boas-vindas à 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, adicionado para representar os espaçamentos adicionados nas Views como marginTop, marginBottom ou marginHorizontal. 

A seguir, exibimos 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.

Aliás, antes de copiar e colar, perceba 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

Em seguida, 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. Então, 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.

Imagem do artigo sobre modernização de legado mobile com  tela com o app carregado no emulador ou device físico, nela é possível conferir Run ‘LoginScreenPreview’ selecionado.
Imagem do artigo sobre modernização de legado mobile com  tela de um celular android, nela aparece email e password como campos a serem preenchidos para fazer login.

Por fim, 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. Então, fica a seu critério utilizar qualquer valor para esses atributos.

Em seguida, 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, aparece a mensagem “Credenciais inválidas”.

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 geramos o método onLoginClicked() 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). 

Em seguida, vamos implementar novamente a função para validação de credenciais também. Por fim, 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. A seguir, as imagens mostram o app no cenário de credenciais válidas quanto credenciais inválidas.

Cenário 1: Credenciais válidas

Imagem do artigo sobre modernização de legado mobile com  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

Imagem do artigo sobre modernização de legado mobile com  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 usamos a conversão de código, 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 gerados que não apresentaram nenhum erro de sintaxe, apesar de 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

Em suma, durante a implementação deste exemplo, encontramos 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á gerados, sendo necessário realizar um prompt adicional para corrigir. Por exemplo, a 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

Em suma, 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. Portanto, antes de enviar o código para produção, é preciso validar se a implementação gerada está seguindo os requisitos de negócio.

Aproveite e marque uma demo com nossos times de especialistas, clique no botão abaixo.

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

Related posts