📘 GUÍA DEL'ALUMNE – SETMANA 5 (18/5 – 24/5)¶
UD7: API REST II & UD8: Firebase I¶
Aquesta guia està dissenyada perquè puguis treballar de manera autònoma. Conté explicacions detallades, codi complet en Java, passos pràctics, solució d'errors freqüents i criteris d'autoavaluació. Segueix l'ordre de les sessions i no avancis fins a tenir cada pas verificat.
📦 0. PREPARACIÓ PRÈVIA (Fer abans de començar la Setmana 5)¶
🔧 Configuració del projecte¶
- Android Studio: Versió 2023.2.1 o superior.
- Min SDK:
24(Android 7.0). - Permisos (
app/src/main/AndroidManifest.xml):<uses-permission android:name="android.permission.INTERNET" /> - Dependències (
app/build.gradle):dependencies { // Retrofit + Gson + OkHttp implementation 'com.squareup.retrofit2:retrofit:2.11.0' implementation 'com.squareup.retrofit2:converter-gson:2.11.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' // Firebase BoM (gestiona versions automàticament) implementation platform('com.google.firebase:firebase-bom:33.1.0') implementation 'com.google.firebase:firebase-auth' implementation 'com.google.firebase:firebase-analytics' } - Plugins (
build.gradledel projecte, no de l'app):buildscript { repositories { google(); mavenCentral() } dependencies { classpath 'com.google.gms:google-services:4.4.2' } } // Al final del mateix fitxer: apply plugin: 'com.google.gms.google-services'
✅ Verificació: Sincronitza Gradle (Sync Project). Si no hi ha errors en vermell, pots continuar.
📘 SESSIÓ 1 (2h) – Enviament POST amb Retrofit + Gestió de respostes i errors¶
🎯 Objectius¶
- Enviar dades JSON al servidor amb
@POSTi@Body - Gestionar respostes HTTP (2xx, 4xx, 5xx) i errors de xarxa
- Registrar el tràfic de xarxa per depuració
- Entendre com Retrofit gestiona fils (threading) a Android
📖 Conceptes clau¶
| Concepte | Explicació |
|---|---|
@POST("ruta") |
Indica a Retrofit que ha de fer una petició HTTP POST a la URL base + aquesta ruta. |
@Body Object |
Serialitza automàticament un objecte Java a JSON usant Gson. |
Call<T> |
Representa una petició HTTP que encara no s'ha executat. Per executar-la s'ha d'usar .enqueue() (asíncron) o .execute() (síncron, mai al fil principal). |
Callback<T> |
Interfície amb dos mètodes: onResponse() (èxit HTTP) i onFailure() (error de xarxa/connexió). |
Response<T> |
Conté isSuccessful(), code(), body() i errorBody(). Permet diferenciar errors del servidor de la resposta JSON. |
HttpLoggingInterceptor |
Eina d'OkHttp que imprimeix a Logcat les capçaleres, cos de la petició i resposta. Imprescindible per depurar. |
💻 Codi de referència (Java)¶
1. Models de dades (PostRequest.java, PostResponse.java)
public class PostRequest {
private String title;
private String body;
private int userId;
public PostRequest(String title, String body, int userId) {
this.title = title;
this.body = body;
this.userId = userId;
}
// Getters i setters necessaris per Gson
}
public class PostResponse {
private int id;
private String title;
private String body;
private int userId;
// Getters i setters
}
2. Interfície de l'API (ApiService.java)
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface ApiService {
@POST("posts")
Call<PostResponse> createPost(@Body PostRequest post);
}
3. Client Retrofit (RetrofitClient.java)
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
private static ApiService apiService;
public static ApiService getApiService() {
if (apiService == null) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
}
return apiService;
}
}
4. Ús a l'Activitat (MainActivity.java)
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
private EditText etTitle, etBody;
private Button btnSend;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etTitle = findViewById(R.id.etTitle);
etBody = findViewById(R.id.etBody);
btnSend = findViewById(R.id.btnSend);
btnSend.setOnClickListener(v -> enviarPost());
}
private void enviarPost() {
String title = etTitle.getText().toString().trim();
String body = etBody.getText().toString().trim();
if (title.isEmpty() || body.isEmpty()) {
Toast.makeText(this, "Omple tots els camps", Toast.LENGTH_SHORT).show();
return;
}
PostRequest request = new PostRequest(title, body, 1);
Call<PostResponse> call = RetrofitClient.getApiService().createPost(request);
call.enqueue(new Callback<PostResponse>() {
@Override
public void onResponse(Call<PostResponse> call, Response<PostResponse> response) {
if (response.isSuccessful() && response.body() != null) {
Toast.makeText(MainActivity.this, "✅ Enviat correctament (ID: " + response.body().getId() + ")", Toast.LENGTH_LONG).show();
} else {
// Error HTTP (4xx, 5xx)
Log.e("RETROFIT", "Codi HTTP: " + response.code());
Toast.makeText(MainActivity.this, "❌ Error del servidor: " + response.code(), Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<PostResponse> call, Throwable t) {
// Error de xarxa, DNS, timeout, sense internet
Log.e("RETROFIT", "Error de xarxa", t);
Toast.makeText(MainActivity.this, "❌ Error de connexió: " + t.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
}
🛠️ Exercici pràctic¶
- Crea un layout amb dos
EditText(títol, cos) i unButton(Enviar). - Copia/adepta les classes Java anteriors.
- Executa l'app en un emulador/dispositiu amb internet.
- Fes clic a "Enviar" i verifica:
- Apareix un
Toastverd amb l'ID retornat per JSONPlaceholder. - A Logcat, filtra per
RETROFIToOkHttpi observa la petició/respuesta en JSON. - Desactiva temporalment el WiFi i comprova que salta
onFailure.
- Apareix un
⚠️ Errors freqüents¶
| Problema | Causa | Solució |
|---|---|---|
NetworkOnMainThreadException |
Usar .execute() en lloc de .enqueue() |
Sempre usa .enqueue() a Android |
IllegalArgumentException: No Retrofit annotation found |
Falta @Body, @POST, o tipus de retorn incorrecte |
Revisa que el mètode retorna Call<T> i té @POST |
Resposta 400 o 422 |
El servidor espera un camp que no estàs enviant | Revisa la documentació de l'API o el HttpLoggingInterceptor |
| No es mostra el Toast | El callback s'executa en un thread secundari (en versions antigues) | Retrofit per defecte retorna al main thread a Android. Si fas servir execute(), usa runOnUiThread() |
✅ Autoverificació Sessió 1¶
- La petició POST s'executa sense bloquejar la UI
- Es mostren missatges diferents per èxit HTTP vs error de xarxa
- Logcat mostra el JSON enviat i rebut
- El codi gestiona camps buits abans d'enviar
📘 SESSIÓ 2 (2h) – Introducció a Firebase + Configuració¶
🎯 Objectius¶
- Entendre què és Firebase i per què s'usa com a Backend-as-a-Service (BaaS)
- Crear un projecte a la consola Firebase i registrar l'app Android
- Integrar correctament
google-services.json - Verificar que Firebase s'inicialitza sense errors
📖 Conceptes clau¶
| Concepte | Explicació |
|---|---|
| BaaS | Backend-as-a-Service. Firebase ofereix serveis llests (Auth, Firestore, Storage, Analytics) sense configurar servidors propis. |
| Consola Firebase | Portal web (console.firebase.google.com) on es gestionen projectes, apps, regles de seguretat i mètriques. |
google-services.json |
Fitxer de configuració que conté l'ID del projecte, claus d'API i informació del client Android. Ha d'anar a app/, no a l'arrel del projecte. |
Plugin google-services |
Processa el JSON durant la compilació i genera recursos/manifests automàtics. S'aplica al final de app/build.gradle. |
| SHA-1 | Impressió digital de la clau de signatura. Necessària per a alguns serveis (Google Sign-In, Dynamic Links). Per a develop/debug, Android Studio ja en genera una automàticament. |
🛠️ Passos pràctics (Seguir exactament)¶
-
Crear projecte:
- Ves a https://console.firebase.google.com
- Fes clic a
+ Afegeix un projecte→ Nom:UF5_FirebaseDemo→ Accepta termes → Continua → Desactiva Google Analytics (per simplificar) → Crea projecte.
-
Registrar app Android:
- Dins del projecte, fes clic a l'icona 🤖 Android.
- Nom del paquet: El mateix que té el teu
app/build.gradle(applicationId "com.elteu.paquet.firebase"). - Deixa en blanc el nom i SHA-1 (pots afegir-lo després si cal). Fes clic a
Registra l'app.
-
Descarregar configuració:
- Fes clic a
Descarrega google-services.json. - Mou el fitxer a:
app/google-services.json(dins la carpetaapp/, no a l'arrel). - Fes clic a
Següent→Següent→Continua fins a la consola.
- Fes clic a
-
Sincronitzar i verificar:
- Sincronitza Gradle a Android Studio.
- Afegeix aquest codi a
MainActivity.javadinsonCreate():import com.google.firebase.FirebaseApp; import android.util.Log; // ... FirebaseApp.initializeApp(this); Log.d("FIREBASE_CHECK", "Firebase inicialitzat: " + FirebaseApp.getApps(this).size() + " apps"); - Executa l'app i mira Logcat. Ha d'aparèixer:
Firebase inicialitzat: 1 appsi cap error vermell.
⚠️ Errors freqüents¶
| Problema | Causa | Solució |
|---|---|---|
Failed to resolve: com.google.gms:google-services |
Versió incorrecta o falta repositori google() |
Assegura't que google() i mavenCentral() estan a buildscript.repositories |
google-services.json not found |
Fitxer a la carpeta equivocada | Ha d'estar a app/google-services.json |
minSdkVersion must be at least 19 |
Versió mínima massa baixa | Posa minSdk 24 a defaultConfig |
Default FirebaseApp is not initialized |
Plugin no aplicat o JSON corrupte | Revisa que apply plugin: 'com.google.gms.google-services' és al final de app/build.gradle |
✅ Autoverificació Sessió 2¶
- Projecte creat a la consola Firebase
-
google-services.jsonaapp/ - Gradle sincronitza sense errors
- Logcat confirma inicialització de Firebase
- Cap advertència sobre versions o plugins
📘 SESSIÓ 3 (1h) – Firebase Authentication: Conceptes bàsics¶
🎯 Objectius¶
- Entendre el flux d'autenticació amb email i contrasenya
- Implementar registre i inici de sessió amb
FirebaseAuth - Gestionar errors específics (contrasenya feble, email duplicat)
- Detectar automàticament l'estat de l'usuari (
AuthStateListener)
📖 Conceptes clau¶
| Concepte | Explicació |
|---|---|
FirebaseAuth.getInstance() |
Singleton que gestiona la sessió de l'usuari. Manté la sessió activa encara que tanquis l'app. |
createUserWithEmailAndPassword() |
Crea un compte nou. Retorna un Task<AuthResult> amb callbacks. |
signInWithEmailAndPassword() |
Inicia sessió. Si les credencials són correctes, l'usuari queda autenticat. |
AuthStateListener |
Interfície que s'activa cada vegada que canvia l'estat d'autenticació (login, logout, token renovat). Ideal per redirigir a MainActivity o LoginActivity. |
| Seguretat per defecte | Firebase exigeix contrasenyes de mínim 6 caràcters. No permet emails duplicats al mateix projecte. |
💻 Codi de referència (Java)¶
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.FirebaseAuthWeakPasswordException;
public class LoginActivity extends AppCompatActivity {
private EditText etEmail, etPassword;
private Button btnRegister, btnLogin;
private FirebaseAuth mAuth;
private FirebaseAuth.AuthStateListener authListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
etEmail = findViewById(R.id.etEmail);
etPassword = findViewById(R.id.etPassword);
btnRegister = findViewById(R.id.btnRegister);
btnLogin = findViewById(R.id.btnLogin);
mAuth = FirebaseAuth.getInstance();
// Configurar botons
btnRegister.setOnClickListener(v -> registrar());
btnLogin.setOnClickListener(v -> iniciarSessio());
// Listener d'estat d'autenticació
authListener = firebaseAuth -> {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
Log.d("FIREBASE_AUTH", "Usuari loguejat: " + user.getEmail());
// Aquí redirigiries a la pantalla principal
// startActivity(new Intent(LoginActivity.this, HomeActivity.class));
// finish();
} else {
Log.d("FIREBASE_AUTH", "Cap usuari loguejat");
}
};
}
@Override
protected void onStart() {
super.onStart();
mAuth.addAuthStateListener(authListener);
}
@Override
protected void onStop() {
super.onStop();
if (authListener != null) {
mAuth.removeAuthStateListener(authListener);
}
}
private void registrar() {
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (!validarCamps(email, password)) return;
mAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
mostrarMissatge("✅ Registre completat");
} else {
gestionarErrorAuth(task.getException());
}
});
}
private void iniciarSessio() {
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (!validarCamps(email, password)) return;
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
mostrarMissatge("✅ Sessió iniciada");
} else {
gestionarErrorAuth(task.getException());
}
});
}
private boolean validarCamps(String email, String password) {
if (email.isEmpty() || password.isEmpty()) {
mostrarMissatge("⚠️ Omple email i contrasenya");
return false;
}
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
}
private void gestionarErrorAuth(Exception e) {
if (e instanceof FirebaseAuthWeakPasswordException) {
mostrarMissatge("❌ Contrasenya massa curta (mínim 6 caràcters)");
} else if (e instanceof FirebaseAuthUserCollisionException) {
mostrarMissatge("❌ Aquest email ja està registrat");
} else if (e instanceof FirebaseAuthInvalidCredentialsException) {
mostrarMissatge("❌ Email o contrasenya incorrectes");
} else {
mostrarMissatge("❌ Error inesperat: " + e.getMessage());
}
}
private void mostrarMissatge(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
}
🔐 Activació a la consola Firebase¶
- Ves a Authentication → Sign-in method.
- Fes clic a
Email/Password→ Activa'l → Desa. - Important: Sense aquest pas, totes les crides retornaran error intern.
🛠️ Exercici pràctic¶
- Crea
activity_login.xmlamb 2EditText(email, password) i 2Button. - Implementa el codi Java anterior.
- Prova els casos:
- Registre amb email vàlid i contrasenya ≥6 caràcters → ✅ Èxit
- Registre amb el mateix email → ❌ Col·lisió
- Login amb credencials incorrectes → ❌ Credencials invàlides
- Reinicia l'app → Verifica que
AuthStateListenerdetecta l'usuari
- Opcional: Afegeix un botó
mAuth.signOut()i verifica que el listener detecta logout.
⚠️ Errors freqüents¶
| Problema | Causa | Solució |
|---|---|---|
An internal error has occurred |
Mètode Email/Password no activat a la consola | Activa'l a Authentication → Sign-in method |
Password should be at least 6 characters |
Firebase imposa límit per defecte | Usa contrasenyes ≥6 o canvia polítiques a Consola |
FirebaseApp not initialized |
Falta google-services.json o sincronització |
Revisa Sessió 2 |
| L'usuari no es manté després de tancar l'app | Error de lògica o ús incorrecte de SharedPreferences |
No cal guardar res manualment. Firebase manté la sessió automàticament |
✅ Autoverificació Sessió 3¶
- Registre i login funcionen amb èxit
- Es capturen i mostren errors específics de Firebase
-
AuthStateListenerestà registrat aonStart()i deregistat aonStop() - La sessió persisteix després de tancar/reobrir l'app
- Cap credencial es desa manualment a
SharedPreferenceso fitxers
📊 RÚBRICA D'AUTOAVALUACIÓ SETMANA 5¶
| Criteri | Comprova-ho |
|---|---|
| Retrofit POST | Faig servir Call<T> + .enqueue(), gestiono onResponse i onFailure per separat |
| Errors HTTP/Xarxa | Diferencio response.isSuccessful() de Throwable en onFailure |
| Firebase Setup | google-services.json a app/, Gradle sense errors, Logcat confirma inicialització |
| Auth Flow | Implemento createUser i signIn amb addOnCompleteListener |
| Gestió d'estat | Uso AuthStateListener i no guardo tokens/emails manualment |
| Depuració | Utilitzo Logcat i HttpLoggingInterceptor per verificar dades i errors |
Si compleixes tots els punts, estàs llest per lliurar. Documenta amb captures de Logcat i de la consola Firebase.
🔗 RECURSOS OFICIALS (Java)¶
- Retrofit Documentation: https://square.github.io/retrofit/
- Firebase Android Setup (Java): https://firebase.google.com/docs/android/setup
- Firebase Auth (Java): https://firebase.google.com/docs/auth/android/start
- OkHttp Logging: https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
- Obtenir SHA-1 debug:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
💡 Consells finals per a l'alumne
1. No copiïs sense entendre: Cada línia de codi té una funció. Si no saps per què s'usa, pregunta-ho o documenta't.
2. Logcat és el teu millor amic: Filtres útils: RETROFIT, FirebaseAuth, Network, OkHttp.
3. Threading a Android: Mai facis crides de xarxa al fil principal. Retrofit .enqueue() ja ho gestiona. Si fas servir LiveData o ViewModel, els callbacks s'actualitzaran automàticament a la UI.
4. Seguretat: Mai hardcodegis claus, emails de prova o contrasenyes. Usa google-services.json i variables d'entorn si cal.
5. Lliurament: Envia un ZIP amb el projecte Android Studio, captures de Logcat (èxit i error), i captura de la consola Firebase amb Auth activat.
📩 Si trobes un error que no apareix en aquesta guia, indica'm: - Versió d'Android Studio - Logcat complet (primeres 20 línies de l'error) - Fragment de codi on falla I t'ajudaré a resoldre'l pas a pas.