Inicio » Blog » Android

9 agosto, 2019

App Android con una API Rest en Laravel

La solución es crear una Api Rest para gestionar las peticiones http de la App móvil donde usaremos la librería Retrofit para extraer datos e insertarlos

Ver video en

Suscríbete a nuestro canal en Youtube

Suscríbirse

Desarrollo de una Aplicación móvil que permita publicar y mostrar las últimas publicaciones realizadas por los usuarios de la App.

Para el desarrollo del proyecto necesitamos diferenciar dos partes:

  1. El desarrollo de la Api Rest en Laravel
  2. El desarrollo de Aplicación móvil Android

EL DESARROLLO DE LA API REST

Es un servicio web cuya función principal es gestionar las peticiones http invocadas desde la app móvil, devolviendo una respuesta; para crear una API Rest usaremos el Framework de desarrollo de aplicaciones web en PHP llamado Laravel.

Pasos que debemos seguir:

Instalar Laravel

Abrimos Visual Studio Code y levantamos un terminal, nos ubicamos en el directorio de proyectos de Xampp. Entonces usamos un comando composer , el cual tendrá la siguiente sintaxis 

composer create-project laravel\laravel arequipa --prefer-dist

Este comando descargará la última versión estable de Laravel

Configuración Básica de Laravel

  1. Abrir el archivo .env donde se declara las variables de entorno, declaramos los datos para la concexión a la base de datos 
    DB_DATABASE=arequipa
    DB_USERNAME=root
    DB_PASSWORD=

     

  2. Configuramos una restricción limitando la longitud de los campos tipo varchar en el archivo app/providers/AppServiceProvider.php, y se verá de la siguiente manera: 
    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use Illuminate\Support\Facades\Schema;
    
    class AppServiceProvider extends ServiceProvider
    {
        public function register()
        {
            Schema::defaultStringLength(191);
        }
    
        public function boot()
        {
            //
        }
    }
    

     

Creando el Modelo Publicaciones

Creamos el modelo y la migración para poder generar la tabla en la Base de Datos Mysql. entoncer digitamos en la terminal el siguiente comando artisan :

php artisan make:model Publicaciones -m

Una vez creado nuestro el modelo Publicaciones.php debemos agregar dos campos de la siguiente manera

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Publicaciones extends Model
{
    protected $fillable = [
        'nombre', 'descripcion'
    ];

}

