Solucionado (ver solução)
Solucionado
(ver solução)
12
respostas

[Dúvida] Conexão do app usando o pacote mysql1

Olá! Eu gostaria de conectar esse meu app a um banco de dados mysql externo. Achei um pacote chamado mysql1 para fazer os primeiros testes, mas não consigo transformar os resultados no tipo List para mostrar na tela inicial do app. Alguém saberia me ajudar a resolver esse problema?

12 respostas

Oi Luiz, pode compartilhar com a gente o pacote?

Dei uma pesquisada e achei esse daqui:https://pub.dev/packages/mysql1

é esse?

Queria pedir também pra você compartilhar com a gente seu código! Quero entender melhor quais resultados você ta querendo transformar em List e como você tá querendo buildar na tela!

Opa! É esse pacote mesmo!

Eu fiz umas alterações no código, e acabou que o problema ficou diferente. Meu objetivo é fazer uma lista de "clientes", como fizemos com as Tasks no curso, porém pegando os valores do banco de dados externo ao invés do banco de dados local. Esse código eu peguei de um site (https://medium.com/@flutterqueen/how-to-send-and-show-data-from-the-flutter-app-to-mysql-database-without-php-or-rest-api-server-2c249b09bc04) mas está dando um erro que diz: "null is not a subtype of String". Mas caso haja algum outro jeito para resolver o problema eu não me importo em refazer o código. Obrigado pela ajuda! Segue o código abaixo:

main.dart:

void main() async {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SQLData(),
    );
  }
}

conn.dart:

class Mysql{
  static String host = 'host';
  static int port = 3306;
  static String db = 'db';
  static String user = 'user';
  static String password = 'password';

  Mysql();

  Future<MySqlConnection> getConnection()async{
    var settings = ConnectionSettings(
      host: host,
      port: port,
      db: db,
      user: user,
      password: password,
    );
    return await MySqlConnection.connect(settings);
  }
}

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

  @override
  State<SQLData> createState() => _SQLDataState();
}

class _SQLDataState extends State<SQLData> {
  Future<List<Cliente>> getSQLData() async {
    final List<Cliente> listaClientes = [];
    final Mysql db = Mysql();
    await db.getConnection().then((conn) async {
      String sqlQuery = 'SELECT razao_social from clientes LIMIT 5';
      await conn.query(sqlQuery).then((results) {
        for (var res in results) {
          final clienteModel = Cliente(res["nome"]);

          listaClientes.add(clienteModel);
        }
      }).onError((error, stackTrace) {
        print(error);
        return null;
      });
      conn.close();
    });

    return listaClientes;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: getDBData(),
    ));
  }

  FutureBuilder<List<Cliente>> getDBData() {
    return FutureBuilder<List<Cliente>>(
        future: getSQLData(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text(snapshot.error.toString());
          }
          return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final data = snapshot.data as List<Cliente>;
                return ListTile(
                  title: Text(
                    data[index].nome.toString(),
                    style: const TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                );
              });
        });
  }
}

Vamos lá, esse erro: "null is not a subtype of String" é bem facil de resolver: Você ta esquecendo de tratar o Null safety, onde as informações que estão vindo do seu banco de dados possuem valor nulo por um tempo, logo a sua String será String? e em alguns Widgets o valor String? não é aceito (por exemplo o Text()), logo você deve fazer um tratamento para garantir que o seu String? só seja usado quando o valor dele não é mais nulo! [um if(String? != null já deve servir]

Para mais informações sobre Null safety da uma olhadinha nesses conteudos:

Outra coisa, esse seu

.onError((error, stackTrace) {
        print(error);
        return null;
      });

faz com que sempre que houver um erro no banco de dados o seu objeto seja nulo, talvez retornar uma lista default seja uma melhor opção, pois nulo vai quebrar seu código.

Por fim, nessa parte do código aqui eu quero uma reflexão sua:

 return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final data = snapshot.data as List<Cliente>;
                return ListTile(
                  title: Text(
                    data[index].nome.toString(),
                    style: const TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                );

O que aconteceria com o ListView.builder se o itemCount fo nulo? e com o Text() se o data for nulo?

Dica: Muita coisa vai quebrar!

Ahhh tinha esquecido de tratar o Null Safety. Parando para pensar, nesse último trecho de código muita coisa poderia dar errado KKKKKKK. Caso o itemCount fosse nulo acho que nem buildar o ListView ele iria buildar, além de que se o Text fosse nulo... eu não tenho certeza do resultado mas provavelmente seria o "null is not a subtype of String".

Obrigado pela ajuda, Kako! Você é fera >:D

Kako, fiz umas boas mudanças no código e agora ele já está rodando, porém ele não lista as informações do banco de dados, apenas abre o app e aparece o ícone de exclamação com um texto dizendo "Não há nenhum cliente". Estou tentando entender o porquê disso acontecer, poderia dar uma olhada, por favor?

Algumas coisas que eu fiz que eu não tenho muita segurança são os ! e ? do último arquivo, coloquei porque a IDE estava pedindo mas não sei se isso está causando o erro ou se é outra coisa.

Vou enviar em 2 comentários separados para não ultrapassar o limite de caracteres, ok?

main.dart:

import 'package:flutter/material.dart';
import 'package:testes_mysql1/Data/cliente_inherited.dart';
import 'package:testes_mysql1/screens/initial_screen.dart';

void main() async {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ClienteInherited(child: const InitialScreen(),)
    );
  }
}

