{"id":"l-rn-2-3","courseId":"react-native-apps","lang":"en","kind":"lesson","summary":"Make Trailhead’s screens fit any phone with useWindowDimensions and safe-area insets, and write platform-specific code with Platform.select or per-platform files only where the platforms really differ.","html":"<p>The phones your users carry range from small Android devices around 360 points wide to large iPhones over 430, plus tablets, foldables that change width mid-session, and landscape. On top of that, the screen isn’t a clean rectangle: there’s a notch or Dynamic Island, rounded corners, a home indicator, and on Android, status and navigation bars that your app now draws behind. Three tools make Trailhead fit all of that.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Reading the screen size the right way</h2>\n<p><code dir=\"ltr\">useWindowDimensions()</code> returns the window’s <code dir=\"ltr\">width</code>, <code dir=\"ltr\">height</code>, <code dir=\"ltr\">scale</code> and <code dir=\"ltr\">fontScale</code>, and re-renders your component whenever they change:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { useWindowDimensions } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> PhotoGrid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">photos</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">photos</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[] }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">width</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useWindowDimensions</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> columns</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> width </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 700</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …render photos in `columns` columns</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>You’ll find older code calling <code dir=\"ltr\">Dimensions.get('window')</code> at module level. That reads the size once, when the file loads, and never again: rotate the phone, open the app in iPad split view, or unfold a foldable, and the layout keeps using the old width. Use the hook inside components.</p>\n<p>Prefer flex over measurements when you can. Most layouts never need the width at all; reach for <code dir=\"ltr\">useWindowDimensions</code> when the <strong>structure</strong> changes with size (two columns become four), not to compute pixel values flexbox already handles.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Respect fontScale</span></p>\n<p><code dir=\"ltr\">fontScale</code> is above 1 when the user has turned up their system text size; 1.3 or more is common among older hikers, which is Trailhead’s audience. Test your screens with the largest accessibility text size at least once. Rows with fixed heights are the first thing to break.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">Safe areas on both platforms</h2>\n<p>The <strong>safe area</strong> is the part of the screen not covered by hardware cutouts or system UI. Navigation headers and tab bars from Expo Router already respect it. Screens without them, like a full-screen map or an onboarding page, must handle it themselves.</p>\n<p>The library for this is <code dir=\"ltr\">react-native-safe-area-context</code>. Expo Router already includes it and provides the <code dir=\"ltr\">SafeAreaProvider</code> at the root, so you can use the hook straight away:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/app/track.tsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { StyleSheet, Text, View } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { useSafeAreaInsets } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native-safe-area-context'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> TrackScreen</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> insets</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useSafeAreaInsets</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">styles.screen</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      {</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* the map fills the whole screen, edge to edge */</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[styles.topBar, { top: insets.top </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">styles.timer</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>01:24:10&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[styles.bottomBar, { paddingBottom: insets.bottom </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Stop and save&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> styles</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StyleSheet.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">create</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  screen: { flex: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  topBar: { position: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'absolute'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, left: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, right: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, alignItems: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'center'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  timer: { fontSize: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, fontWeight: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'700'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  bottomBar: { position: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'absolute'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, left: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, right: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, bottom: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, paddingTop: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, alignItems: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'center'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p>The map can draw under the status bar, which looks good, while the controls sit exactly below the unsafe area. <code dir=\"ltr\">insets</code> has <code dir=\"ltr\">top</code>, <code dir=\"ltr\">bottom</code>, <code dir=\"ltr\">left</code> and <code dir=\"ltr\">right</code>, so landscape works too. The library also exports a <code dir=\"ltr\">SafeAreaView</code> component that applies padding for you, which is handy for simple screens.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Using SafeAreaView from react-native</span></p>\n<p><code dir=\"ltr\">import { SafeAreaView } from 'react-native'</code> still compiles, but it’s deprecated, only ever worked on iOS, and ignores Android’s edge-to-edge layout. On recent Android versions apps draw behind the status and navigation bars, so content without insets ends up under the clock or the gesture bar. Import from <code dir=\"ltr\">react-native-safe-area-context</code> instead.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">When the platforms differ</h2>\n<p>React Native shares code across iOS and Android, but the platforms aren’t identical, and pretending they are produces an app that feels foreign on both. <code dir=\"ltr\">Platform</code> gives you the current OS:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Platform, StyleSheet } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> styles</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StyleSheet.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">create</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  title: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    fontSize: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">17</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    fontWeight: Platform.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">OS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'ios'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '600'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '500'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  card: Platform.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    ios: { boxShadow: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'0 2px 8px rgba(0, 0, 0, 0.12)'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    android: { elevation: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    default: {},</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p><code dir=\"ltr\">Platform.select</code> returns the value for the current platform, falling back to <code dir=\"ltr\">default</code>. It’s the right tool for small differences: a font weight, a shadow, a hit slop.</p>\n<p>When a <strong>whole component</strong> differs, split it into files. Create <code dir=\"ltr\">DatePicker.ios.tsx</code> and <code dir=\"ltr\">DatePicker.android.tsx</code>, then import <code dir=\"ltr\">./DatePicker</code> with no extension; Metro picks the right file per platform. The same works with <code dir=\"ltr\">.native.tsx</code> and <code dir=\"ltr\">.web.tsx</code> if Trailhead ever runs on the web.</p>\n<p>Where do the platforms really differ? Navigation conventions (iOS swipes back from the left edge; Android has a system back gesture), pickers and date inputs, typography defaults, haptics, and permission flows. Where they don’t, such as your hike list, your forms and your business logic, share everything.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">A tablet-friendly grid</h2>\n<p>Putting the pieces together: the photo grid should show as many columns as fit, with a minimum card width, consistent gaps and screen padding. That’s a small calculation, run every time the width changes, and it’s exactly the kind of pure function worth getting right once. You’ll write it in the exercise below.</p>\n<p>Next up: lists. Trailhead will soon hold hundreds of hikes, and rendering them all at once is the fastest way to make a phone feel slow.</p>\n","toc":[{"id":"sec-1","text":"Reading the screen size the right way","level":2},{"id":"sec-2","text":"Safe areas on both platforms","level":2},{"id":"sec-3","text":"When the platforms differ","level":2},{"id":"sec-4","text":"A tablet-friendly grid","level":2}],"takeaways":["<code dir=\"ltr\">useWindowDimensions()</code> re-renders on rotation and resizing; <code dir=\"ltr\">Dimensions.get()</code> is a one-time snapshot that goes stale.","Screens without a navigation header or tab bar must pad themselves with safe-area insets from react-native-safe-area-context.","Android draws apps edge to edge behind the system bars, so insets matter on Android as much as on iOS.","Use <code dir=\"ltr\">Platform.select</code> for small differences and <code dir=\"ltr\">.ios.tsx</code> / <code dir=\"ltr\">.android.tsx</code> files when a whole component differs."],"further":[{"title":"Safe areas","url":"https://docs.expo.dev/develop/user-interface/safe-areas/"},{"title":"useWindowDimensions","url":"https://reactnative.dev/docs/usewindowdimensions"},{"title":"Platform-Specific Code","url":"https://reactnative.dev/docs/platform-specific-code"}],"quiz":[{"q":"<p>Trailhead’s photo grid computes its columns once with <code dir=\"ltr\">Dimensions.get('window').width</code> at the top of the file. What breaks?</p>","options":[{"text":"Nothing; screen width never changes while an app runs.","why":"It changes on rotation, in iPad split view and on foldables, and the stored value doesn’t update."},{"text":"The grid keeps its old column count after the user rotates the phone or unfolds a foldable.","why":"Correct. Read size with <code dir=\"ltr\">useWindowDimensions()</code> inside the component so it re-renders with the new width."},{"text":"It crashes on Android because <code dir=\"ltr\">Dimensions</code> is iOS-only.","why":"<code dir=\"ltr\">Dimensions</code> works on both platforms. The problem is that the value is read once and never refreshed."}],"answer":1},{"q":"<p>Trailhead’s full-screen map has no header. Its top buttons sit under the iPhone’s Dynamic Island. What’s the right fix?</p>","options":[{"text":"Add <code dir=\"ltr\">paddingTop: 50</code> to the screen.","why":"The right amount differs by device, orientation and platform. A magic number is wrong on most phones."},{"text":"Wrap the screen in <code dir=\"ltr\">SafeAreaView</code> imported from <code dir=\"ltr\">react-native</code>.","why":"That component is deprecated, works only on iOS, and doesn’t handle Android’s edge-to-edge layout."},{"text":"Hide the status bar so nothing overlaps.","why":"The Dynamic Island and rounded corners are hardware; hiding the status bar doesn’t move them."},{"text":"Read <code dir=\"ltr\">useSafeAreaInsets()</code> and apply <code dir=\"ltr\">insets.top</code> to the buttons’ container.","why":"Correct. Insets are the exact size of the unsafe area on this device, right now, on both platforms."}],"answer":3},{"q":"<p>Trailhead’s date picker needs completely different components on iOS and Android, while the rest of the form is shared. What’s the cleanest structure?</p>","options":[{"text":"Two files, <code dir=\"ltr\">DatePicker.ios.tsx</code> and <code dir=\"ltr\">DatePicker.android.tsx</code>, imported as <code dir=\"ltr\">./DatePicker</code>.","why":"Correct. The bundler picks the right file per platform, and each file stays simple and readable."},{"text":"One component with <code dir=\"ltr\">if (Platform.OS === 'ios')</code> around every line that differs.","why":"That works for a line or two, but a whole component full of branches is hard to read and easy to break."},{"text":"Two separate apps, one per platform.","why":"That throws away the shared form logic, the main reason to use React Native."}],"answer":0}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// Work out how many photo cards fit across the screen, and how wide each one is.\nfunction gridLayout(screenWidth, { minCardWidth = 160, gap = 12, padding = 16 } = {}) {\n  const columns = Math.floor(screenWidth / minCardWidth);\n  const cardWidth = screenWidth / columns;\n  return { columns, cardWidth };\n}\n\nconsole.log(gridLayout(390)); // iPhone in portrait\nconsole.log(gridLayout(820)); // iPad in portrait\n"},"solution":{"js":"function gridLayout(screenWidth, { minCardWidth = 160, gap = 12, padding = 16 } = {}) {\n  const available = screenWidth - padding * 2;\n  const columns = Math.max(1, Math.floor((available + gap) / (minCardWidth + gap)));\n  const cardWidth = Math.floor((available - gap * (columns - 1)) / columns);\n  return { columns, cardWidth };\n}\n\nconsole.log(gridLayout(390)); // iPhone in portrait\nconsole.log(gridLayout(820)); // iPad in portrait\n"},"checks":[{"id":"phone","test":"return typeof gridLayout === 'function' && (() => { const r = gridLayout(390); return r && r.columns === 2 && r.cardWidth === 173; })();"},{"id":"small-phone","test":"return (() => { const r = gridLayout(320); return r && r.columns === 1 && r.cardWidth === 288; })();"},{"id":"tablet","test":"return (() => { const r = gridLayout(820); return r && r.columns === 4 && r.cardWidth === 188; })();"},{"id":"options","test":"return (() => { const r = gridLayout(400, { minCardWidth: 100, gap: 8, padding: 20 }); return r && r.columns === 3 && r.cardWidth === 114; })();"},{"id":"never-zero","test":"return (() => { const r = gridLayout(200, { minCardWidth: 300 }); return r && r.columns === 1 && r.cardWidth === 168; })();"}],"title":"Fit the photo grid to any screen","prompt":"<p>Trailhead’s photo grid calls <code dir=\"ltr\">gridLayout(width)</code> with the value from <code dir=\"ltr\">useWindowDimensions()</code>. Fix <code dir=\"ltr\">gridLayout(screenWidth, options)</code> so it returns <code dir=\"ltr\">{ columns, cardWidth }</code> where:</p>\n<ul>\n<li>the screen has <code dir=\"ltr\">padding</code> on the left <strong>and</strong> right, and cards have <code dir=\"ltr\">gap</code> points between them (no gap at the outer edges),</li>\n<li><code dir=\"ltr\">columns</code> is the <strong>largest</strong> number of cards that are each at least <code dir=\"ltr\">minCardWidth</code> wide, and never less than 1,</li>\n<li><code dir=\"ltr\">cardWidth</code> is the width each card gets when the leftover space is shared equally, rounded <strong>down</strong> to a whole point.</li>\n</ul>\n<p>Defaults: <code dir=\"ltr\">minCardWidth = 160</code>, <code dir=\"ltr\">gap = 12</code>, <code dir=\"ltr\">padding = 16</code>. For example, a 390-point iPhone gives <code dir=\"ltr\">{ columns: 2, cardWidth: 173 }</code>.</p>\n","hints":["Start with the space you can actually use: <code dir=\"ltr\">screenWidth - padding * 2</code>.","n cards need n widths and n - 1 gaps. So n cards fit when <code dir=\"ltr\">n * minCardWidth + (n - 1) * gap &lt;= available</code>, which rearranges to <code dir=\"ltr\">n &lt;= (available + gap) / (minCardWidth + gap)</code>.","Then <code dir=\"ltr\">cardWidth = Math.floor((available - gap * (columns - 1)) / columns)</code>, and wrap the column count in <code dir=\"ltr\">Math.max(1, …)</code>."],"explanation":"<p>The starter ignored padding and gaps, so cards overflowed the screen, and it could return zero columns on a narrow screen (dividing by zero gives <code dir=\"ltr\">Infinity</code>). The fix starts from the usable width, counts how many card-plus-gap units fit (with one fewer gap than cards), guarantees at least one column, and shares the leftover width equally. Because it’s a pure function of the width, the grid updates correctly on rotation and on foldables as soon as <code dir=\"ltr\">useWindowDimensions</code> reports a new size.</p>\n","checkLabels":{"phone":"A 390-point phone gets 2 columns of 173","small-phone":"A 320-point phone gets 1 column of 288","tablet":"An 820-point tablet gets 4 columns of 188","options":"Custom minCardWidth, gap and padding are respected","never-zero":"A screen narrower than one card still gets 1 column"}},"words":642,"readMinutes":3}