Inicio » Blog »

8 abril, 2025

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

App Android con una API Rest en Laravel

Suscríbete a nuestro canal en Youtube

Suscríbirse

¿Quieres crear una aplicación móvil que permita a los usuarios publicar contenido y ver las últimas publicaciones en tiempo real? En este tutorial, te guiaré paso a paso para desarrollar una solución completa: una API REST con Laravel como backend y una app Android que consuma esos datos. Desde configurar el servidor hasta mostrar publicaciones en una lista, aprenderás a conectar ambas partes de forma sencilla y efectiva. ¡Perfecto para desarrolladores que buscan dominar el desarrollo full-stack!

Índice

  1. ¿Por Qué Combinar Laravel y Android Studio?
  2. Requisitos Previos
  3. Desarrollo de la API REST con Laravel
    • Paso 1: Instalar Laravel
    • Paso 2: Configurar la Base de Datos
    • Paso 3: Crear el Modelo y Migración
    • Paso 4: Definir Rutas y Controlador
  4. Desarrollo de la App Android
    • Paso 5: Configurar el Proyecto Android
    • Paso 6: Agregar Dependencias y Permisos
    • Paso 7: Diseñar la Interfaz de Usuario
    • Paso 8: Implementar Lógica con Retrofit
  5. Código Completo y Estructura
  6. Mejoras y Buenas Prácticas
  7. Conclusión: Tu App Full-Stack Lista

¿Por Qué Combinar Laravel y Android Studio?

Laravel y Android Studio forman una dupla poderosa para desarrollar aplicaciones móviles dinámicas:

  • Laravel: Framework PHP robusto para crear APIs REST rápidas y seguras.
  • Android Studio: Entorno ideal para apps Android con soporte para Retrofit y SQLite.
  • Beneficios: Escalabilidad, acceso a datos en tiempo real y una arquitectura cliente-servidor sólida.

Este tutorial te enseñará a construir una app donde los usuarios puedan publicar mensajes y ver un feed actualizado, perfecta para redes sociales, blogs o sistemas de comentarios.


Requisitos Previos

  • Laravel: PHP 7.4+, Composer, y un servidor local (XAMPP, WAMP).
  • Android Studio: Versión reciente con emulador o dispositivo físico.
  • Conocimientos básicos: PHP, Java, XML y conceptos de REST.
  • Base de datos: MySQL configurada.

Desarrollo de la API REST con Laravel

Paso 1: Instalar Laravel

Instalamos Laravel en el directorio de proyectos de XAMPP con Composer:

bash

composer create-project laravel/laravel arequipa --prefer-dist
  • --prefer-dist: Usa una versión precompilada para una instalación más rápida.

  • Resultado: Se crea el proyecto arequipa con la última versión estable.

Paso 2: Configurar la Base de Datos

Editamos el archivo .env para conectar con MySQL:

text

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=arequipa
DB_USERNAME=root
DB_PASSWORD=

Limitamos la longitud de los campos varchar en app/Providers/AppServiceProvider.php:

php

namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;

class AppServiceProvider extends ServiceProvider {
    public function boot() {
        Schema::defaultStringLength(191);
    }
}
  • Por qué: Evita errores de longitud en índices de MySQL.

Paso 3: Crear el Modelo y Migración

Generamos el modelo Publicaciones con su migración:

bash

php artisan make:model Publicaciones -m

Editamos app/Models/Publicaciones.php:

php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

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

Configuramos la migración en database/migrations/xxx_create_publicaciones_table.php:

php

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

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

    public function down() {
        Schema::dropIfExists('publicaciones');
    }
}

Ejecutamos la migración:

bash

php artisan migrate

Paso 4: Definir Rutas y Controlador

En routes/api.php, añadimos una ruta resource:

php

use App\Http\Controllers\PublicacionesController;
use Illuminate\Support\Facades\Route;

Route::resource('publicaciones', PublicacionesController::class)->only(['index', 'store']);

Creamos el controlador:

bash

php artisan make:controller PublicacionesController

Editamos app/Http/Controllers/PublicacionesController.php:

php

namespace App\Http\Controllers;
use App\Models\Publicaciones;
use Illuminate\Http\Request;

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

    public function store(Request $request) {
        $publicacion = new Publicaciones($request->only('nombre', 'descripcion'));
        $publicacion->save();
        return response()->json($publicacion, 201);
    }
}
  • index: Devuelve todas las publicaciones en JSON, ordenadas por fecha descendente.
  • store: Inserta una nueva publicación y retorna el registro creado.