Luego, debemos también modificar la migración agregando la declaración de los dos campos 

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePublicacionesTable extends Migration
{
    public function up()
    {
        Schema::create('publicaciones', function (Blueprint $table) {
            $table->increments('id');
            $table->string('nombre',25);
            $table->string('descripcion',190);
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('publicaciones');
    }
}

Ahora procedemos a crear la Ruta la API

Para ello, abrimos el archivo routes/api.php, es en este archivo donde crearemos la ruta para gestionar las peticiones GET y POST enviadas desde la APP Móvil, debemos modificar el código de la siguiente manera:

<?php
use Illuminate\Http\Request;
Route::resource('publicaciones', 'PublicacionesController');

Usamos el constructor resource, del cual solo aprovecharemos los métodos index y store

Creando los métodos para PublicacionesController

Y por último implementamos los métodos index y store para el controlador PublicacionesController, veamos el código PHP que deben tener:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Publicaciones;

class PublicacionesController extends Controller
{
    public function index(){
        $p=Publicaciones::orderBy("created_at","desc")->get();
        return response()->json($p, 200);
    }

    public function store(Request $request){
        $p=new Publicaciones($request->all());
        $p->save();
        return response()->json($p, 200);
    }
}

El método index realiza un consulta para extraer todos los registros de la tabla publicaciones ordenados en forma descendente, y los devuelve en formato Json.

El método store recibe las variables necesarias para insertar un nuevo registros a través del objeto $request, declaramos la clase publicaciones y llenamos con los valores para luego ejecutar el método save, este insertará el nuevo registro, retorna un Json afirmando que se ha insertado correctamente.

Finalmente terminamos con la primera parte correspondiente a la implementación de una API Rest con Laravel, a continuación debemos levantar Laravel y verificar al menos la url que retorna la lista de registros de la tabla publicaciones en formato JSON:

php artisan serve

La url para probar deber ser muy parecida a la siguiente, donde Ud. debería variar la dirección IP.

http://112.168.0.4/arequipa/public/api/publicaciones

 

EL DESARROLLO DE LA APP ANDROID

La funcionalidad radica en permitir a los usuarios enviar nuevas publicaciones y visualizar las publicaciones anteriores de la base de datos del servidor usando peticiones http gestionadas por la líbreria Retrofit hacia la API Rest 

Creando el proyecto para empezar a programar

Abrimos Android Studio y damos en Star New Project Android  con el Nombre de Arequipa pasamos a configurar la aplicación

Agregando Retrofit, Gson y CardView con Gradle

Para gestionar y resolver las peticiones http hacia la API Rest son necesarias las dependencias de Retrofit y Gson; y la dependencia CardView nos va a permitir aplicar diseño. Para lograr ello debemos modificar el archivo de Gradle en la sección de dependencias:

implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

Modificando AndroidManifest.xml

Nuestra aplicación va a consumir servicios web por ende necesita conectarse a internet, entonces debemos habilitar el permiso de internet, y por último debemos agregar y configurar un atributo usesCleartextTraffic, esto es apartir de la versión 9 pie, el cual se refiere a que los datos enviados por http deben estar cifrados entonces debemos darle el valor de true. El xml de AndroidManifest quedaría de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="codea.app.arequipa">
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".PublicacionesActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Modificando la Actividad para la Portada 

Una vez que se ha creado nuestro proyecto por default implementa una actividad con el nombre de MainActivity.java con su correspondiente xml el activity_main.xml.

Código activity_mail.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@drawable/ic_fondo"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnEntrar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="120dp"
        android:text="ENTRAR"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.551"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:src="@drawable/ic_logo"
        app:layout_constraintBottom_toTopOf="@+id/btnEntrar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.526"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.446"
        app:srcCompat="@mipmap/ic_launcher" />

</android.support.constraint.ConstraintLayout>

El comportamiento de la actividad lo definimos MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Button btnentrar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnentrar = findViewById(R.id.btnEntrar);
        btnentrar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(getApplicationContext(),PublicacionesActivity.class));
            }
        });
    }
}

Segunda Actividad PublicacionesActivity

Ahora creamos la segunda actividad para la gestión de las publicaciones donde vamos ver partes claramente diferenciadas,

La primera parte permitirá enviar un publicación

En la primera sección mostrará un formulario con dos campos tipo EdiText y un Botón, la principal funcionalidad es permitir al usuario ingresar su nombre, luego el contenido de la publicación y por último presionar en el botó, esta acción enviará una petición http solicitando la inserción de un nueva publicación a la base de datos. Esta acción espera el click en el botón para ejecutarse.

La segunda parte mostrará las últimas publicaciones

La función de ésta parte realizará un petición http con retrofit al servidor solicitando la lista completa de las publicaciones, los cuales son devueltos por la API Rest en formato JSON, la respuesta es tomada por la app  la cual infla o llena un ListaView con los datos correspondientes en orden descendente. Esta acción siempre se ejecutará como primera al cargar la actividad.

Veamos el código XML de nuestra interfaz.

