Sistema di Like e Reazioni Ottimistiche in SwiftUI & Laravel: Guida Completa

In qualsiasi moderna app social (come Instagram, TikTok o X), quando un utente tocca l'icona del cuore per mettere "Like", la risposta visiva e aptica deve essere immediata a 0 millisecondi, senza attendere il round-trip di rete con il server. Questo approccio si chiama Optimistic UI Update.

1. Il Pattern Optimistic Update in SwiftUI

Il segreto dell'Optimistic Update consiste nel modificare immediatamente lo stato locale della vista (incrementando il contatore e attivando l'animazione di rimbalzo), inviando la chiamata di rete in un Task asincrono. Se la richiesta fallisce, lo stato viene ripristinato (rollback) con un feedback visivo d'errore.

Esempio di Componente Cuore con Animazione Spring & Haptics:

import SwiftUI

struct LikeButtonView: View {
    @State var isLiked: Bool
    @State var likesCount: Int
    let itemId: Int
    
    @State private var bounceScale: CGFloat = 1.0

    var body: some View {
        Button {
            toggleLikeOptimistic()
        } label: {
            HStack(spacing: 6) {
                Image(systemName: isLiked ? "heart.fill" : "heart")
                    .font(.system(size: 17, weight: .semibold))
                    .foregroundStyle(isLiked ? Color.red : Color.gray)
                    .scaleEffect(bounceScale)
                
                Text("\(likesCount)")
                    .font(.system(size: 13, weight: .bold, design: .rounded))
                    .foregroundStyle(isLiked ? Color.red : Color.primary)
            }
        }
        .buttonStyle(.plain)
    }

    private func toggleLikeOptimistic() {
        let previousLiked = isLiked
        let previousCount = likesCount

        // 1. Risposta aptica e aggiornamento istantaneo UI
        UIImpactFeedbackGenerator(style: .medium).impactOccurred()
        
        withAnimation(.spring(response: 0.25, dampingFraction: 0.45)) {
            isLiked.toggle()
            likesCount += isLiked ? 1 : -1
            bounceScale = 1.35
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
            withAnimation(.easeOut(duration: 0.15)) {
                bounceScale = 1.0
            }
        }

        // 2. Chiamata API asincrona in background
        Task {
            do {
                let updatedStats = try await SocialApiClient.shared.toggleLike(itemId: itemId)
                await MainActor.run {
                    self.likesCount = updatedStats.likesCount
                    self.isLiked = updatedStats.isLiked
                }
            } catch {
                // Rollback in caso di errore di rete
                await MainActor.run {
                    withAnimation {
                        self.isLiked = previousLiked
                        self.likesCount = previousCount
                    }
                    UINotificationFeedbackGenerator().notificationOccurred(.error)
                }
            }
        }
    }
}

2. Gestione Concorrente nel Backend Laravel & PostgreSQL

Sul database PostgreSQL, è fondamentale prevenire race conditions e conteggi errati quando migliaia di utenti mettono like contemporaneamente. Utilizziamo transazioni atomiche e vincoli unici su (user_id, item_id):

<?php

namespace App\Http\Controllers;

use App\Models\Like;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class LikeController extends Controller
{
    public function toggleLike(Request $request, $id)
    {
        $user = $request->user();
        $post = Post::findOrFail($id);

        $result = DB::transaction(function () use ($user, $post) {
            $existing = Like::where('user_id', $user->id)
                ->where('post_id', $post->id)
                ->lockForUpdate()
                ->first();

            if ($existing) {
                $existing->delete();
                $post->decrement('likes_count');
                $isLiked = false;
            } else {
                Like::create([
                    'user_id' => $user->id,
                    'post_id' => $post->id,
                ]);
                $post->increment('likes_count');
                $isLiked = true;
            }

            return [
                'is_liked' => $isLiked,
                'likes_count' => $post->fresh()->likes_count,
            ];
        });

        return response()->json($result);
    }
}

Vuoi integrare funzionalità social ad alte prestazioni nella tua app?

Sviluppiamo architetture mobile e cloud scalabili, progettate per gestire milioni di interazioni in tempo reale.