Desenvolvendo aplicativo com banco de dados SQL em Flutter e publicando na Play Store

Desenvolvendo aplicativo com banco de dados SQL em Flutter e publicando na Play Store

Uma forma fácil e prática de ter acesso a sistemas de controle e bancos de dados, é através do uso de aplicativos para celular, pois nos dias de hoje, praticamente todo mundo anda com um no bolso, seja ele Android ou iOS.

Um framework bastante popular para o desenvolvimento de aplicativos para múltiplas plataformas é o Flutter, que utilizaremos ao longo deste artigo para criar e publicar um aplicativo com banco de dados na Play Store.

Flutter e a linguagem Dart

O Flutter foi um projeto desenvolvido pelo Google e lançado em 2017, com o objetivo de ser uma alternativa mais moderna e eficiente para o desenvolvimento de aplicativos, ele utiliza como base a linguagem Dart, que também foi desenvolvida pelo Google.

Para quem já conhece linguagens como o Python, JavaScript e C++, não vai ter muita dificuldade com Dart, pois a sintaxe é semelhante.

Ela é orientada a objetos, tem tipagem opcional e um modo de compilação Just-In-Time (JIT), o que permite ao desenvolvedor uma boa flexibilidade na forma de escrever o código. Segue um exemplo abaixo da criação de funções e loops em Dart, respectivamente:

void printMessage(String message) {
    print(message);
}

for (int i = 0; i < 10; i++) {
    print(i);
}


O Flutter utiliza bastante o conceito de orientação a objetos do Dart, ele possui uma arquitetura baseada em widgets, que são objetos da classe widget e representam os elementos que serão desenhados na tela. Segue abaixo um exemplo de como as classes e objetos em Dart funcionam:

class Pessoa {
    String nome;
    int idade;

    Pessoa(this.nome, this.idade); // Esse é o construtor, ele possui o mesmo nome da classe

    void cumprimentar(){
        print("Prazer, meu nome é $nome e tenho $idade anos");
    }
}

void main() {
    Pessoa joao = Pessoa("João", 15);
    joao.cumprimentar();
}


Para iniciar o projeto, temos que configurar o ambiente. Baixe o Flutter SDK no site oficial, depois baixe e instale o Android Studio, nele abra o SDK Manager e baixe o Android SDK e o Command Line Tools. Talvez seja necessário adicionar o caminho do SDK ao PATH do sistema, então quando o Flutter estiver instalado, utilize o comando flutter doctor no terminal para saber se falta algo. Tenha certeza de que a versão mais atualizada do Java também está instalada, pois isso pode gerar um erro.

Você pode iniciar um dispositivo virtual ou utilizar um celular conectado ao computador para testar o aplicativo. Para ver a lista de dispositivos disponíveis, basta utilizar o comando:

sdkmanager --list


Com a lista, pode tentar criar um emulador com o seguinte comando, trocando apenas o nome do dispositivo e o nome do pacote por um dos que estiver na lista:

avdmanager create avd --name Nexus_5X_API_31 --package "system-images;android-31;google_apis;x86_64" --device "Nexus 5X" --tag google_apis --abi x86_64


Em caso de erro, abra o android Studio, vá na aba gerenciador de dispositivos e crie manualmente um novo dispositivo, escolhendo a resolução do dispositivo e a versão do Android. Feito isso, o emulador já estará disponível para o Flutter.

flutter emulators


O comando acima vai listar os emuladores que estiverem disponíveis, pegue o nome do emulador e rode o mesmo comando com a opção launch seguido do nome do dispositivo:

flutter emulators --launch Nexus_5X_API_31


A outra forma de testar o aplicativo flutter é com um dispositivo físico, a vantagem deste método é que o seu computador não vai ficar mais lento para ter que rodar o emulador. Para isso é necessário conectar o celular ao computador via USB, depois procurar pela opção no celular para liberar o debug por USB, que geralmente se encontra nas configurações de desenvolvedor, assim, quando rodar o flutter doctor um novo dispositivo deve aparecer como disponível.

Com tudo configurado, basta criar o projeto utilizando o comando flutter create, seguido do nome do seu aplicativo:

flutter create task_list


Esse comando vai gerar um projeto Flutter contendo várias pastas e arquivos que vão servir para transformar o código que escrevemos em um executável para diferentes plataformas, sendo elas: Android, iOS, Windows, Linux, Mac e web. Como estamos focando no Android neste artigo, pode apagar as pastas referentes às outras plataformas. O código propriamente fica na pasta lib no arquivo main.dart.

Ao criar o projeto, o arquivo main.dart já vai vir com um código de exemplo, no início é importado o pacote flutter/material.dart, que vai dar acesso a vários componentes já construídos para adicionar ao seu código, desde elementos simples como texto, até elementos complexos de interface como o PageView que serve para o layout e fazer animações. Combinando estes componentes em uma estrutura de árvore, podemos criar uma interface complexa.

A função main vai ser onde o programa começa, nela chamaremos a função do flutter runApp, que vai receber como argumento um único widget e aumentá-lo para o tamanho da tela, independente do dispositivo utilizado, este widget que representa a aplicação é uma classe que extende a classe StatelessWidget, e vai receber um parâmetro opcional que é a chave, ela serve para identificar a instância da classe. O StatelessWidget possui uma função interna chamada build, que vai receber como parâmetro o contexto e retornar um widget, será ela que iremos sobrescrever para gerar os nossos widgets:

import "package:flutter/material.dart"

void main(){
    runApp(TaskListApp());
}

class TaskListApp extends StatelessWidget {
    const TaskListApp({Key? key}) : super(key: key);

    @override build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
                appBar: AppBar(
                    backgroundColor: Colors.red,
                    title: const Text('Task List'),
                ),
            ),
        );
    }
}


O Widget MaterialApp será retornado pelo build da classe TaskListApp e dentro dele haverá o Scaffold, que é onde colocaremos todos os outros widgets da nossa aplicação. O mais comum deles para a criação do layout da aplicação são os containers, eles recebem um child como argumento e podem ser customizados alterando as margens o padding, cor tamanho e largura:

Scaffold(
    appBar: AppBar(
        backgroundColor: Colors.red,
        title: const Text('Task List'),
    ),
    body: Container(
        padding: const EdgeInsets.all(10.0),
        margin: const EdgeInsets.all(5.0),
        color: Colors.red,
        width:100,
        height:100,
        child: Text('Tarefa 1'),
    ),
)


Outros widgets que são mais focados são o Center para centralizar ou o SizedBox para criar um retângulo com tamanho fixo. Para organizar múltiplos elementos em linhas ou colunas, podemos utilizar o Column e o Row, que diferente do Container, eles recebem múltiplos childs. Como argumento, podemos também, alterando o alinhamento no axis principal ou no outro axis, mudar a posição dos elementos na lista:

body: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: const [
        Icon(Icons.person),
        Icon(Icons.leaderboard),
        Icon(Icons.backpack)
    ],
)


Todos os child por padrão tem o mesmo tamanho, mas é possível deixar mais espaço para um, colocando eles dentro do Expanded e alterando a propriedade flex:

body: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: const [
        Expanded(
            flex: 4,
            child: Icon(Icons.person),
        ),
        Expanded(
            flex: 2,
            child: Icon(Icons.leaderboard),
        ),
        Icon(Icons.backpack)
    ],
)


Se você precisar colocar mais elementos dentro do Row ou Column, fazendo com que seu tamanho ultrapasse o tamanho da tela do dispositivo, um erro vai aparecer. Para contornar este problema, podemos utilizar o ListView no lugar, que permite fazer o scroll com os elementos:

body: ListView(
    scrollDirection: Axis.vertical,
    children: [
        Container(
            width: 100,
            height: 400,
            color: Colors.yellow,
            child: const Center(child: Icon(Icons.backpack))
        ),
        Container(
            width: 100,
            height: 400,
            color: Colors.green,
            child: const Center(child: Icon(Icons.leaderboard))
        ),
        Container(
            width: 100,
            height: 400,
            color: Colors.red,
            child: const Center(child: Icon(Icons.person)),
        )
    ]
),


