Flutter PHP MySQL – Rellenar ListView con Imágenes y Texto
Flutter es un framework para crear aplicaciones móviles, tanto para Android como para iOS. De hecho, actualmente se puede utilizar incluso para crear aplicaciones web y de escritorio. Es un framework maduro y bien soportado y actualmente es tendencia.
Por otro lado, MySQL es una base de datos que se utiliza principalmente para alojar datos para aplicaciones web y móviles. Es gratuito y es probablemente el marco RDBMS más popular en el mercado. Generalmente nos gusta usarlo con PHP que es un lenguaje de programación del lado del servidor. PHP y MySQL es una combinación que potencia la mayor parte de la web.
En esta lección queremos ver cómo trabajar tanto con la base de datos MySQL como con la aplicación Flutter. Simplemente realizamos un HTTP GET contra el servidor MySQL de PHP, descargamos los datos y poblamos nuestro listview personalizado con imágenes y texto. Luego, cuando se hace clic en una tarjeta de nuestro listview, abrimos una página de detalles pasando los datos que habíamos descargado de mysql.
Demo
Aquí está la demo de lo que se crea en el proyecto.

Video Tutorial
Aquí está el video tutorial:
1. PHP
Primero tenemos que escribir el código PHP que:
- Conecte a la base de datos mysql usando la clase
mysqli. - Seleccione todos los registros de nuestra tabla de base de datos mysql.
- Devolver los resultados a un array de PHP.
- Codificar en JSON ese array e imprimirlo a la persona que llama.
(a). index.php
Este es el único archivo que tenemos. Escribimos código PHP orientado a objetos. El primer paso en este archivo es crear una clase Constants que contendrá las credenciales de nuestra base de datos. Recuerda que estamos alojando nuestra base de datos en MySQL por lo que necesitamos definir las credenciales como el nombre de la base de datos, el nombre de usuario así como la contraseña.
En el archivo index.php escribimos la etiqueta PHP de apertura:
<?php
Luego creamos la clase con las credenciales estáticas de la base de datos:
class Constants
{
//DATABASE DETAILS
static $DB_SERVER="localhost";
static $DB_NAME="spacecraftsDB";
static $USERNAME="root";
static $PASSWORD="";
Luego definir la sentencia sql para seleccionar todos los datos de la base de datos:
static $SQL_SELECT_ALL="SELECT * FROM spacecraftsTB";
}
En el mismo archivo php proceda a crear otra clase que alojará nuestros métodos CRUD:
class Spacecrafts
{
En esta clase definiremos una función para conectar con nuestra base de datos usando mysqli. Pasamos el servidor de la base de datos, el nombre de la base de datos, el nombre de usuario y la contraseña al constructor de nuestra clase mysqli. Luego si la conexión es errática, devolvemos null en caso contrario devolvemos nuestra instancia mysqli:
public function connect()
{
$con=new mysqli(Constants::$DB_SERVER,Constants::$USERNAME,Constants::$PASSWORD,Constants::$DB_NAME);
if($con->connect_error)
{
// echo "Unable To Connect"; - For debug
return null;
}else
{
//echo "Connected"; - For debug
return $con;
}
}
La siguiente función nos permitirá seleccionar datos de nuestra base de datos mysql. Recuperaremos datos de la base de datos, luego empujaremos esos datos en un array, luego codificaremos e imprimiremos ese array:
public function select()
{
$con=$this->connect();
if($con != null)
{
$result=$con->query(Constants::$SQL_SELECT_ALL);
if($result->num_rows>0)
{
$spacecrafts=array();
while($row=$result->fetch_array())
{
array_push($spacecrafts, array("id"=>$row['id'],"name"=>$row['name'],
"propellant"=>$row['propellant'],"destination"=>$row['destination'],
"image_url"=>$row['image_url'],"technology_exists"=>$row['technology_exists']));
}
print(json_encode(array_reverse($spacecrafts)));
}else
{
print(json_encode(array("PHP EXCEPTION : CAN'T RETRIEVE FROM MYSQL. ")));
}
$con->close();
}else{
print(json_encode(array("PHP EXCEPTION : CAN'T CONNECT TO MYSQL. NULL CONNECTION.")));
}
}
Aquí está el código completo de index.php:
<?php
class Constants
{
//DATABASE DETAILS
static $DB_SERVER="localhost";
static $DB_NAME="spacecraftsDB";
static $USERNAME="root";
static $PASSWORD="";
//STATEMENTS
static $SQL_SELECT_ALL="SELECT * FROM spacecraftsTB";
}
class Spacecrafts
{
/*******************************************************************************************************************************************/
/*
1.CONNECT TO DATABASE.
2. RETURN CONNECTION OBJECT
*/
public function connect()
{
$con=new mysqli(Constants::$DB_SERVER,Constants::$USERNAME,Constants::$PASSWORD,Constants::$DB_NAME);
if($con->connect_error)
{
// echo "Unable To Connect"; - For debug
return null;
}else
{
//echo "Connected"; - For debug
return $con;
}
}
/*******************************************************************************************************************************************/
/*
1.SELECT FROM DATABASE.
*/
public function select()
{
$con=$this->connect();
if($con != null)
{
$result=$con->query(Constants::$SQL_SELECT_ALL);
if($result->num_rows>0)
{
$spacecrafts=array();
while($row=$result->fetch_array())
{
array_push($spacecrafts, array("id"=>$row['id'],"name"=>$row['name'],
"propellant"=>$row['propellant'],"destination"=>$row['destination'],
"image_url"=>$row['image_url'],"technology_exists"=>$row['technology_exists']));
}
print(json_encode(array_reverse($spacecrafts)));
}else
{
print(json_encode(array("PHP EXCEPTION : CAN'T RETRIEVE FROM MYSQL. ")));
}
$con->close();
}else{
print(json_encode(array("PHP EXCEPTION : CAN'T CONNECT TO MYSQL. NULL CONNECTION.")));
}
}
}
$spacecrafts=new Spacecrafts();
$spacecrafts->select();
//end
2. Dart
La siguiente parte es escribir nuestro código Dart. Dart es el lenguaje de programación que utilizamos para crear nuestra aplicación Flutter. Sólo tenemos un archivo:
(a). main.dart
Empezamos añadiendo los imports:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:convert';
Crea una clase llamada Spacecraft para representar una nave espacial. Esta es nuestra clase modelo. Definimos las propiedades de esta nave espacial como instancias de esta clase. Estas propiedades incluyen id, nombre, URL de la imagen y propulsor.
class Spacecraft {
final String id;
final String name, imageUrl, propellant;
Estas propiedades se recibirán a través del constructor:
Spacecraft({
this.id,
this.name,
this.imageUrl,
this.propellant,
});
Tendremos una función encargada de convertir los datos JSON en nuestro objeto Spacecraft:
factory Spacecraft.fromJson(Map<String, dynamic> jsonData) {
return Spacecraft(
id: jsonData['id'],
name: jsonData['name'],
propellant: jsonData['propellant'],
imageUrl: "http://192.168.12.2/PHP/spacecrafts/images/"+jsonData['image_url'],
);
}
}
También necesitaremos crear una pantalla que liste nuestros datos. Listaremos nuestros datos en un ListView. Así que tenemos que crear un elemento ListView. Cuando se haga clic en ese elemento ListView abriremos la página de detalles y mostraremos los resultados en otra pantalla:
Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
return new ListTile(
title: new Card(
elevation: 5.0,
child: new Container(
decoration: BoxDecoration(border: Border.all(color: Colors.orange)),
padding: EdgeInsets.all(20.0),
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Padding(
child: Image.network(spacecraft.imageUrl),
padding: EdgeInsets.only(bottom: 8.0),
),
Row(children: <Widget>[
Padding(
child: Text(
spacecraft.name,
style: new TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
),
padding: EdgeInsets.all(1.0)),
Text(" | "),
Padding(
child: Text(
spacecraft.propellant,
style: new TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.right,
),
padding: EdgeInsets.all(1.0)),
]),
],
),
),
),
onTap: () {
//We start by creating a Page Route.
//A MaterialPageRoute is a modal route that replaces the entire
//screen with a platform-adaptive transition.
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new SecondScreen(value: spacecraft),
);
//A Navigator is a widget that manages a set of child widgets with
//stack discipline.It allows us navigate pages.
Navigator.of(context).push(route);
});
}
}
Quizá te interese saber cómo descargamos los datos JSON del servidor. El primer paso es definir una función que devuelva un objeto Future. Este método será asíncrono para no bloquear nuestra interfaz de usuario Thread. El parámetro genérico de esta función será una lista de naves espaciales:
Future<List<Spacecraft>> downloadJSON() async {
A continuación definiremos un endpoint JSON. Esta es la URL contra la que realizaremos una petición HTTP GET. Luego ejecuta ese endpoint usando la función get():
final jsonEndpoint =
"http://192.168.12.2/PHP/spacecrafts";
final response = await get(jsonEndpoint);
También comprobaremos el código de respuesta si hemos tenido éxito. Un código de estado de respuesta 200 implica éxito, que nos hemos comunicado con éxito con el servidor. Así que decodificamos el cuerpo de la respuesta JSON en una Lista. Es esa lista la que vamos a renderizar en nuestro listview:
if (response.statusCode == 200) {
List spacecrafts = json.decode(response.body);
return spacecrafts
.map((spacecraft) => new Spacecraft.fromJson(spacecraft))
.toList();
} else
throw Exception('We were not able to successfully download the json data.');
}
Aquí está el código completo:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:convert';
class Spacecraft {
final String id;
final String name, imageUrl, propellant;
Spacecraft({
this.id,
this.name,
this.imageUrl,
this.propellant,
});
factory Spacecraft.fromJson(Map<String, dynamic> jsonData) {
return Spacecraft(
id: jsonData['id'],
name: jsonData['name'],
propellant: jsonData['propellant'],
imageUrl: "http://192.168.12.2/PHP/spacecrafts/images/"+jsonData['image_url'],
);
}
}
class CustomListView extends StatelessWidget {
final List<Spacecraft> spacecrafts;
CustomListView(this.spacecrafts);
Widget build(context) {
return ListView.builder(
itemCount: spacecrafts.length,
itemBuilder: (context, int currentIndex) {
return createViewItem(spacecrafts[currentIndex], context);
},
);
}
Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
return new ListTile(
title: new Card(
elevation: 5.0,
child: new Container(
decoration: BoxDecoration(border: Border.all(color: Colors.orange)),
padding: EdgeInsets.all(20.0),
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Padding(
child: Image.network(spacecraft.imageUrl),
padding: EdgeInsets.only(bottom: 8.0),
),
Row(children: <Widget>[
Padding(
child: Text(
spacecraft.name,
style: new TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
),
padding: EdgeInsets.all(1.0)),
Text(" | "),
Padding(
child: Text(
spacecraft.propellant,
style: new TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.right,
),
padding: EdgeInsets.all(1.0)),
]),
],
),
),
),
onTap: () {
//We start by creating a Page Route.
//A MaterialPageRoute is a modal route that replaces the entire
//screen with a platform-adaptive transition.
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new SecondScreen(value: spacecraft),
);
//A Navigator is a widget that manages a set of child widgets with
//stack discipline.It allows us navigate pages.
Navigator.of(context).push(route);
});
}
}
//Future is n object representing a delayed computation.
Future<List<Spacecraft>> downloadJSON() async {
final jsonEndpoint =
"http://192.168.12.2/PHP/spacecrafts";
final response = await get(jsonEndpoint);
if (response.statusCode == 200) {
List spacecrafts = json.decode(response.body);
return spacecrafts
.map((spacecraft) => new Spacecraft.fromJson(spacecraft))
.toList();
} else
throw Exception('We were not able to successfully download the json data.');
}
class SecondScreen extends StatefulWidget {
final Spacecraft value;
SecondScreen({Key key, this.value}) : super(key: key);
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Detail Page')),
body: new Container(
child: new Center(
child: Column(
children: <Widget>[
Padding(
child: new Text(
'SPACECRAFT DETAILS',
style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0),
textAlign: TextAlign.center,
),
padding: EdgeInsets.only(bottom: 20.0),
),
Padding(
//`widget` is the current configuration. A State object's configuration
//is the corresponding StatefulWidget instance.
child: Image.network('${widget.value.imageUrl}'),
padding: EdgeInsets.all(12.0),
),
Padding(
child: new Text(
'NAME : ${widget.value.name}',
style: new TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
padding: EdgeInsets.all(20.0),
),
Padding(
child: new Text(
'PROPELLANT : ${widget.value.propellant}',
style: new TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
padding: EdgeInsets.all(20.0),
)
], ),
),
),
);
}
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new Scaffold(
appBar: new AppBar(title: const Text('MySQL Images Text')),
body: new Center(
//FutureBuilder is a widget that builds itself based on the latest snapshot
// of interaction with a Future.
child: new FutureBuilder<List<Spacecraft>>(
future: downloadJSON(),
//we pass a BuildContext and an AsyncSnapshot object which is an
//Immutable representation of the most recent interaction with
//an asynchronous computation.
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Spacecraft> spacecrafts = snapshot.data;
return new CustomListView(spacecrafts);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
//return a circular progress indicator.
return new CircularProgressIndicator();
},
),
),
),
);
}
}
void main() {
runApp(MyApp());
}
//end
Descargar
Aquí están los recursos de referencia:
| --- |
|---|