React Native 0.86 nel 2026: Edge-to-Edge Android, DevTools e Domande per Colloqui

Padroneggia React Native 0.86 con il supporto edge-to-edge per Android 15+, miglioramenti DevTools e domande pratiche per colloqui. Guida completa con esempi di codice.

Diagramma del workflow di sviluppo React Native 0.86 edge-to-edge Android

React Native 0.86 introduce un supporto edge-to-edge completo per Android 15+, rappresentando la seconda release consecutiva senza breaking changes. Rilasciata a giugno 2026, questa versione risolve problemi critici nelle API di layout e misurazione che l'applicazione obbligatoria dell'edge-to-edge di Android stava silenziosamente compromettendo.

Zero Breaking Changes

React Native 0.86 mantiene la piena retrocompatibilità con la versione 0.85. Il percorso di aggiornamento non richiede modifiche al codice per la maggior parte delle applicazioni, rendendolo uno dei passaggi di versione major più fluidi nella storia di React Native.

Comprendere Edge-to-Edge su Android 15

I layout edge-to-edge permettono ai contenuti di estendersi sotto gli elementi UI di sistema come la barra di stato e la barra di navigazione. Android 15 impone questo comportamento per impostazione predefinita, indipendentemente dalla configurazione dell'app. React Native 0.86 garantisce che tutte le API di misurazione e layout funzionino correttamente in queste condizioni.

La funzione measureInWindow ora restituisce coordinate accurate quando la modalità edge-to-edge è attiva. In precedenza, le misurazioni potevano essere sfalsate dall'altezza degli elementi UI di sistema, causando bug di posizionamento in overlay, tooltip e pulsanti di azione flottanti.

SafeAreaHandler.tsxtypescript
import React, { useRef, useCallback } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

interface TooltipPosition {
  x: number;
  y: number;
}

export function SafeAreaHandler() {
  const buttonRef = useRef<View>(null);
  const insets = useSafeAreaInsets();

  const showTooltip = useCallback(() => {
    // measureInWindow now returns correct coordinates on Android 15+
    buttonRef.current?.measureInWindow((x, y, width, height) => {
      const position: TooltipPosition = {
        x: x + width / 2,
        // Coordinates are accurate even with edge-to-edge enabled
        y: y + height + 8,
      };
      console.log('Tooltip position:', position);
    });
  }, []);

  return (
    <View style={[styles.container, { paddingTop: insets.top }]}>
      <View ref={buttonRef} collapsable={false}>
        <TouchableOpacity onPress={showTooltip} style={styles.button}>
          <Text style={styles.buttonText}>Show Tooltip</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8 },
  buttonText: { color: '#FFFFFF', fontWeight: '600' },
});

La proprietà collapsable={false} assicura che la View mantenga un riferimento nativo per la misurazione. Senza questa impostazione, React Native potrebbe ottimizzare via la view nativa, causando il fallimento di measureInWindow.

Correzioni di KeyboardAvoidingView per Android 15

Il componente KeyboardAvoidingView calcola lo spazio disponibile in modo errato quando la modalità edge-to-edge è attiva. La versione 0.86 risolve questo problema tenendo conto degli insets UI di sistema nel calcolo dell'altezza disponibile.

