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

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
- ¿Por Qué Combinar Laravel y Android Studio?
- Requisitos Previos
- 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
- 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
- Código Completo y Estructura
- Mejoras y Buenas Prácticas
- 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
- Validación: Añade reglas en Laravel ($request->validate) para campos requeridos.
- Seguridad: Usa HTTPS en producción y tokens JWT para autenticación.
- Rendimiento: Implementa paginación en la API (Publicaciones::paginate(10)).
- Diseño: Personaliza CardView con colores y sombras consistentes.
- 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.
Test: Desarrollar una App Móvil para Publicar y Mostrar Publicaciones con Laravel y Android Studio
...