Activity_publicaciones.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@drawable/ic_fondo"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".PublicacionesActivity">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:background="#fff"
        app:cardElevation="10dp"
        android:layout_margin="10dp"
        android:layout_height="wrap_content">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <EditText
                android:hint="NOMBRE:"
                android:id="@+id/nombre"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <EditText
                android:hint="SALUDO:"
                android:id="@+id/descripcion"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <Button
                android:layout_gravity="right"
                android:id="@+id/btnSave"
                android:text="PUBLICAR"
                android:textColor="#fff"
                android:background="@color/colorPrimaryDark"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </LinearLayout>
    </android.support.v7.widget.CardView>
    <ListView
        android:id="@+id/lista"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>

</LinearLayout>

En un paréntesis vemos que tenemos un ListView, este contenedor se inflará con la lista de las publicaciones devueltos por la API Rest,  para lo cual hace uso de un adaptador quién requiere de un recurso xml, que básicamente es la plantilla para cada registro. a

Agregamos el recurso con el nombre de res/lista.xml, el cual tiene dos TextView, veamos identificados correctamente.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.CardView
        android:layout_margin="5dp"
        android:elevation="10dp"
        android:background="#fff"
        app:contentPadding="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/txtnombre"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/txtdescripcionn"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

PublicacionesActivity.java

Ahora veamos el código Java para determinar el comportamiento de la Actividad PublicacionesActitivy; 

public class PublicacionesActivity extends AppCompatActivity {

    private EditText n,d;
    private Button btnSave;
    private ListView lv;
    private AdapterPublicaciones adapter;
    private ApiService servicio= Config.getRetrofit().create(ApiService.class);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_publicaciones);
        lv = findViewById(R.id.lista);
        n = findViewById(R.id.nombre);
        d = findViewById(R.id.descripcion);
        btnSave = findViewById(R.id.btnSave);
        getPublicaciones();
        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(n.getText().toString().isEmpty())
                    Config.Mensaje(getApplicationContext(),"INGRESE NOMBRE");
                else if(d.getText().toString().isEmpty())
                    Config.Mensaje(getApplicationContext(),"INGRESE DESCRIPCIÓN");
                else{
                   savePublicacion(
                           n.getText().toString(),
                           d.getText().toString()
                   );
                }

            }
        });
    }
    private void savePublicacion(String n,String d){
    Call<Publicaciones> call = servicio.savePublicacion(n,d);
    call.enqueue(new Callback<Publicaciones>() {
        @Override
        public void onResponse(Call<Publicaciones> call, Response<Publicaciones> response) {
            if(response.isSuccessful()) {
                Config.Mensaje(getApplicationContext(), "ok");
                startActivity(new Intent(getApplicationContext(),PublicacionesActivity.class));
                finish();
            }
            else
                Config.Mensaje(getApplicationContext(),"error");
        }
        @Override
        public void onFailure(Call<Publicaciones> call, Throwable t) {
            Log.d("ERROR",t.getMessage());
        }
    });
}
    private void getPublicaciones(){
        Call<List<Publicaciones>> listCall = servicio.getPublicaciones();
        listCall.enqueue(new Callback<List<Publicaciones>>() {
            @Override
            public void onResponse(Call<List<Publicaciones>> call, Response<List<Publicaciones>> response) {
                if(response.isSuccessful()){
                    Log.d("ERROR",response.body().toString());
                    adapter = new AdapterPublicaciones(getApplicationContext(),response.body());
                    lv.setAdapter(adapter);
                }
            }
            @Override
            public void onFailure(Call<List<Publicaciones>> call, Throwable t) {
                Log.d("ERROR",t.getMessage());
            }
        });
    }
    class AdapterPublicaciones extends ArrayAdapter<Publicaciones>{
        List<Publicaciones> listapublicacion;
        public AdapterPublicaciones(Context context, List<Publicaciones> lista) {
            super(context, R.layout.lista, lista);
            listapublicacion = lista;
        }
        @Override
        public View getView(int position,  View convertView, ViewGroup parent) {
            View view= LayoutInflater.from(getContext()).inflate(R.layout.lista,null);
            TextView n= view.findViewById(R.id.txtnombre);
            TextView d= view.findViewById(R.id.txtdescripcionn);
            n.setText(listapublicacion.get(position).getNombre());
            d.setText(listapublicacion.get(position).getDescripcion());
            return view;
        }
    }
}