ChatInput.tsxtypescript
import React, { useState } from 'react';
import {
  KeyboardAvoidingView,
  TextInput,
  Platform,
  StyleSheet,
  View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export function ChatInput() {
  const [message, setMessage] = useState('');
  const insets = useSafeAreaInsets();

  return (
    <KeyboardAvoidingView
      // behavior="padding" works correctly on Android 15+ edge-to-edge
      behavior={Platform.OS === 'ios' ? 'padding' : 'padding'}
      style={styles.container}
      // keyboardVerticalOffset accounts for headers and safe areas
      keyboardVerticalOffset={Platform.select({ ios: 88, android: 0 })}
    >
      <View style={styles.content}>
        {/* Chat messages would render here */}
      </View>
      <View style={[styles.inputContainer, { paddingBottom: insets.bottom }]}>
        <TextInput
          value={message}
          onChangeText={setMessage}
          placeholder="Type a message..."
          style={styles.input}
          multiline
        />
      </View>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  content: { flex: 1 },
  inputContainer: { borderTopWidth: 1, borderTopColor: '#E5E5E5', padding: 8 },
  input: { minHeight: 40, padding: 12, backgroundColor: '#F5F5F5', borderRadius: 20 },
});

Su Android 15, l'uso di behavior="height" spesso causa salti nel layout. Il comportamento padding fornisce animazioni più fluide e un posizionamento più prevedibile con edge-to-edge abilitato.

Aggiornamenti StatusBar durante la visualizzazione di Modal

Un bug persistente impediva le modifiche allo stile e alla visibilità della StatusBar mentre un Modal era aperto. React Native 0.86 risolve questo problema, abilitando il theming dinamico della barra di stato nei flussi modali.

ThemedModal.tsxtypescript
import React, { useState, useEffect } from 'react';
import { Modal, View, StatusBar, Text, Pressable, StyleSheet } from 'react-native';

interface ThemedModalProps {
  visible: boolean;
  onClose: () => void;
  darkMode?: boolean;
}

export function ThemedModal({ visible, onClose, darkMode = false }: ThemedModalProps) {
  // StatusBar changes now apply correctly while modal is visible
  useEffect(() => {
    if (visible) {
      StatusBar.setBarStyle(darkMode ? 'light-content' : 'dark-content', true);
      StatusBar.setBackgroundColor(darkMode ? '#1A1A1A' : '#FFFFFF', true);
    }
    return () => {
      // Restore original status bar when modal closes
      StatusBar.setBarStyle('dark-content', true);
      StatusBar.setBackgroundColor('#FFFFFF', true);
    };
  }, [visible, darkMode]);

  return (
    <Modal visible={visible} animationType="slide" onRequestClose={onClose}>
      <View style={[styles.modalContent, darkMode && styles.darkBackground]}>
        <StatusBar
          barStyle={darkMode ? 'light-content' : 'dark-content'}
          backgroundColor={darkMode ? '#1A1A1A' : '#FFFFFF'}
        />
        <Text style={[styles.title, darkMode && styles.lightText]}>
          Modal Content
        </Text>
        <Pressable onPress={onClose} style={styles.closeButton}>
          <Text style={styles.closeButtonText}>Close</Text>
        </Pressable>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  modalContent: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#FFFFFF' },
  darkBackground: { backgroundColor: '#1A1A1A' },
  title: { fontSize: 24, fontWeight: 'bold', color: '#000000' },
  lightText: { color: '#FFFFFF' },
  closeButton: { marginTop: 20, padding: 12, backgroundColor: '#007AFF', borderRadius: 8 },
  closeButtonText: { color: '#FFFFFF', fontWeight: '600' },
});

Questa correzione è particolarmente importante per le app che implementano toggle della modalità scura all'interno di flussi modali, come schermate di impostazioni o visualizzatori di immagini.

Pronto a superare i tuoi colloqui su React Native?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Emulazione Modalità Chiara e Scura in DevTools

React Native DevTools introduce il supporto per Emulation.setEmulatedMedia, abilitando il cambio istantaneo del tema senza modifiche alla configurazione del dispositivo o dell'emulatore. Si accede a questa funzionalità tramite la Command Palette con Cmd+Shift+P (macOS) o Ctrl+Shift+P (Windows/Linux).

ThemeProvider.tsxtypescript
import React, { createContext, useContext, useState, useEffect } from 'react';
import { useColorScheme, Appearance } from 'react-native';

type Theme = 'light' | 'dark';

interface ThemeContextValue {
  theme: Theme;
  toggleTheme: () => void;
  colors: typeof lightColors;
}

const lightColors = {
  background: '#FFFFFF',
  surface: '#F5F5F5',
  text: '#000000',
  textSecondary: '#666666',
  primary: '#007AFF',
  border: '#E5E5E5',
};

const darkColors = {
  background: '#1A1A1A',
  surface: '#2D2D2D',
  text: '#FFFFFF',
  textSecondary: '#A0A0A0',
  primary: '#0A84FF',
  border: '#3D3D3D',
};

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  // DevTools emulation triggers colorScheme changes via setEmulatedMedia
  const systemColorScheme = useColorScheme();
  const [theme, setTheme] = useState<Theme>(systemColorScheme ?? 'light');

  useEffect(() => {
    // Listen for DevTools theme emulation changes
    const subscription = Appearance.addChangeListener(({ colorScheme }) => {
      if (colorScheme) {
        setTheme(colorScheme);
      }
    });
    return () => subscription?.remove();
  }, []);

  const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
  const colors = theme === 'dark' ? darkColors : lightColors;

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme, colors }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme(): ThemeContextValue {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Lo stato del media emulato viene resettato quando DevTools si disconnette, assicurando che le build di produzione non siano influenzate dai test del tema durante lo sviluppo.

Domande per Colloqui: React Native 0.86 ed Edge-to-Edge

I colloqui tecnici nel 2026 si concentrano sempre più sulla compatibilità con Android 15 e sull'implementazione edge-to-edge. Padroneggiare questi concetti dimostra la comprensione delle pratiche moderne di sviluppo mobile. Per una preparazione completa ai colloqui, si consiglia di esplorare il modulo domande per colloqui sul debugging React Native.

Cos'è la modalità edge-to-edge e perché Android 15 la impone?

La modalità edge-to-edge estende i contenuti dell'app sotto gli elementi UI di sistema (barra di stato, barra di navigazione, indicatori gesture). Android 15 impone questo comportamento per impostazione predefinita per fornire esperienze immersive e UI coerente su tutta la piattaforma. Le app devono gestire gli insets delle safe area per evitare che i contenuti vengano oscurati dall'UI di sistema.

Come cambia il comportamento di measureInWindow con edge-to-edge abilitato?

Prima di React Native 0.86, measureInWindow restituiva coordinate relative alla finestra visibile, che escludeva le aree UI di sistema. Con edge-to-edge, la finestra si estende fino ai bordi dello schermo, ma le misurazioni erano erroneamente sfalsate. La versione 0.86 garantisce che le coordinate siano accurate indipendentemente dallo stato edge-to-edge.

Qual è il comportamento consigliato per KeyboardAvoidingView su Android 15?

Su Android 15 con edge-to-edge abilitato, si consiglia di usare behavior="padding". Il comportamento height può causare salti nel layout perché il calcolo dell'altezza disponibile cambia quando appare la tastiera. Il comportamento padding fornisce transizioni più fluide e un posizionamento più prevedibile.

Come differiscono gli insets delle safe area tra iOS e Android edge-to-edge?

Su iOS, gli insets delle safe area esistono dall'iPhone X per il notch e l'indicatore home. Su Android edge-to-edge, gli insets includono l'altezza della barra di stato, l'altezza della barra di navigazione e le aree degli indicatori gesture. La libreria react-native-safe-area-context fornisce valori di inset multipiattaforma tramite useSafeAreaInsets().

Pressable android_ripple con PlatformColor

React Native 0.86 aggiunge il supporto PlatformColor alla proprietà android_ripple, abilitando colori ripple dinamici che rispettano le impostazioni del tema di sistema.

ThemedButton.tsxtypescript
import React from 'react';
import { Pressable, Text, PlatformColor, Platform, StyleSheet } from 'react-native';

interface ThemedButtonProps {
  title: string;
  onPress: () => void;
}

export function ThemedButton({ title, onPress }: ThemedButtonProps) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => [
        styles.button,
        pressed && styles.pressed,
      ]}
      android_ripple={{
        // PlatformColor adapts to system theme automatically
        color: Platform.OS === 'android'
          ? PlatformColor('?attr/colorControlHighlight')
          : undefined,
        borderless: false,
        foreground: true,
      }}
    >
      <Text style={styles.text}>{title}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    padding: 16,
    backgroundColor: Platform.select({
      android: PlatformColor('?attr/colorPrimary'),
      default: '#007AFF',
    }),
    borderRadius: 8,
    alignItems: 'center',
  },
  pressed: { opacity: 0.8 },
  text: { color: '#FFFFFF', fontWeight: '600', fontSize: 16 },
});