Probamos la API:

bash

php artisan serve

URL de prueba: http://127.0.0.1:8000/api/publicaciones


Desarrollo de la App Android

Paso 5: Configurar el Proyecto Android

En Android Studio, creamos un nuevo proyecto llamado "Arequipa" con una actividad vacía.

Paso 6: Agregar Dependencias y Permisos

En app/build.gradle, añadimos:

gradle

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'androidx.cardview:cardview:1.0.0'
}

En AndroidManifest.xml, habilitamos internet:

xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity android:name=".PublicacionesActivity"/>
        <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>
  • usesCleartextTraffic: Permite tráfico HTTP no cifrado (solo para pruebas).

Paso 7: Diseñar la Interfaz de Usuario

res/layout/activity_main.xml (Portada):

xml

<androidx.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F5F5F5">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_logo"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/btnEntrar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <Button
        android:id="@+id/btnEntrar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Entrar"
        android:backgroundTint="#0288D1"
        android:textColor="#FFFFFF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginBottom="120dp"/>
</androidx.constraint.ConstraintLayout>

res/layout/activity_publicaciones.xml (Publicaciones):

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:elevation="4dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="8dp">
            <EditText
                android:id="@+id/nombre"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Nombre"/>
            <EditText
                android:id="@+id/descripcion"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Descripción"/>
            <Button
                android:id="@+id/btnSave"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Publicar"
                android:layout_gravity="end"
                android:backgroundTint="#0288D1"
                android:textColor="#FFFFFF"/>
        </LinearLayout>
    </androidx.cardview.widget.CardView>

    <ListView
        android:id="@+id/lista"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
</LinearLayout>

res/layout/lista.xml (Plantilla para ListView):

xml

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:elevation="4dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">
        <TextView
            android:id="@+id/txtnombre"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textStyle="bold"/>
        <TextView
            android:id="@+id/txtdescripcionn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</androidx.cardview.widget.CardView>

Paso 8: Implementar Lógica con Retrofit

Config.java:

java

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import android.content.Context;
import android.widget.Toast;

public class Config {
    private static final String BASE_URL = "http://127.0.0.1:8000/api/";
    private static Retrofit retrofit;

    public static Retrofit getRetrofit() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        }
        return retrofit;
    }

    public static void mensaje(Context context, String texto) {
        Toast.makeText(context, texto, Toast.LENGTH_SHORT).show();
    }
}

ApiService.java (Interfaz Retrofit):

java

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import java.util.List;

public interface ApiService {
    @GET("publicaciones")
    Call<List<Publicaciones>> getPublicaciones();

    @POST("publicaciones")
    @FormUrlEncoded
    Call<Publicaciones> savePublicacion(
        @Field("nombre") String nombre,
        @Field("descripcion") String descripcion
    );
}

Publicaciones.java (Modelo):

java

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; }
}

MainActivity.java:

java

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnEntrar = findViewById(R.id.btnEntrar);
        btnEntrar.setOnClickListener(v -> startActivity(new Intent(this, PublicacionesActivity.class)));
    }
}

PublicacionesActivity.java:

java

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.util.List;
public class PublicacionesActivity extends AppCompatActivity {
    private EditText nombre, descripcion;
    private Button btnSave;
    private ListView lista;
    private ApiService apiService = Config.getRetrofit().create(ApiService.class);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_publicaciones);

        nombre = findViewById(R.id.nombre);
        descripcion = findViewById(R.id.descripcion);
        btnSave = findViewById(R.id.btnSave);
        lista = findViewById(R.id.lista);
        getPublicaciones();
        btnSave.setOnClickListener(v -> {
            String n = nombre.getText().toString().trim();
            String d = descripcion.getText().toString().trim();
            if (n.isEmpty()) {
                Config.mensaje(this, "Ingrese un nombre");
            } else if (d.isEmpty()) {
                Config.mensaje(this, "Ingrese una descripción");
            } else {
                savePublicacion(n, d);
            }
        });
    }
    private void savePublicacion(String n, String d) {
        Call<Publicaciones> call = apiService.savePublicacion(n, d);
        call.enqueue(new Callback<Publicaciones>() {
            @Override
            public void onResponse(Call<Publicaciones> call, Response<Publicaciones> response) {
                if (response.isSuccessful()) {
                    Config.mensaje(PublicacionesActivity.this, "Publicación guardada");
                    nombre.setText("");
                    descripcion.setText("");
                    getPublicaciones();
                } else {
                    Config.mensaje(PublicacionesActivity.this, "Error al guardar");
                }
            }
            @Override
            public void onFailure(Call<Publicaciones> call, Throwable t) {
                Config.mensaje(PublicacionesActivity.this, "Error de conexión: " + t.getMessage());
            }
        });
    }
    private void getPublicaciones() {
        Call<List<Publicaciones>> call = apiService.getPublicaciones();
        call.enqueue(new Callback<List<Publicaciones>>() {
            @Override
            public void onResponse(Call<List<Publicaciones>> call, Response<List<Publicaciones>> response) {
                if (response.isSuccessful()) {
                    List<Publicaciones> publicaciones = response.body();
                    lista.setAdapter(new AdapterPublicaciones(PublicacionesActivity.this, publicaciones));
                }
            }
            @Override
            public void onFailure(Call<List<Publicaciones>> call, Throwable t) {
                Config.mensaje(PublicacionesActivity.this, "Error al cargar: " + t.getMessage());
            }
        });
    }
    class AdapterPublicaciones extends ArrayAdapter<Publicaciones> {
        private List<Publicaciones> publicaciones;
        public AdapterPublicaciones(Context context, List<Publicaciones> publicaciones) {
            super(context, R.layout.lista, publicaciones);
            this.publicaciones = publicaciones;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.lista, parent, false);
            }
            TextView txtNombre = convertView.findViewById(R.id.txtnombre);
TextView txtDescripcion = convertView.findViewById(R.id.txtdescripcionn);
            txtNombre.setText(publicaciones.get(position).getNombre());
            txtDescripcion.setText(publicaciones.get(position).getDescripcion());
            return convertView;
        }
    }
}

Mejoras y Buenas Prácticas

  1. Validación: Añade reglas en Laravel ($request->validate) para campos requeridos.
  2. Seguridad: Usa HTTPS en producción y tokens JWT para autenticación.
  3. Rendimiento: Implementa paginación en la API (Publicaciones::paginate(10)).
  4. Diseño: Personaliza CardView con colores y sombras consistentes.
  5. Errores: Maneja excepciones detalladamente en Retrofit y Laravel.

Conclusión: Tu App Full-Stack Lista

Con este tutorial, has creado una app móvil funcional que publica y muestra contenido en tiempo real usando Laravel y Android Studio. Desde una API REST sencilla hasta una interfaz Android con Retrofit, ahora tienes las bases para proyectos más complejos como redes sociales o foros. ¡Personaliza, escala y publica tu app! ¿Listo para más? Explora autenticación y notificaciones en nuestro próximo artículo.


Leido 16423 veces | 1 usuarios

Descarga del código fuente Android de App Android con una API Rest en Laravel

Accede al código fuente esencial de nuestra aplicación en formato ZIP ó TXT. Ideal para desarrolladores que desean personalizar o integrar nuestra solución.

Opciones de descarga

  • Usuarios Registrados: Inicia sesión para descarga inmediata.
  • Nuevos Usuarios: Regístrate y descarga.

0 descargas

Para descargar el código inicia sesión o crea una cuenta

Iniciar Sesión

Compartir link del tutorial con tus amigos

Test: Desarrollar una App Móvil para Publicar y Mostrar Publicaciones con Laravel y Android Studio

...

1. ¿Qué comando se usa para instalar Laravel en el tutorial?

2. ¿Qué archivo se modifica para limitar la longitud de los campos varchar en Laravel?

3. ¿Qué método del controlador PublicacionesController devuelve la lista de publicaciones?

4. ¿Qué biblioteca se usa en Android para realizar peticiones HTTP a la API?

5. ¿Qué permiso se añade al AndroidManifest.xml para permitir acceso a internet?

6. ¿Qué componente de Android se usa para mostrar la lista de publicaciones?

7. ¿Qué método de Retrofit se usa para ejecutar una petición asíncrona?

8. ¿Qué clase contiene la configuración de la URL base para Retrofit?

9. ¿Qué método de Laravel ordena las publicaciones por fecha descendente?

10. ¿Qué atributo en AndroidManifest.xml permite tráfico HTTP no cifrado?


Android Básico App para un Restaurante

USD 10.00

Descarga del código fuente

Android Básico App para un Restaurante
Android PHP MySql App Restaurant

USD 12.00

Descarga del código fuente

Android PHP MySql App Restaurant
Lector QR en Android PHP y MySql

USD 11.00

Descarga del código fuente

Lector QR en Android PHP y MySql

Codea Applications

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