Trabajando con API en Flutter

Argel Bejarano
Comunidad Flutter
Published in
8 min readOct 20, 2018

Primero que nada y antes de empezar con este post, quiero agradecer el gran trabajo que realizo Poojã Bhaumik en la creación de la versión original de esta información, me disponía a crear uno cuando encontré esta gran publicación, hable con ella para pasar esto a nuestro idioma y me dio luz verde para hacerlo.

El articulo previo de Pooja Analizar JSON Complejos en Flutter (se encuentra en inglés) obtuvo bastantes revisiones por las personas que están iniciando en Flutter, y una de las preguntas frecuentes para los principiantes fue, ¿Cómo hacer esto con llamadas a un API?

¡Tus sueños serán concedidos amigo!

Trabajemos con una API sencilla la cual está disponible sin ningún tipo de autenticación.

Métodos HTTP que soporta JSONPlaceholder

GET: /POST/1

Veamos cómo será la respuesta obtenida cuando realicemos la petición a este endpoint de la API. Puedes hacer uso de Postman o solo pegar el siguiente enlace en tu navegador. Recuerda, si te encuentras trabajando con una API que requiera autenticación y otros headers HTTP, se recomienda hacer uso de Postman.

Hagamos un modelo de la clase para esta estructura de JSON.

Un pequeño consejo. Puedes hacer uso de todos tus conocimientos obtenidos en el post mencionado al inicio de esta lectura o ahorrarte un poco de tiempo utilizando esta pequeña herramienta. La descubrí recientemente y me gusto.

Recuerda cambiar el nombre de tu clase y el tipo de fuente. No olvides cambiar el lenguaje a Dart.

Esta herramienta creara tus modelos de clases, el método factory y los métodos convertidos, deberán verse así:

Enfoquémonos en nuestro método postFromJson.

Post postFromJson(String str) {    
final jsonData = json.decode(str);
return Post.fromJson(jsonData);
}

El str es solo un string JSON. Después de decodificar str, jsonData deberá verse así.

Sin formato, solo un Map de <String, dynamic>

Eso alimenta a Post.formJson eso hace que el método Factory pueda crear un nuevo objeto Post para que puedas hacer uso de el en tu aplicación.

Hey, ¿Por qué estamos enviando un Map al Post.fromJson?

factory Post.fromJson(Map<String, dynamic> json){
...
}

Si, por que Post.fromJason requere un argumento de tipo Map<String, dynamic>, entiendes…

Ahora llamemos a nuestra API

Mayormente yo tengo las llamadas a los métodos de mi API en un archivo diferente, supongamos que se llama services.dart

String url = 'https://jsonplaceholder.typicode.com/posts';

Future<Post> getPost() async{
final response = await http.get('$url/1');
return postFromJson(response.body);
}

Nota: no olvides importar los paquetes necesarios. Mira este archivo para los importes necesarios.

¡Podrías por favor explicar el código!

Hasta este momento, estuvimos hablando acerca de los string de JSON, pero aun ni lo tenemos. Porque no hemos llamado a nuestra API. Entonces hagamos ese esto primero.

El método getPost() llamara el endpoint de API que está definido en la url, y recibiremos el string JSON en responde.body, el cual tenemos que enviar a postFromJson para que pueda realizar su conversión mágica.

¿Pero por que el tipo de retorno es Future<Post> y no Post?

Bien, esto es correcto.

Estamos realizando una llamada a la red. Entonces obviamente no tendremos una respuesta de inmediato cuando llamemos al API. Tomará algo de tiempo. Por definición un Future es utilizado para representar un posible valor o error que estará disponible en algún momento en el futuro. Como nuestra respuesta estará disponible en algún momento del futuro, usamos Futures.

Como tenemos una llamada de red, obviamente necesitáremos una manera asíncrona de llamar a nuestra API. Por este motivo es que necesitamos async y await. En palabras simples, async es una palabra reservada que hace a nuestro método asíncrono. En una función declarada async, cuando nos topamos con await, la siguiente expresión es evaluada y la función ejecutada en el momento es suspendida hasta que obtenemos nuestro resultado. En este caso, hasta que obtengamos nuestra respuesta de éxito o error.

¿Entonces como creamos nuestra IU con la respuesta que obtenemos?

Sí, estaba por llegar a eso. Obviamente, si tenemos nuestra respuesta en el futuro, la interfaz de usuario, dependiendo de la respuesta, también debería estar en el futuro.

¿Por que?

Porque su interfaz de usuario se creará tan pronto como se ejecute la aplicación, pero no obtendrá la respuesta de la API tan pronto como se ejecute la aplicación. Por lo tanto, si su interfaz de usuario depende de los valores de respuesta de la API, se producirán muchos errores nulos.

Y para resolver este problema, tenemos…

El futuro de futuros: FutureBuilder

En pocas palabras, utilice un FutureBuilder para construir un widget cuando hay Futuros involucrados. Vamos a añadir las siguientes líneas de código en la función de compilación de su interfaz de usuario.

FutureBuilder<Post>(
future: getPost(),
builder: (context, snapshot) {
return Text('${snapshot.data.title}');
}
)