L'uso di ?attr/colorControlHighlight assicura che il colore del ripple corrisponda alle linee guida Material Design e si adatti ai temi chiaro/scuro configurati a livello di sistema.

Image.getSize restituisce dimensioni accurate

I metodi Image.getSize e Image.getSizeWithHeaders ora restituiscono le dimensioni originali dell'immagine invece di valori ricampionati. Questa correzione è essenziale per implementare calcoli corretti del rapporto d'aspetto e layout di immagini responsive.

ResponsiveImage.tsxtypescript
import React, { useState, useEffect } from 'react';
import { Image, View, useWindowDimensions, StyleSheet } from 'react-native';

interface ResponsiveImageProps {
  uri: string;
  maxHeight?: number;
}

export function ResponsiveImage({ uri, maxHeight = 400 }: ResponsiveImageProps) {
  const { width: screenWidth } = useWindowDimensions();
  const [dimensions, setDimensions] = useState({ width: screenWidth, height: 200 });

  useEffect(() => {
    // getSize now returns true source dimensions, not downsampled values
    Image.getSize(
      uri,
      (width, height) => {
        const aspectRatio = width / height;
        let calculatedHeight = screenWidth / aspectRatio;
        
        // Constrain to maxHeight while maintaining aspect ratio
        if (calculatedHeight > maxHeight) {
          calculatedHeight = maxHeight;
        }
        
        setDimensions({
          width: screenWidth,
          height: calculatedHeight,
        });
      },
      (error) => {
        console.error('Failed to get image size:', error);
      }
    );
  }, [uri, screenWidth, maxHeight]);

  return (
    <View style={styles.container}>
      <Image
        source={{ uri }}
        style={{ width: dimensions.width, height: dimensions.height }}
        resizeMode="cover"
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { width: '100%' },
});

