RN Lezione 12: API avanzate, Refresh e POST
Obiettivi
Section titled “Obiettivi”- Aggiungere pull-to-refresh alla FlatList
- Fare chiamate POST con body JSON
- Passare parametri a query API
- Gestire errori di rete in modo robusto
1. Pull-to-refresh — tirare per ricaricare
Section titled “1. Pull-to-refresh — tirare per ricaricare”const [refreshing, setRefreshing] = useState(false);const [dati, setDati] = useState([]);
const caricaDati = async () => { const res = await fetch('https://api.esempio.com/dati'); const json = await res.json(); setDati(json);};
const onRefresh = async () => { setRefreshing(true); await caricaDati(); // ← ricarica i dati setRefreshing(false);};
<FlatList data={dati} renderItem={renderItem} refreshing={refreshing} // ← mostra/nasconde indicatore onRefresh={onRefresh} // ← funzione da chiamare/>Esempio completo con refresh
Section titled “Esempio completo con refresh”import { View, Text, FlatList, ActivityIndicator, StyleSheet } from 'react-native';import { useState, useEffect } from 'react';
export default function App() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [errore, setErrore] = useState(null);
useEffect(() => { fetchPosts(); }, []);
const fetchPosts = async () => { try { const res = await fetch( 'https://jsonplaceholder.typicode.com/posts?_limit=10' ); if (!res.ok) throw new Error('Errore caricamento'); const data = await res.json(); setPosts(data); setErrore(null); } catch (err) { setErrore(err.message); } finally { setLoading(false); setRefreshing(false); } };
const onRefresh = () => { setRefreshing(true); fetchPosts(); };
if (loading) { return ( <View style={styles.center}> <ActivityIndicator size="large" /> </View> ); }
if (errore && posts.length === 0) { return ( <View style={styles.center}> <Text style={styles.erroreTesto}>❌ {errore}</Text> </View> ); }
return ( <View style={styles.container}> <FlatList data={posts} renderItem={({ item }) => ( <View style={styles.card}> <Text style={styles.titolo}>{item.title}</Text> <Text style={styles.corpo}>{item.body}</Text> </View> )} keyExtractor={item => item.id.toString()} refreshing={refreshing} onRefresh={onRefresh} /> </View> );}2. API con parametri di query
Section titled “2. API con parametri di query”// URL con parametri (stringa)const citta = 'Roma';const res = await fetch( `https://geocoding-api.open-meteo.com/v1/search?name=${citta}&count=1`);
// URL con parametri (URLSearchParams)const params = new URLSearchParams({ name: 'Roma', count: '1', language: 'it', format: 'json',});const res = await fetch( `https://geocoding-api.open-meteo.com/v1/search?${params}`);Esempio: Ricerca città meteo
Section titled “Esempio: Ricerca città meteo”const [citta, setCitta] = useState('');const [risultati, setRisultati] = useState([]);const [cercando, setCercando] = useState(false);
const cerca = async () => { if (!citta.trim()) return; setCercando(true); try { const res = await fetch( `https://geocoding-api.open-meteo.com/v1/search?name=${citta}&count=5` ); const data = await res.json(); setRisultati(data.results || []); } catch (err) { Alert.alert('Errore', err.message); } finally { setCercando(false); }};3. POST — inviare dati
Section titled “3. POST — inviare dati”const creaPost = async () => { try { const response = await fetch( 'https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: 'Nuovo post', body: 'Contenuto del post...', userId: 1, }), } ); const data = await response.json(); console.log('Creato con ID:', data.id); } catch (err) { console.error('Errore:', err); }};Esempio: Form che invia dati
Section titled “Esempio: Form che invia dati”const [titolo, setTitolo] = useState('');const [corpo, setCorpo] = useState('');const [salvataggio, setSalvataggio] = useState(false);
const inviaPost = async () => { if (!titolo || !corpo) { Alert.alert('Errore', 'Compila tutti i campi'); return; }
setSalvataggio(true); try { const res = await fetch( 'https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: titolo, body: corpo, userId: 1 }), } ); const data = await res.json(); Alert.alert('✅ Successo', `Post creato con ID ${data.id}`); setTitolo(''); setCorpo(''); } catch (err) { Alert.alert('❌ Errore', err.message); } finally { setSalvataggio(false); }};4. Gestione errori robusta
Section titled “4. Gestione errori robusta”const fetchSicura = async (url, options = {}) => { try { const response = await fetch(url, options);
// Controllo HTTP error if (!response.ok) { throw new Error( `Errore HTTP ${response.status}: ${response.statusText}` ); }
const data = await response.json(); return { success: true, data }; } catch (err) { // Distinguere errori di rete da errori API if (err.message.includes('Network request failed')) { return { success: false, error: 'Nessuna connessione internet. Controlla la rete.', }; } if (err.message.includes('HTTP')) { return { success: false, error: `Errore del server: ${err.message}`, }; } return { success: false, error: err.message }; }};
// Usoconst carica = async () => { const result = await fetchSicura('https://api.esempio.com/dati'); if (result.success) { setDati(result.data); } else { Alert.alert('Errore', result.error); }};5. API Key — come usarle in sicurezza
Section titled “5. API Key — come usarle in sicurezza”Per app didattiche, puoi mettere la chiave in un file di configurazione:
export const API_KEY = 'la_tua_chiave_qui';
// App.jsimport { API_KEY } from './config';
const res = await fetch( `https://api.openweathermap.org/data/2.5/weather?q=Roma&appid=${API_KEY}`);⚠️ Per app reali: mai hardcodare API key! Usa variabili d’ambiente o un backend proxy.
6. Esempio guidato: App Meteo completa
Section titled “6. Esempio guidato: App Meteo completa”import { View, Text, TextInput, TouchableOpacity, ActivityIndicator, StyleSheet, Alert } from 'react-native';import { useState, useEffect } from 'react';import AsyncStorage from '@react-native-async-storage/async-storage';
const CHIAVE_CITTA = '@ultima_citta';
export default function App() { const [citta, setCitta] = useState(''); const [meteo, setMeteo] = useState(null); const [loading, setLoading] = useState(false); const [errore, setErrore] = useState('');
useEffect(() => { caricaUltimaCitta(); }, []);
const caricaUltimaCitta = async () => { const saved = await AsyncStorage.getItem(CHIAVE_CITTA); if (saved) { setCitta(saved); cercaMeteo(saved); } };
const cercaMeteo = async (nomeCitta) => { if (!nomeCitta.trim()) return; setLoading(true); setErrore('');
try { // 1. Geocoding (trova coordinate) const geoRes = await fetch( `https://geocoding-api.open-meteo.com/v1/search?name=${nomeCitta}&count=1` ); const geoData = await geoRes.json();
if (!geoData.results || geoData.results.length === 0) { throw new Error('Città non trovata'); }
const { latitude, longitude, name } = geoData.results[0];
// 2. Meteo attuale const meteoRes = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}` + `¤t=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code&timezone=auto` ); const meteoData = await meteoRes.json();
setMeteo({ citta: name, ...meteoData.current }); await AsyncStorage.setItem(CHIAVE_CITTA, name); } catch (err) { setErrore(err.message); setMeteo(null); } finally { setLoading(false); } };
return ( <View style={styles.container}> <Text style={styles.titolo}>🌤️ Meteo</Text>
<TextInput style={styles.input} placeholder="Nome città..." value={citta} onChangeText={setCitta} onSubmitEditing={() => cercaMeteo(citta)} />
<TouchableOpacity style={styles.bottone} onPress={() => cercaMeteo(citta)} disabled={loading} > {loading ? ( <ActivityIndicator color="white" /> ) : ( <Text style={styles.bottoneTesto}>Cerca</Text> )} </TouchableOpacity>
{errore ? ( <View style={styles.cardErrore}> <Text style={styles.erroreTesto}>❌ {errore}</Text> </View> ) : null}
{meteo ? ( <View style={styles.cardMeteo}> <Text style={styles.citta}>{meteo.citta}</Text> <Text style={styles.temp}>{meteo.temperature_2m}°C</Text> <Text style={styles.percepita}> Percepita: {meteo.apparent_temperature}°C </Text> <Text style={styles.umidita}> Umidità: {meteo.relative_humidity_2m}% </Text> </View> ) : null} </View> );}7. Tabella riassuntiva
Section titled “7. Tabella riassuntiva”| Funzionalità | Codice |
|---|---|
| Pull-to-refresh | refreshing={s} onRefresh={fn} su FlatList |
| GET con parametri | fetch(`url?${new URLSearchParams(p)}`) |
| POST dati | fetch(url, {method:'POST', body: JSON.stringify(d)}) |
| API Key | Variabile in config.js (solo didattica) |
| Gestione errori | try/catch + !response.ok check |
8. Esercizio autonomo
Section titled “8. Esercizio autonomo”🎯 Esercizio: “App Citazioni con refresh”
Section titled “🎯 Esercizio: “App Citazioni con refresh””Crea un’app che:
- Carica una citazione casuale da
https://api.quotable.io/random - Mostra: citazione (grande, corsivo) + autore
- Bottone “Nuova citazione” → carica altra
- Pull-to-refresh sulla ScrollView
- ActivityIndicator durante caricamento
- Gestione errore
- Salva l’ultima citazione in AsyncStorage
Struttura risposta API
Section titled “Struttura risposta API”{ "content": "La vita è ciò che accade...", "author": "John Lennon"}