FutureBuilder también es un widget, por lo que puede tenerlo conectado a su Scaffold directamente, o conectarlo como un child a cualquier widget que desee.

FutureBuilder tiene dos propiedades principales — futuro y constructor. futuro necesita un future y getPost()devuelve un future.

Así que el futurellamará al método getPost(), hará su magia de llamada de red y devolverá el resultado al snapshot del builder. Ahora simplemente crea cualquier widget que te guste con el resultado dado. ¿Listview? Contenedores con Texto? ¡De verdad, lo que sea!

Nota: Aquí FutureBuilder tiene el tipo <Post> que es también el tipo de retorno de getPost(). Por lo tanto, cualquiera que sea el tipo que devuelva su futura función, ese debería ser el tipo de su FutureBuilder.

¿Y si quiero un comportamiento como este. Mientras espero los resultados, quiero mostrar a los usuarios un CircularProgressIndicator y tan pronto como el resultado esté disponible, mostrar el widget Text.

FutureBuilder logra esto facilmente.

if(snapshot.connectionState == ConnectionState.done)
return Text('Title from Post JSON : ${snapshot.data.title}');
else
return
CircularProgressIndicator();

Y supongamos que quiero mostrar una interfaz de usuario en particular para situaciones de error como ¿Sin conexión a Internet?

if(snapshot.connectionState == ConnectionState.done) {
if(snapshot.hasError){
return ErrorWidget();
}
return Text('Title from Post JSON : ${snapshot.data.title}');
}

Hay otros métodos como snapshot.hasData y otros ConnectionStates como ConnectionState.waiting, ConnectionState.active. Te sugiero que experimentes con todos ellos para construir mejores aplicaciones.

POST /posts

Oye, eso fue mucha información detallada con respecto a una petición de GET. ¿Puedes decirme rápidamente cómo hacer una solicitud POST?

Claro, en una petición POST, el cuerpo del método de llamada de red se vería un poco diferente, pero por lo demás todo es casi igual.

Crearas una clase modelo de su respuesta de solicitud POST de la misma manera. Su FutureBuilder también se construirá de forma similar. Veamos qué cambios hay en el método de llamada de red.

Future<Post> createPost(Post post) async{
final response = await http.post('$url',
headers: {
HttpHeaders.contentTypeHeader: 'application/json'
},
body: postToJson(post)
);
return postFromJson(response.body);
}

Su http.postahora tomará 3 parámetros: url → (URL del punto final de la API), headers (encabezados HTTP; si es necesario) y body (obligatorio).

Objeto Post enviado con la petición http

Por lo tanto, es posible que tenga un objeto de publicación que se parezca a éste.

El postToJson(post) convertirá su objeto de post a una cadena JSON lista para ser enviada al servidor.

Ahora llame al método createPost en su FutureBuilder y construya su Widget!

Pero no quiero construir una interfaz de usuario para esta llamada de red

Ese es un escenario posible. Como un caso de uso de Login, o simplemente una llamada de red que enviará algunos valores al servidor, y devolverá un statusCode 200 o 400, que es lo único que me molestaría.

Entonces, ¿no quieres un FutureBuilder?

Simplemente use un método .then()

createPost(post).then(
(response){

}
)

Llámelo cuando quiera llamar a la API. Por ejemplo, este fragmento podría estar en la función onPressed de su botón.

Aquí, la respuesta es el resultado que obtenemos cuando el createPost tiene alguna respuesta. Ahora podemos usar esta respuesta para hacer lo que queramos. Tal vez navegar a otra pantalla.

createPost(post).then((response){
Navigate.of(context).pushNamed('dashboard');
})

Pero en este caso, puede navegar a otra pantalla incluso cuando statusCode es 400, porque devuelve alguna respuesta de todos modos. (El error también es una respuesta válida)

Nota. Supongamos que queremos controlar nuestra lógica sobre la base del código de éxito o código de error, tendríamos que modificar el método createPost.

Future<http.Response> createPost(Post post) async{//same as previous bodyreturn response;
}

Ahora createPost devuelve un futuro de tipo http.response. De esta manera podemos controlar muchas cosas desde nuestra interfaz de usuario.

createPost(post).then((response){
if(response.statusCode == 200)
Navigate.of(context).pushNamed('dashboard');
else
print(response.statusCode);
})

Les dejare el link al código de ejemplo que tiene en github para que lo prueben ustedes mismo

Les dejo también pos si desean ver lo que he hecho, no tengo mucho en código pero me encontraba trabajando con el gran equipo de Flutter en Español para hacer una realizad la documentación también en nuestro idioma.

Les dejare un pequeño ejemplo con layouts espero les agrade:

Y de igual manera mi cuenta de Twitter donde sigo continuamente a grandes desarrolladores tanto en nuestro idioma como en otros, al igual de muchas de las comunidades alrededor del mundo hablando de nuestro adorado #Flutterverse.

Esto es todo de mi parte, espero estar de regreso próximamente con mucho mas contenido interesante de Flutter.

No dejen de aletear!!!

--

--

Argel Bejarano
Comunidad Flutter

Flutter & Dart GDE | Speaker and Editor from Comunidad Flutter | Founder @EsFlutter