Questa correzione elimina la necessità di workaround che recuperavano le dimensioni dell'immagine tramite moduli nativi o elaborazione lato server.

Gestione di risposte HTTP di grandi dimensioni senza OutOfMemoryError

Il NetworkingModule in precedenza generava OutOfMemoryError durante la gestione di risposte HTTP di grandi dimensioni. La versione 0.86 implementa l'elaborazione a blocchi per le risposte che superano le soglie di memoria.

LargeFileDownload.tsxtypescript
import { useState, useCallback } from 'react';

interface DownloadProgress {
  loaded: number;
  total: number;
  percentage: number;
}

export function useLargeFileDownload() {
  const [progress, setProgress] = useState<DownloadProgress | null>(null);
  const [error, setError] = useState<string | null>(null);

  const downloadFile = useCallback(async (url: string): Promise<Blob | null> => {
    try {
      setError(null);
      setProgress({ loaded: 0, total: 0, percentage: 0 });

      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const contentLength = response.headers.get('Content-Length');
      const total = contentLength ? parseInt(contentLength, 10) : 0;

      // NetworkingModule now handles large responses without OutOfMemoryError
      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error('Response body is not readable');
      }

      const chunks: Uint8Array[] = [];
      let loaded = 0;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        chunks.push(value);
        loaded += value.length;
        
        setProgress({
          loaded,
          total,
          percentage: total > 0 ? Math.round((loaded / total) * 100) : 0,
        });
      }

      const blob = new Blob(chunks);
      setProgress({ loaded, total, percentage: 100 });
      return blob;
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Download failed';
      setError(message);
      return null;
    }
  }, []);

  return { downloadFile, progress, error };
}

Per download di file superiori a 50MB, si consiglia di utilizzare react-native-blob-util o librerie simili di gestione file native per una migliore gestione della memoria.

L'header Cookie passato attraverso il costruttore WebSocket veniva rimosso prima di raggiungere il server. Questo impediva l'autenticazione basata su sessione per le connessioni WebSocket.

AuthenticatedWebSocket.tstypescript
type MessageHandler = (data: string) => void;
type ErrorHandler = (error: Event) => void;

interface WebSocketOptions {
  url: string;
  sessionCookie: string;
  onMessage: MessageHandler;
  onError?: ErrorHandler;
}

export function createAuthenticatedWebSocket({
  url,
  sessionCookie,
  onMessage,
  onError,
}: WebSocketOptions): WebSocket {
  // Cookie header now reaches the server correctly in 0.86
  const ws = new WebSocket(url, undefined, {
    headers: {
      Cookie: sessionCookie,
      // Additional headers as needed
      'X-Client-Version': '1.0.0',
    },
  });

  ws.onopen = () => {
    console.log('WebSocket connected with session cookie');
  };

  ws.onmessage = (event) => {
    if (typeof event.data === 'string') {
      onMessage(event.data);
    }
  };

  ws.onerror = (error) => {
    console.error('WebSocket error:', error);
    onError?.(error);
  };

  ws.onclose = (event) => {
    console.log(`WebSocket closed: ${event.code} ${event.reason}`);
  };

  return ws;
}

Questa correzione è critica per le applicazioni che utilizzano la gestione delle sessioni basata su cookie insieme a funzionalità in tempo reale. Il modulo domande per colloqui sul networking React Native tratta in dettaglio i pattern di implementazione WebSocket.

Conclusione

  • React Native 0.86 fornisce correzioni edge-to-edge obbligatorie per Android 15 senza breaking changes
  • measureInWindow, KeyboardAvoidingView e Dimensions funzionano correttamente con edge-to-edge abilitato
  • Gli aggiornamenti della StatusBar ora si applicano mentre i componenti Modal sono visibili
  • L'emulazione del tema in DevTools accelera lo sviluppo e il testing della modalità scura
  • Il supporto PlatformColor in android_ripple abilita feedback touch consapevoli del tema
  • Image.getSize restituisce dimensioni originali accurate per layout responsive
  • Le risposte HTTP di grandi dimensioni non causano più OutOfMemoryError
  • Gli header Cookie WebSocket raggiungono il server come previsto

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

#react-native
#android
#mobile-development
#edge-to-edge
#devtools

Condividi

Articoli correlati