initial_screen.dart:

import 'package:flutter/material.dart';
import 'package:testes_mysql1/Data/cliente_dao.dart';
import 'package:testes_mysql1/components/cliente.dart';

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

  @override
  State<InitialScreen> createState() => _InitialScreenState();
}

class _InitialScreenState extends State<InitialScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          leading: Container(),
          actions: [
            IconButton(onPressed: () {
              setState(() {

              });
            }, icon: Icon(Icons.refresh))
          ],
          title: const Text('Clientes'),
        ),
        body: Padding(
          padding: EdgeInsets.only(top: 8, bottom: 70),
          child: FutureBuilder<List<Cliente>>(
              future: ClienteDao().find(1),
              builder: (context, snapshot) {
                List<Cliente>? items = snapshot.data;
                switch (snapshot.connectionState) {
                  case ConnectionState.none:
                    return Center(
                        child: Column(
                          children: [
                            CircularProgressIndicator(),
                            Text('Carregando'),
                          ],
                        )
                    );
                    break;
                  case ConnectionState.waiting:
                    Center(
                        child: Column(
                          children: [
                            CircularProgressIndicator(),
                            Text('Carregando')
                          ],
                        )
                    );
                    break;
                  case ConnectionState.active:
                    Center(
                        child: Column(
                          children: [
                            CircularProgressIndicator(),
                            Text('Carregando')
                          ],
                        )
                    );
                    break;
                  case ConnectionState.done:
                    if (snapshot.hasData && items != null) {
                      if (items.isNotEmpty) {
                        return ListView.builder(itemCount: items.length,
                          itemBuilder: (BuildContext context, int index) {
                          final Cliente cliente = items[index];
                          return cliente;
                          });
                      }
                    }
                }
                return Center(
                  child: Column(
                    children: [
                      Icon(
                        Icons.error_outline,
                        size: 128,
                      ),
                      Text(
                        'Não há nenhum cliente', style: TextStyle(fontSize: 32),
                      ),
                    ],
                  ),
                );
              }
          ),
        ),
    );
  }
}

Continuação do código:

cliente_inherited:

import 'package:flutter/material.dart';
import 'package:testes_mysql1/components/cliente.dart';

class ClienteInherited extends InheritedWidget {
  ClienteInherited({
    Key? key,
    required Widget child,
  }) : super(key: key, child: child);

  final List<Cliente> clientelist = [
    Cliente(3, 'nome')
  ];

  void newCliente(int id, String nome){
    clientelist.add(Cliente(id,nome));
  }

  static ClienteInherited of(BuildContext context) {
    final ClienteInherited? result =
        context.dependOnInheritedWidgetOfExactType<ClienteInherited>();
    assert(result != null, 'No ClienteInherited found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(ClienteInherited oldWidget) {
    return oldWidget.clientelist.length != clientelist.length;
  }
}

cliente.dart:

import 'package:flutter/material.dart';

class Cliente extends StatefulWidget {
  late final int id;
  late final String nome;

  Cliente(this.id, this.nome, {Key?key}) : super(key: key);

  @override
  State<Cliente> createState() => _ClienteState();
}

class _ClienteState extends State<Cliente> {

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Stack(
        children: [
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(4), color: Colors.blue),
            height: 140,
          ),
          Column(
            children: [
              Container(
                height: 100,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(4),
                  color: Colors.white
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        SizedBox(
                          width: 200,
                          child: Text(
                            widget.nome,
                            style: const TextStyle(
                              fontSize: 24,
                              overflow: TextOverflow.ellipsis,
                            ),
                          ),
                        )
                      ],
                    )
                  ],
                )
              )
            ],
          )
        ],
      )
    );
  }
}

cliente_dao.dart:

import 'package:mysql1/mysql1.dart';
import 'package:testes_mysql1/components/cliente.dart';

class ClienteDao {

  Future<List<Cliente>> find (int id) async{
    print('Acessando find...');

    final conn = await MySqlConnection.connect(ConnectionSettings(
        host: 'host', //valores de exemplo, nos testes eu coloquei os valores corretos do banco de dados
        port: 3306,
        user: 'user',
        db: 'db',
        password:password'
    ));

    var resultado = await conn.query('SELECT razao_social FROM clientes where id = ?', [id]);

    List<Cliente>? resultadoFinal;

    for (int i = 0; i <= resultado.length; i++){
      resultadoFinal![i].id   = resultado.elementAt(0).fields[0];
      resultadoFinal[i].nome = resultado.elementAt(0).fields[2];
    }

    return resultadoFinal!;

  }
}