Outra vantagem de utilizar o ListView, é que ele possui um builder, uma função que permite criar os elementos dinamicamente, com isso você pode criar arrays de strings, números ou até mesmo de widgets e colocá-los dentro do ListView:

List<String> lista_tarefas = ['Fazer a feira', 'Lavar a louça', 'Pagar o boleto']

body: ListView.builder(
    scrollDirection: Axis.vertical,
    itemCount: 5,
    itemBuilder: (BuildContext context, int index) {
        return Container(
            width: 100,
            height: 100,
            color: Colors.red,
            child: Center(child: Text(lista_tarefas[index])),
        );
    }
)


O itemCount vai determinar quantos elementos vão ter dentro do ListView, enquanto o itemBuilder é uma função que vai retornar um elemento para cada elemento no itemCount. Neste caso temos um container com um texto centralizado, note que o texto está vindo da lista, sendo acessado pelo parâmetro index, que é o contador do itemBuilder.

A classe StatelessWidget não tem estado, o que significa que os dados dentro dela não são alterados, mas existe outra classe onde podemos alterar variáveis dinamicamente, que é o StatefullWidget, ele é separado em duas classes, a primeira mantêm o widget propriamente, imutável, já a segunda é o estado do widget, nesta segunda classe podemos definir variáveis que podem ser alteradas com a função nativa setState:

class ListaTarefas extends StatefulWidget {
  const ListaTarefas({Key? key}) : super(key: key);

  @override
  State<ListaTarefas> createState() => _ListaTarefasState();
}

class _ListaTarefasState extends State<ListaTarefas> {

  List<String> tasks = [‘Tarefa 1’, 'Tarefa 2', 'Tarefa 3', 'Tarefa 4', 'Tarefa 5'];
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          height: 50,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              const Text('Data'),
              FloatingActionButton(
                child: const Icon(Icons.add),
                onPressed: () {
                  setState(() {
                    tasks.add('value');
                  });
                }
              )
            ],
          ),
        ),
        Expanded(
          child: ListView.builder(
                scrollDirection: Axis.vertical,
                itemCount: teste.length,
                itemBuilder: (BuildContext context, int index){
                  return Container(
                    width: 100,
                    height: 100,
                    color: Colors.red,
                    child: Center(child: Text(tasks[index])),
                  );
                },
          ),
        ),
      ],
    );
  }
}



Banco de dados SQL com Flutter

Algumas vezes, seu aplicativo vai precisar salvar dados no dispositivo, se eles forem estruturados, você utilizará um banco SQL e a plataforma de SQL que está disponível em quase todo dispositivo hoje em dia, incluindo smartphones. O SQLite é pequeno e utiliza um único arquivo para armazenar os seus dados. O Flutter tem várias formas de desenvolver aplicativos com bancos SQL, uma das bibliotecas mais utilizadas é o sqflite.

Para adicionar a biblioteca, abra o arquivo pubspec.yaml e em dependencies adicione o sqflite da seguinte forma:

dependencies:
sqflite: ^2.2.8+2


Depois de adicionar a biblioteca, no arquivo main.dart, você deve importá-la no início do arquivo:

import "package:sqflite/sqflite.dart"


As operações no banco de dados podem ser feitas utilizando uma classe auxiliar, dentro dela vão conter diferentes métodos que vão servir para criar uma instância do banco de dados e fazer o CRUD (Create, Read, Update e Delete), elas serão funções assíncronas e poderão ser chamadas a partir do click de botões em widgets do tipo StatefullWidget, conforme o exemplo abaixo:

class SQLHelper {
    // Este método cria o banco de dados e uma tabela
    static Future<void> createTables(Database database) async {
        await database.execute("""CREATE TABLE items(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title TEXT, description TEXT, createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""");
    }

    // Este método cria uma instância do banco de dados e cria a tabela se ela já não existir
    static Future<Database> db() async {
        return openDatabase('tasks.db', version: 1, onCreate(Database database, int version) async {
        await createTables(database);});
    }