Esta sección hace referencia a los siguientes clases:

Codeando con Retrofit

Como es claro vamos a realizar dos peticiones http, una para solicitar la lista de las publicaciones y otra para solicitar la inserción de una nueva publicación, por tanto, debemos crear una clase implementando Restrofit para usarlo en cualquiera de los dos casos, para ello crearemos un archivo con el nombre de Config dentro de una carpeta, además tambien agregaremos un método para llamar a las notificaciones Toast de cualquier parte de la app, reduciendo el código. Veamos.

public class Config {
    private static final String BASEURL = "http://xxx.xxx.xxx.xxx/arequipa/public/api/";
    private static Retrofit retrofit;
    public static Retrofit getRetrofit() {
        if (retrofit == null) {
            retrofit = new retrofit2.Retrofit.Builder()
                    .baseUrl(BASEURL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
    public static void Mensaje(Context context, String mensaje) {
        Toast.makeText(context, mensaje, Toast.LENGTH_LONG).show();
    }
}

Programando una Interface, la cual permitirá  gestionar las peticiones http solicitadas por retrofit, y tiene el siguiente código

import codea.app.arequipa.model.Publicaciones;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;

public interface ApiService {
    @GET("publicaciones")
    Call<List<Publicaciones>> getPublicaciones();
    @POST("publicaciones")
    @FormUrlEncoded
    Call<Publicaciones> savePublicacion(
            @Field("nombre") String nombre,
            @Field("descripcion") String descripcion
    );
}

Vemos entonces, que se tienen dos peticiones  GET Y POST

El modelo Publicaciones

Debemos crear nuestro modelo para determinar los campos que usaremos en la Clase, los atributos son id, nombre y descripción con sus respectivos getter y setter. 

public class Publicaciones {
    private int id;
    private String nombre;
    private String descripcion;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getNombre() {
        return nombre;
    }
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
}

 

ENTONCES, CONCLUIMOS QUE:

Es relativamente sencilla ésta app, pues esta desarrollanda con funciones básicas, para entender que una APP funcional con datos en tiempo real requiere de una arquitectura mucho más amplia y completa, pues si o si requiere de servicios web y en este caso hemos hecho un ejemplo básico implementando una API Rest para acceder a los datos en un servidor web.

Finalmente ser un desarrollador de apps requiere de conocimientos fullstask como : programar de lado del servidor  una API Rest, Diseñar Base de Datos y Diseñar interfaces, programar la lógica, especialidades que pueden también ser resueltas por un equipo de desarrollo, esto claro está que depende de la envergadura de la APP, 

LINK DE DESCARGA: Puede probar la app temporalmente en el siguiente link de descarga  CLICK AQUI, tengan en cuenta que deben configurar su dispositivo para que acepte la instalación de aplicaciones de terceros.

Hasta un próximo Post acerca de las tecnologías en Internet y el mundo Móvil


Leido 14795 veces

Compartir link del tutorial con tus amigos


Android Básico App Restaurante Android Básico App Restaurante

Curso Android Básico para desarrollar una app para un restaurante.

Descarga del código fuente

USD 12.00

Android PHP MySql App Restaurant Android PHP MySql App Restaurant

Curso Android, PHP y MySql App Restaurant con Pedidos Delivery

Descarga del código fuente

USD 27.00

Android PHP MySql Lector QR Android PHP MySql Lector QR

Curso Lector QR en Android con servicios web en PHP y MySql

Descarga del código fuente

USD 10.00

Aprende más sobre Android

Cursos de programación

Codea Codea App

México, Colombia, España, Venezuela, Argentina, Bolivia, Perú

© Todos los derechos reservados Codea App | ...de frente al código!!! | 2020 - 2023