Bom pelo que eu entendi, sua mensagem de não há tarefas

 return Center(
                  child: Column(
                    children: [
                      Icon(
                        Icons.error_outline,
                        size: 128,
                      ),
                      Text(
                        'Não há nenhum cliente', style: TextStyle(fontSize: 32),
                      ),
                    ],
                  ),
                );

está depois do Switch case, se tornando assim um retorno default. Sugiro colocar ele algumas chaves antes para que ele seja o retorno para caso o valor do snapshot seja vazio ( sem nada no banco)

Mais algumas coisas, pelo seu future, eu vi que você ta usando um find(id), correto?

child: FutureBuilder<List<Cliente>>(
              future: ClienteDao().find(1),
              builder: (context, snapshot) {
                List<Cliente>? items = snapshot.data;

Esse find(id) procura um elemento baseado no id dele, se esse elemento com id = 1 não existir não vai aparecer mesmo, mas eu nao consigo garantir isso apenas com os prints.

Queria te pedir pra compartilhar seu codigo comigo via github porque ai eu consigo fazer uns tests para ver se o banco de dados está salvando, e se o snapshot está buscando devidamente as informações do banco!

( Uma dica preciosa é usar o debuggPrint para ir verificando as ações do seu projeto, para garantir que as informações estão sendo salvas, atualizadas, encontradas com precisão no DB)

Opa, boa tarde!

Não tinha notado que esse Center estava fora do Switch, vou arrumar isso.

Criei um repositório com o código, o link é esse aqui: https://github.com/loulouizz/testeappFlutter.git

Dei uma olhadinha no seu código, vamos às considerações:

No initial_screen.dart

No switch and case faltou adicionar os return antes dos Centers() em cada case:

case ConnectionState.waiting:
                    return Center(
                        child: Column(
                          children: [
                            CircularProgressIndicator(),
                            Text('Carregando')
                          ],
                        )
                    );
                    break;

No codigo disponivel no github tava sem o código de retorno para caso o banco de dados estaja vazio então coloquei:

case ConnectionState.done:
                    if (snapshot.hasData && items != null) {
                      if (items.isNotEmpty) {
                        return ListView.builder(itemCount: items.length,
                          itemBuilder: (BuildContext context, int index) {
                          final Cliente cliente = items[index];
                          return cliente;
                          });
                      }
                    }
                    return Text('Não há nada no banco de dados');

No client_dao.dart Eu fiz algumas experiencias para poder entender e cheguei a uma conclusão: O valor que você ta pegando é nulo! Se liga:

Alterei seu código para não precisar de null safety e também coloquei uns prints pra gente ver os resultados ` List resultadoFinal = [];

for (int i = 0; i <= resultado.length; i++){
  print('entrando no loop com tamanho de ${resultado.length}');
  print('O dado encontrado no banco de dados é: $resultado');
  print('O elemento na posição 0 é: ${resultado.elementAt(0)}');
  print('O elemento no campo 0  é: ${resultado.elementAt(0).fields[0]}');
  print('O elemento no campo 2  é: ${resultado.elementAt(0).fields[2]}');
  resultadoFinal[i].id   = resultado.elementAt(0).fields[0];
  resultadoFinal[i].nome = resultado.elementAt(0).fields[2];
}


return resultadoFinal;

`

O Resultado foi esse:

I/flutter ( 5816): Acessando find... Reloaded 1 of 715 libraries in 342ms (compile: 11 ms, reload: 113 ms, reassemble: 173 ms). I/flutter ( 5816): entrando no loop com tamanho de 1 I/flutter ( 5816): O dado encontrado no banco de dados é: (Fields: {razao_social: JV DE ANDRADE INFORMATICA xxx}) I/flutter ( 5816): O elemento na posição 0 é: Fields: {razao_social: JV DE ANDRADE INFORMATICA xxx} I/flutter ( 5816): O elemento no campo 0 é: null I/flutter ( 5816): O elemento no campo 2 é: null

Note que os elementos que você está pegando ambos estão nulos, produzindo um resultadoFinal nulo, portanto sua lista de cliente não vai carregar!

Mas e ai Kako o que você sugere? Sugiro que você dê um repensada na lógica dos elementos que esta buscando no seu banco de dados, pois usar o .fields[?] não vai te dar o resultado que deseja, de resto achei sensacional o código que você fez, desde a criação dos Clientes até a implementação da lista de cliente na tela inicial, vc ta de parabens mano!

Dica: Sei que estamos num espaço de estudo mas tome cuidado com os codigos que você compartilha, tem coisa sensivel aqui...

Fiz mais um testezinho e alterei o codigo de field para value:

print('O elemento na posição 0 é: ${resultado.elementAt(0).values?[0]}');

Olha o resultado:

I/flutter ( 7109): O elemento na posição 0 é: JV DE ANDRADE INFORMATICA xxx

Significa que ja conseguimos pegar o nome para colocar no resultadoFinal[i].nome = JV DE ANDRADE INFORMATICA xxx

mas o ID não consegui achar no banco de dados que esta implementado, então imagino que será sempre nulo ;c

solução!

Boa tarde!

Desculpa a demora para responder. Obrigado pela ajuda, agora já está funcionando como eu gostaria.