    static Future<int> createItem(String title, String? description) async {
        final db = await SQLHelper.db();

        final data = {'title': title, 'description': description};
        final id = await db.insert('items', data, conflictAlgorithm: conflictAlgorithm.replace);
        return id;
    }

    static Future<List<Map<String, dynamic>>> getItems() async {
        final db = await SQLHelper.db();
        return db.query('items', orderBy: "id");
    }

    static Future<List<Map<String, dynamic>>> getItem(int id) async {
        final db = await SQLHelper.db();
        return db.query('items', where: "id = ?", whereArgs: [id], limit: 1);
    }

    static Future<int> updateItem(int id, String title, String? description) async {
        final db = await SQLHelper.db();
        final data = {'title': title, 'description': description, 'createdAt': DateTime.now().toString()};
        final result = await db.update('items', data, where: "id = ?", whereArgs: [id]);
        return result;
    }

    static Future<void> deleteItem(int id) async {
        final db = await SQLHelper.db();
        try {
            await db.delete("items", where: "id = ?", whereArgs: [id]);
        } catch (err) {
            debugPrint("Something went wrong when deleting an item: $err");
        }
    }
}


Note que todas as funções que realizam alguma operação no banco de dados precisam primeiro criar a instância do banco através do método SQLHelper.db() e os dados devem estar no formato de Map.

Gerando o aplicativo e publicando na Play Store

Quando o código do aplicativo estiver pronto, o próximo passo é torná-lo disponível para outros usuários. Para isso é necessário ter uma conta de desenvolvedor no Google Play Console,  além disso, antes de publicar é necessário saber como o aplicativo será apresentado para o usuário final, como vai ficar o ícone no celular e na Play Store, se vai ter splash screen ou não, e o nome do aplicativo.

Para mudar o nome do aplicativo abra o arquivo pubspec.yaml e edite a linha name para o nome desejado. Para mudar o ícone do aplicativo, coloque o arquivo em formato PNG ou JPEG com as dimensões 512x512 pixels na pasta assets, em seguida, no mesmo arquivo onde mudamos o nome, edite a seção flutter para incluir os assets:

flutter:
    ...
    assets:
    - assets/novo_icon.png


Com tudo configurado, basta rodar o seguinte comando no terminal:

flutter build appbundle


Este comando vai gerar o APK ou o pacote de aplicativo (AAB) do seu aplicativo. O APK é um arquivo único que contém todo o código e recursos do seu aplicativo, enquanto o AAB é um pacote menor que a Google Play store pode otimizar para diferentes dispositivos. Na página inicial do Play console, crie um novo aplicativo, informando nome, descrição e o ícone.

Na seção versão do app, adicione a versão do seu aplicativo e faça o upload do arquivo APK ou AAB que foi gerado, depois, na seção Preços e distribuição, selecione o preço e a região de distribuição do seu aplicativo, aqui você também pode optar por deixar ele gratuito. Finalmente, quando estiver pronto para publicar, vá na seção Lançamento do aplicativo e clique em Publicar app. O processo de revisão do Google Play Console, pode levar algumas horas ou dias antes que o aplicativo seja aprovado e disponibilizado para download na Google Play Store.


Conclusão e próximos passos

A partir do que foi passado neste artigo é possível desenvolver projetos mais complexos de aplicativos, por exemplo, criptografando o banco de dados, criando um servidor, fazendo requisições a partir do aplicativo Flutter para o servidor e adicionando um sistema de login, procurar bibliotecas para trabalhar com outras funcionalidades do celular, como utilizar a câmera, o GPS, Bluetooth, giroscópio, dentre outros.

O Flutter é uma ótima opção para o desenvolvimento de aplicativos móveis, permitindo a criação de interfaces incríveis e funcionais com facilidade. Com o uso do banco de dados SQLite, é possível armazenar informações importantes no aplicativo, como uma lista de tarefas. E, com as ferramentas disponíveis para publicação na Play Store, é simples e relativamente fácil tornar o seu aplicativo disponível para milhões de usuários.

Com a combinação dessas tecnologias, desenvolver um aplicativo pode ser uma tarefa emocionante e gratificante para qualquer desenvolvedor.

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.